{
  "openapi": "3.1.0",
  "info": {
    "title": "Nouko API",
    "version": "1.0.0",
    "description": "Vérification d'identité avec revue humaine (Bénin, Côte d'Ivoire). Les résultats finaux sont envoyés par webhooks signés. Documentation : https://nouko.openclouds.fr/docs — version Markdown pour les IA : https://nouko.openclouds.fr/llms-full.txt"
  },
  "servers": [
    {
      "url": "https://nouko.openclouds.fr/api/public"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé projet nk_test_… (sandbox) ou nk_live_… (production)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Session": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string"
          },
          "country": {
            "enum": [
              "BJ",
              "CI"
            ]
          },
          "tier": {
            "type": "integer"
          },
          "achieved_tier": {
            "type": "integer"
          },
          "status": {
            "enum": [
              "draft",
              "pending",
              "approved",
              "rejected",
              "complement",
              "expired",
              "cancelled"
            ]
          },
          "environment": {
            "enum": [
              "sandbox",
              "production"
            ]
          },
          "metadata": {
            "type": "object"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateSession": {
        "type": "object",
        "required": [
          "reference",
          "country"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "maxLength": 120
          },
          "country": {
            "enum": [
              "BJ",
              "CI"
            ]
          },
          "tier": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3,
            "default": 2
          },
          "redirect_url": {
            "type": "string",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "maxProperties": 20
          },
          "expires_in": {
            "type": "integer",
            "minimum": 900,
            "maximum": 2592000,
            "default": 604800
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "environment": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/Session"
          }
        }
      },
      "SessionList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Session"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Decision": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "final": {
            "type": "boolean"
          },
          "decision": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "approved",
              "declined",
              null
            ]
          },
          "tier": {
            "type": "integer"
          },
          "achieved_tier": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "check_type": {
                  "type": "string"
                },
                "provider": {
                  "type": "string"
                },
                "outcome": {
                  "type": "string"
                },
                "risk": {
                  "type": "string"
                },
                "created_at": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/sessions": {
      "post": {
        "summary": "Créer une session de vérification",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSession"
              },
              "example": {
                "reference": "client-42",
                "country": "BJ",
                "tier": 2,
                "redirect_url": "https://votre-site.com/kyc/retour",
                "metadata": {
                  "user_id": "42"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session créée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                },
                "example": {
                  "id": "6f1c2a9e-0000-4000-8000-000000000000",
                  "reference": "client-42",
                  "status": "draft",
                  "country": "BJ",
                  "tier": 2,
                  "environment": "sandbox",
                  "redirect_url": "https://votre-site.com/kyc/retour",
                  "metadata": {
                    "user_id": "42"
                  },
                  "expires_at": "2026-10-24T18:00:00Z",
                  "verification_url": "https://nouko.openclouds.fr/verify/…",
                  "replayed": false,
                  "sandbox_scenario": null
                }
              }
            }
          },
          "200": {
            "description": "Rejeu idempotent (replayed: true)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide ou Idempotency-Key manquante",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "…"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "…"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Compte suspendu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "account_suspended",
                    "message": "…"
                  }
                }
              }
            }
          },
          "429": {
            "description": "120 requêtes par minute dépassées",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "…"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Erreur interne, réessayez avec la même Idempotency-Key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal_error",
                    "message": "…"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Lister les sessions",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page de sessions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionList"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{id}": {
      "get": {
        "summary": "Lire une session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "404": {
            "description": "Introuvable"
          }
        }
      }
    },
    "/v1/sessions/{id}/decision": {
      "get": {
        "summary": "Décision motivée et contrôles",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Décision motivée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            }
          },
          "404": {
            "description": "Session introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "…"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{id}/cancel": {
      "post": {
        "summary": "Annuler une session non soumise",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session annulée"
          },
          "409": {
            "description": "Session déjà soumise",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_cancellable",
                    "message": "…"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "sessionEvent": {
      "post": {
        "summary": "Événement de session signé (en-tête Nouko-Signature: t=…,v1=HMAC-SHA256(secret, t.corps))",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Répondre 2xx en moins de 8 s"
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Documentation développeurs",
    "url": "https://nouko.openclouds.fr/docs"
  }
}