{
  "openapi": "3.1.0",
  "info": {
    "title": "FedKYC API",
    "version": "1.0.0",
    "description": "Vérification d'identité hébergée. L'application cliente crée une vérification depuis son backend, redirige la personne vers l'URL hébergée, et reçoit le résultat par webhook signé. Les documents, le selfie et la biométrie ne quittent JAMAIS FedKYC : seul le résultat sort.",
    "contact": {
      "name": "Fed Digital",
      "email": "wangfed2007@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://kyc.fedtopup.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "CleApi": []
    }
  ],
  "tags": [
    {
      "name": "Vérifications",
      "description": "Créer, lire, annuler, relancer."
    },
    {
      "name": "Sujets",
      "description": "Statut KYC d'un utilisateur de votre application."
    },
    {
      "name": "Preuves",
      "description": "Verification Proof JWT, vérifiable hors ligne."
    },
    {
      "name": "Service",
      "description": "Capacités et santé."
    }
  ],
  "paths": {
    "/capabilities": {
      "get": {
        "tags": [
          "Service"
        ],
        "summary": "Pays, documents, niveaux et options disponibles",
        "security": [],
        "responses": {
          "200": {
            "description": "Capacités",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          }
        }
      }
    },
    "/verifications": {
      "post": {
        "tags": [
          "Vérifications"
        ],
        "summary": "Créer une vérification et obtenir son URL hébergée",
        "description": "Appelez-la depuis VOTRE BACKEND. L'en-tête `Idempotency-Key` est obligatoire : deux appels avec la même clé rendent la même vérification, jamais deux.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Identifiant unique de VOTRE côté pour cet appel."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreerVerification"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vérification créée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationCreee"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "403": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/verifications/{id}": {
      "get": {
        "tags": [
          "Vérifications"
        ],
        "summary": "Lire le statut et le résultat",
        "description": "Ne retourne jamais d'image, de selfie, de gabarit biométrique ni de numéro de document complet.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdVerification"
          }
        ],
        "responses": {
          "200": {
            "description": "Vérification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/verifications/{id}/cancel": {
      "post": {
        "tags": [
          "Vérifications"
        ],
        "summary": "Annuler une vérification en cours",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdVerification"
          }
        ],
        "responses": {
          "200": {
            "description": "Annulée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/verifications/{id}/retry": {
      "post": {
        "tags": [
          "Vérifications"
        ],
        "summary": "Relancer un parcours en retry_required",
        "description": "Les tentatives sont plafonnées. Au-delà, la réponse est 409 MAX_ATTEMPTS_REACHED.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdVerification"
          }
        ],
        "responses": {
          "200": {
            "description": "Relancée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationCreee"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/verifications/{id}/proof": {
      "post": {
        "tags": [
          "Preuves"
        ],
        "summary": "Émettre un Verification Proof (JWT EdDSA)",
        "description": "Preuve de courte durée, signée Ed25519, vérifiable avec le JWKS public. Aucune donnée personnelle par défaut. Pour une action sensible, redemandez le statut frais.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdVerification"
          }
        ],
        "responses": {
          "200": {
            "description": "Preuve",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preuve"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/subjects/{external_user_id}/status": {
      "get": {
        "tags": [
          "Sujets"
        ],
        "summary": "Statut KYC d'un de VOS utilisateurs",
        "description": "La clé de lecture est (votre application, external_user_id). L'identifiant « 123 » chez vous n'a aucun rapport avec « 123 » chez un autre.",
        "parameters": [
          {
            "name": "external_user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "customer_98734"
          }
        ],
        "responses": {
          "200": {
            "description": "Statut",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatutSujet"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "CleApi": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer fkyc_live_…` ou `fkyc_test_…`. Cette clé est un secret de BACKEND : elle ne doit jamais figurer dans le code d'une page web ni dans une application mobile."
      }
    },
    "parameters": {
      "IdVerification": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^fkyc_ver_[0-9A-HJKMNP-TV-Z]{26}$"
        },
        "example": "fkyc_ver_068MFNTFPF28KVMYPMP2SQEBC3"
      }
    },
    "responses": {
      "Erreur": {
        "description": "Erreur",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erreur"
            }
          }
        }
      }
    },
    "schemas": {
      "Erreur": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "RATE_LIMITED"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreerVerification": {
        "type": "object",
        "required": [
          "external_user_id"
        ],
        "properties": {
          "external_user_id": {
            "type": "string",
            "maxLength": 200,
            "example": "customer_98734",
            "description": "VOTRE identifiant pour cette personne. Il peut être opaque : nous n'avons besoin de rien d'autre."
          },
          "required_level": {
            "type": "string",
            "enum": [
              "standard"
            ],
            "default": "standard"
          },
          "country": {
            "type": "string",
            "enum": [
              "HT"
            ],
            "default": "HT"
          },
          "document_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "national_id",
                "driver_license",
                "passport"
              ]
            },
            "description": "Documents acceptés pour cette vérification. Par défaut : les trois."
          },
          "return_url": {
            "type": "string",
            "format": "uri",
            "description": "Doit correspondre EXACTEMENT à une URL déclarée par votre application. Aucun joker."
          },
          "locale": {
            "type": "string",
            "enum": [
              "fr",
              "en",
              "ht"
            ],
            "default": "fr"
          },
          "reference": {
            "type": "string",
            "description": "Votre référence libre."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "VerificationCreee": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "fkyc_ver_068MFNTFPF28KVMYPMP2SQEBC3"
          },
          "status": {
            "$ref": "#/components/schemas/Statut"
          },
          "verification_url": {
            "type": "string",
            "format": "uri",
            "example": "https://kyc.fedtopup.com/v/fkyc_ver_068MFNTFPF28KVMYPMP2SQEBC3"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Statut": {
        "type": "string",
        "enum": [
          "created",
          "started",
          "document_submitted",
          "processing",
          "retry_required",
          "verified",
          "rejected",
          "unable_to_verify",
          "expired",
          "cancelled",
          "technical_error"
        ],
        "description": "Il n'existe AUCUN statut d'attente d'approbation humaine : la décision est automatique. Un résultat incertain devient `retry_required`, jamais `verified`."
      },
      "Verification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "external_user_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/Statut"
          },
          "level": {
            "type": "string",
            "enum": [
              "standard"
            ]
          },
          "result": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "reason_code": {
            "type": "string",
            "nullable": true
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "assurance_source": {
            "type": "string",
            "example": "document_biometric_self_hosted"
          },
          "government_registry_checked": {
            "type": "boolean",
            "example": false,
            "description": "Faux tant qu'aucun registre officiel n'est branché. FedKYC vérifie un document et un visage, pas un état civil."
          }
        }
      },
      "StatutSujet": {
        "type": "object",
        "properties": {
          "external_user_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "unverified",
              "processing",
              "verified",
              "rejected",
              "expired",
              "restricted"
            ]
          },
          "level": {
            "type": "string",
            "nullable": true
          },
          "verified_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "assurance_source": {
            "type": "string"
          },
          "government_registry_checked": {
            "type": "boolean"
          }
        }
      },
      "Preuve": {
        "type": "object",
        "properties": {
          "proof": {
            "type": "string",
            "description": "JWT compact signé EdDSA (Ed25519)."
          },
          "token_type": {
            "type": "string",
            "example": "JWT"
          },
          "alg": {
            "type": "string",
            "example": "EdDSA"
          },
          "kid": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Capabilities": {
        "type": "object",
        "properties": {
          "countries": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "levels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "features": {
            "type": "object",
            "additionalProperties": true
          },
          "assurance_source": {
            "type": "string"
          }
        }
      }
    }
  },
  "x-webhooks": {
    "description": "Chaque envoi porte `FedKYC-Signature: t=<horodatage>,v1=<hex>`, où v1 = HMAC-SHA256(secret, `${t}.${corpsBrut}`). Vérifiez la signature SUR LE CORPS BRUT, et refusez un horodatage de plus de 5 minutes. `FedKYC-Event-Id` est unique : traitez-le en idempotence.",
    "events": [
      "verification.created",
      "verification.started",
      "verification.document_submitted",
      "verification.processing",
      "verification.retry_required",
      "verification.verified",
      "verification.rejected",
      "verification.expired",
      "verification.cancelled",
      "verification.technical_error"
    ]
  }
}