{
  "openapi": "3.1.0",
  "info": {
    "title": "Justilio API",
    "version": "1.0.0-beta",
    "description": "Justilio pre-analyses the supporting document a customer provides for a bank transfer. It reads the document, cross-checks it against the transfer, official registers and the history you share, and returns a status, the findings and, when information is missing, a question for the customer. **Justilio never approves or rejects a transfer: your analyst decides.**\n\n## Flow\n\n1. `POST /v1/cases`: send the transfer, the customer and the document. The analysis runs asynchronously (`202 Accepted`).\n2. Webhook `case.analyzed`: Justilio notifies your server when the pre-analysis is ready or updated.\n3. `GET /v1/cases/{case_id}`: read the pre-analysis.\n4. `POST /v1/cases/{case_id}/decision`: record your analyst's decision. It feeds the history used for later cases.\n\n## Conventions\n\n- JSON in UTF-8, keys in English and snake_case.\n- Amounts are decimal strings (`\"48000.00\"`), never floats. Dates follow ISO 8601. IBANs have no spaces.\n- Statuses, effects and verdicts are stable English codes (`suspect`), each with a French label (`status_label`).\n- Human-readable texts (`message`, `client_question`) are in French.\n- No score or point is ever returned: findings explain the status.\n\n## Authentication\n\nSend your API key in the `Authorization` header: `Authorization: Bearer jst_<prefix>_<secret>`. Each institution has its own keys and only ever sees its own cases. Justilio stores a fingerprint of each key, never the key itself, and shows it once, when it is created. Call the API from your servers only.\n\n## Idempotency\n\nEvery write carries an `Idempotency-Key` header (1 to 255 characters). The same request sent again with the same key returns the original response and creates nothing; the same key on a different request is refused. `transfer_ref` adds a second guarantee: one case per transfer.\n\n## Errors\n\nErrors return a stable code and a message: `{\"error\": \"case_not_found\", \"message\": \"…\"}`. Rely on `error`, not on `message`. `429` and `503` responses carry a `Retry-After` header.\n\n## Limits\n\n- One document per request: up to 10 MB, in PDF, JPEG, PNG or HEIC. Your institution's settings may narrow this.\n- When too many documents are being received at once, the API answers `429` with `Retry-After`.\n- Up to 500 past transfers per case.\n\n## Environments\n\nA test environment, with your own keys and settings, is provided during the pilot.\n"
  },
  "paths": {
    "/v1/cases": {
      "post": {
        "summary": "Create a case",
        "description": "Creates the case of one transfer with its supporting document, and starts the analysis. The document travels in base 64 inside the JSON body. The response comes back once the document has been received, checked for file security and stored, encrypted; the `case.analyzed` webhook tells you when the pre-analysis is ready.\n\nA document that fails the file security checks is refused with `422 document_rejected`, and nothing is stored. A second case for the same `transfer_ref` returns `409 transfer_already_received` with the existing `case_id`.",
        "operationId": "createCase",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key of this write, 1 to 255 characters, for example a UUID. Sent again with the same request, it returns the original response; with another request, `422 idempotency_key_reused`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7b0e2c64-6f0a-4d6b-9a1e-3c5d8f2e4a10"
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional. Your request identifier (letters, digits, `.`, `_`, `-`, up to 100 characters), kept in the access log to trace a request.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseIn"
              },
              "example": {
                "transfer_ref": "trf_20261106_000481",
                "transfer": {
                  "amount": "48000.00",
                  "currency": "EUR",
                  "execution_date": "2026-11-06",
                  "direction": "outgoing",
                  "counterparty_name": "Velmora Conseil",
                  "counterparty_iban": "FR7699999000010000048006191",
                  "reference": "FACT VC-2026-014",
                  "declared_purpose": "Mission de conseil stratégique",
                  "vop_result": "match"
                },
                "client": {
                  "ref": "cli_8f3a2",
                  "kind": "company",
                  "name": "Nordis Trading",
                  "siren": "093847218"
                },
                "prior_transfers": [
                  {
                    "amount": "1250.00",
                    "currency": "EUR",
                    "execution_date": "2026-10-02",
                    "direction": "outgoing",
                    "counterparty_name": "Brévalis Logistique",
                    "counterparty_iban": "FR7699999000010000027154877",
                    "reference": "FACT 2026-0917"
                  }
                ],
                "document": {
                  "filename": "facture-VC-2026-014.pdf",
                  "content_base64": "JVBERi0xLjcK"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Received. The analysis runs asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                },
                "example": {
                  "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41",
                  "state": "received",
                  "revision": 1
                }
              }
            }
          },
          "400": {
            "description": "The `Idempotency-Key` header is missing or too long. Error codes: `idempotency_key_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "idempotency_key_required": {
                    "value": {
                      "error": "idempotency_key_required",
                      "message": "En-tête Idempotency-Key requis (1 à 255 caractères)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked. The response carries `WWW-Authenticate: Bearer`. Error codes: `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "unauthorized": {
                    "value": {
                      "error": "unauthorized",
                      "message": "Clé d'API absente, invalide ou révoquée."
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "Conflict with the state of the case or of an identical request. Error codes: `transfer_already_received`, `request_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "transfer_already_received": {
                    "value": {
                      "error": "transfer_already_received",
                      "message": "Ce virement a déjà son dossier.",
                      "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41"
                    }
                  },
                  "request_in_progress": {
                    "value": {
                      "error": "request_in_progress",
                      "message": "La même requête est encore en cours."
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large: a document of 10 MB at most. Error codes: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "Corps de requête trop gros : 10 Mo de pièce au plus."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request or the document cannot be accepted. Error codes: `invalid_request`, `invalid_document`, `document_rejected`, `idempotency_key_reused`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "Requête invalide : voir le détail.",
                      "details": [
                        {
                          "loc": [
                            "body",
                            "transfer",
                            "amount"
                          ],
                          "msg": "Input should be greater than 0",
                          "type": "greater_than"
                        }
                      ]
                    }
                  },
                  "invalid_document": {
                    "value": {
                      "error": "invalid_document",
                      "message": "La pièce n'est pas encodée en base 64."
                    }
                  },
                  "document_rejected": {
                    "value": {
                      "error": "document_rejected",
                      "message": "Format de fichier non accepté. (FILE_FORMAT)"
                    }
                  },
                  "idempotency_key_reused": {
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "Cette clé d'idempotence a servi à une autre requête."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many documents are being received at once. Retry after `Retry-After` seconds. Error codes: `too_many_receptions`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "too_many_receptions": {
                    "value": {
                      "error": "too_many_receptions",
                      "message": "Trop de pièces en cours de réception : réessayez dans quelques secondes."
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable. Retry after `Retry-After` seconds, or send the document again. Error codes: `reception_unavailable`, `deposit_expired`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "reception_unavailable": {
                    "value": {
                      "error": "reception_unavailable",
                      "message": "La réception est indisponible."
                    }
                  },
                  "deposit_expired": {
                    "value": {
                      "error": "deposit_expired",
                      "message": "Le dépôt a dépassé sa réservation et le nettoyage l'a repris : renvoyez la pièce."
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "tags": [
          "Cases"
        ]
      }
    },
    "/v1/cases/{case_id}/documents": {
      "post": {
        "summary": "Add a document",
        "description": "Adds a document to a case, for example the one your customer sent after answering the question, and starts a new analysis with all the documents of the case. `revision` increases; the `case.analyzed` webhook is sent again.",
        "operationId": "addDocument",
        "parameters": [
          {
            "name": "case_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Case Id"
            },
            "description": "Identifier of the case, as returned at creation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key of this write, 1 to 255 characters, for example a UUID. Sent again with the same request, it returns the original response; with another request, `422 idempotency_key_reused`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7b0e2c64-6f0a-4d6b-9a1e-3c5d8f2e4a10"
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional. Your request identifier (letters, digits, `.`, `_`, `-`, up to 100 characters), kept in the access log to trace a request.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentIn"
              },
              "example": {
                "filename": "kbis-velmora-conseil.pdf",
                "content_base64": "JVBERi0xLjcK"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Received. A new analysis runs asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                },
                "example": {
                  "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41",
                  "state": "received",
                  "revision": 2
                }
              }
            }
          },
          "400": {
            "description": "The `Idempotency-Key` header is missing or too long. Error codes: `idempotency_key_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "idempotency_key_required": {
                    "value": {
                      "error": "idempotency_key_required",
                      "message": "En-tête Idempotency-Key requis (1 à 255 caractères)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked. The response carries `WWW-Authenticate: Bearer`. Error codes: `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "unauthorized": {
                    "value": {
                      "error": "unauthorized",
                      "message": "Clé d'API absente, invalide ou révoquée."
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No case of your institution has this identifier. Error codes: `case_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_not_found": {
                    "value": {
                      "error": "case_not_found",
                      "message": "Aucun dossier de cette banque ne porte cet identifiant."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict with the state of the case or of an identical request. Error codes: `case_closed`, `request_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_closed": {
                    "value": {
                      "error": "case_closed",
                      "message": "Le dossier est en purge ou purgé."
                    }
                  },
                  "request_in_progress": {
                    "value": {
                      "error": "request_in_progress",
                      "message": "La même requête est encore en cours."
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large: a document of 10 MB at most. Error codes: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "Corps de requête trop gros : 10 Mo de pièce au plus."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request or the document cannot be accepted. Error codes: `invalid_request`, `invalid_document`, `document_rejected`, `idempotency_key_reused`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "Requête invalide : voir le détail.",
                      "details": [
                        {
                          "loc": [
                            "body",
                            "transfer",
                            "amount"
                          ],
                          "msg": "Input should be greater than 0",
                          "type": "greater_than"
                        }
                      ]
                    }
                  },
                  "invalid_document": {
                    "value": {
                      "error": "invalid_document",
                      "message": "La pièce n'est pas encodée en base 64."
                    }
                  },
                  "document_rejected": {
                    "value": {
                      "error": "document_rejected",
                      "message": "Format de fichier non accepté. (FILE_FORMAT)"
                    }
                  },
                  "idempotency_key_reused": {
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "Cette clé d'idempotence a servi à une autre requête."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many documents are being received at once. Retry after `Retry-After` seconds. Error codes: `too_many_receptions`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "too_many_receptions": {
                    "value": {
                      "error": "too_many_receptions",
                      "message": "Trop de pièces en cours de réception : réessayez dans quelques secondes."
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable. Retry after `Retry-After` seconds, or send the document again. Error codes: `reception_unavailable`, `deposit_expired`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "reception_unavailable": {
                    "value": {
                      "error": "reception_unavailable",
                      "message": "La réception est indisponible."
                    }
                  },
                  "deposit_expired": {
                    "value": {
                      "error": "deposit_expired",
                      "message": "Le dépôt a dépassé sa réservation et le nettoyage l'a repris : renvoyez la pièce."
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "tags": [
          "Cases"
        ]
      }
    },
    "/v1/cases/{case_id}": {
      "get": {
        "summary": "Read a case",
        "description": "Returns the case and its latest published pre-analysis: status, findings, values read from the document, forgery check and, for an incomplete case only, the question to ask the customer. `analysis` is `null` until the first analysis is published.",
        "operationId": "getCase",
        "parameters": [
          {
            "name": "case_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Case Id"
            },
            "description": "Identifier of the case, as returned at creation."
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional. Your request identifier (letters, digits, `.`, `_`, `-`, up to 100 characters), kept in the access log to trace a request.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The case and its latest pre-analysis.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseOut"
                },
                "example": {
                  "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41",
                  "transfer_ref": "trf_20261106_000481",
                  "state": "analyzed",
                  "revision": 1,
                  "created_at": "2026-11-06T09:14:02Z",
                  "analysis": {
                    "analysis_id": "b2d7e3f1-5a64-4e8b-8c19-0f3a9d2c7e58",
                    "status": "suspect",
                    "status_label": "suspect",
                    "forgery_check": "modified",
                    "forgery_label": "falsification détectée",
                    "findings": [
                      {
                        "code": "FORGERY_COVERED_TEXT",
                        "effect": "suspect",
                        "effect_label": "suspect",
                        "message": "Sous le total de 48 000,00 €, le PDF garde un ancien total de 4 800,00 €, masqué par un rectangle blanc.",
                        "document": "facture-VC-2026-014.pdf",
                        "field": "total_ttc",
                        "found": "4800.00",
                        "expected": null
                      },
                      {
                        "code": "COMMON_OFFICER",
                        "effect": "vigilance",
                        "effect_label": "vigilance",
                        "message": "Nordis Trading et Velmora Conseil ont un dirigeant en commun.",
                        "document": null,
                        "field": null,
                        "found": null,
                        "expected": null
                      },
                      {
                        "code": "COMPANY_YOUNG",
                        "effect": "vigilance",
                        "effect_label": "vigilance",
                        "message": "Velmora Conseil a été créée le 17/09/2026, sept semaines avant le virement.",
                        "document": "facture-VC-2026-014.pdf",
                        "field": "issuer_siren",
                        "found": "2026-09-17",
                        "expected": null
                      },
                      {
                        "code": "LARGE_AMOUNT_SMALL_SUPPLIER",
                        "effect": "vigilance",
                        "effect_label": "vigilance",
                        "message": "48 000 € pour une société sans salarié, créée il y a moins d'un an.",
                        "document": "facture-VC-2026-014.pdf",
                        "field": "total_ttc",
                        "found": "48000.00",
                        "expected": null
                      },
                      {
                        "code": "NEW_COUNTERPARTY",
                        "effect": "vigilance",
                        "effect_label": "vigilance",
                        "message": "Premier virement de ce client vers Velmora Conseil, d'après l'historique transmis.",
                        "document": null,
                        "field": null,
                        "found": null,
                        "expected": null
                      }
                    ],
                    "document_kind": "invoice",
                    "extracted": {
                      "issuer_name": "Velmora Conseil",
                      "issuer_siren": "048273619",
                      "issuer_vat_number": "FR63048273619",
                      "recipient_name": "Nordis Trading",
                      "invoice_number": "VC-2026-014",
                      "invoice_date": "2026-11-03",
                      "total_ht": "40000.00",
                      "total_tva": "8000.00",
                      "total_ttc": "48000.00",
                      "currency": "EUR",
                      "iban": "FR7699999000010000048006191"
                    },
                    "client_question": null,
                    "rules_version": "examplebank-2026-11-01",
                    "analyzed_at": "2026-11-06T09:14:21Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked. The response carries `WWW-Authenticate: Bearer`. Error codes: `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "unauthorized": {
                    "value": {
                      "error": "unauthorized",
                      "message": "Clé d'API absente, invalide ou révoquée."
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No case of your institution has this identifier. Error codes: `case_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_not_found": {
                    "value": {
                      "error": "case_not_found",
                      "message": "Aucun dossier de cette banque ne porte cet identifiant."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request or the document cannot be accepted. Error codes: `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "Requête invalide : voir le détail.",
                      "details": [
                        {
                          "loc": [
                            "body",
                            "transfer",
                            "amount"
                          ],
                          "msg": "Input should be greater than 0",
                          "type": "greater_than"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Cases"
        ]
      }
    },
    "/v1/cases/{case_id}/decision": {
      "post": {
        "summary": "Record the analyst's decision",
        "description": "Records your analyst's decision on the analysis they read: `validated`, `refused`, `document_requested` or `reopened`. Decisions feed the history Justilio uses for later cases, for example the payments already made on the same invoice.\n\nA decision on an analysis that is no longer the latest is refused with `409 stale_analysis`; while a new analysis runs, with `409 analysis_running`. Both return `published_analysis_id`.",
        "operationId": "recordDecision",
        "parameters": [
          {
            "name": "case_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Case Id"
            },
            "description": "Identifier of the case, as returned at creation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key of this write, 1 to 255 characters, for example a UUID. Sent again with the same request, it returns the original response; with another request, `422 idempotency_key_reused`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7b0e2c64-6f0a-4d6b-9a1e-3c5d8f2e4a10"
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional. Your request identifier (letters, digits, `.`, `_`, `-`, up to 100 characters), kept in the access log to trace a request.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionIn"
              },
              "example": {
                "analysis_id": "b2d7e3f1-5a64-4e8b-8c19-0f3a9d2c7e58",
                "decision": "refused",
                "analyst_ref": "analyst_042",
                "comment": "Facture retouchée entre sociétés liées : transmis à la conformité."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionOut"
                },
                "example": {
                  "decision_id": "e81a4c27-3f9b-4d06-b5e2-7c40a1d9f363"
                }
              }
            }
          },
          "400": {
            "description": "The `Idempotency-Key` header is missing or too long. Error codes: `idempotency_key_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "idempotency_key_required": {
                    "value": {
                      "error": "idempotency_key_required",
                      "message": "En-tête Idempotency-Key requis (1 à 255 caractères)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked. The response carries `WWW-Authenticate: Bearer`. Error codes: `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "unauthorized": {
                    "value": {
                      "error": "unauthorized",
                      "message": "Clé d'API absente, invalide ou révoquée."
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No case of your institution has this identifier. Error codes: `case_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_not_found": {
                    "value": {
                      "error": "case_not_found",
                      "message": "Aucun dossier de cette banque ne porte cet identifiant."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict with the state of the case or of an identical request. Error codes: `stale_analysis`, `analysis_running`, `case_closed`, `request_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "stale_analysis": {
                    "value": {
                      "error": "stale_analysis",
                      "message": "Cette analyse n'est plus la dernière publiée : relisez le dossier.",
                      "published_analysis_id": "c9e1f2a3-7d48-4b6c-9e05-1a2b3c4d5e6f"
                    }
                  },
                  "analysis_running": {
                    "value": {
                      "error": "analysis_running",
                      "message": "Une nouvelle analyse est en cours : décidez sur son résultat.",
                      "published_analysis_id": "b2d7e3f1-5a64-4e8b-8c19-0f3a9d2c7e58"
                    }
                  },
                  "case_closed": {
                    "value": {
                      "error": "case_closed",
                      "message": "Le dossier est en purge ou purgé."
                    }
                  },
                  "request_in_progress": {
                    "value": {
                      "error": "request_in_progress",
                      "message": "La même requête est encore en cours."
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large: a document of 10 MB at most. Error codes: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "Corps de requête trop gros : 10 Mo de pièce au plus."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request or the document cannot be accepted. Error codes: `invalid_request`, `idempotency_key_reused`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "Requête invalide : voir le détail.",
                      "details": [
                        {
                          "loc": [
                            "body",
                            "transfer",
                            "amount"
                          ],
                          "msg": "Input should be greater than 0",
                          "type": "greater_than"
                        }
                      ]
                    }
                  },
                  "idempotency_key_reused": {
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "Cette clé d'idempotence a servi à une autre requête."
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Decisions"
        ]
      }
    },
    "/v1/cases/{case_id}/reanalyze": {
      "post": {
        "summary": "Run the analysis again",
        "description": "Starts a new analysis of the case with its current documents, for example after a register was unavailable. Until the new pre-analysis is published, decisions are refused with `409 analysis_running`. The `case.analyzed` webhook is sent again.",
        "operationId": "reanalyzeCase",
        "parameters": [
          {
            "name": "case_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Case Id"
            },
            "description": "Identifier of the case, as returned at creation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key of this write, 1 to 255 characters, for example a UUID. Sent again with the same request, it returns the original response; with another request, `422 idempotency_key_reused`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7b0e2c64-6f0a-4d6b-9a1e-3c5d8f2e4a10"
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional. Your request identifier (letters, digits, `.`, `_`, `-`, up to 100 characters), kept in the access log to trace a request.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "A new analysis runs asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                },
                "example": {
                  "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41",
                  "state": "received",
                  "revision": 2
                }
              }
            }
          },
          "400": {
            "description": "The `Idempotency-Key` header is missing or too long. Error codes: `idempotency_key_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "idempotency_key_required": {
                    "value": {
                      "error": "idempotency_key_required",
                      "message": "En-tête Idempotency-Key requis (1 à 255 caractères)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked. The response carries `WWW-Authenticate: Bearer`. Error codes: `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "unauthorized": {
                    "value": {
                      "error": "unauthorized",
                      "message": "Clé d'API absente, invalide ou révoquée."
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No case of your institution has this identifier. Error codes: `case_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_not_found": {
                    "value": {
                      "error": "case_not_found",
                      "message": "Aucun dossier de cette banque ne porte cet identifiant."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict with the state of the case or of an identical request. Error codes: `case_closed`, `request_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "case_closed": {
                    "value": {
                      "error": "case_closed",
                      "message": "Le dossier est en purge ou purgé."
                    }
                  },
                  "request_in_progress": {
                    "value": {
                      "error": "request_in_progress",
                      "message": "La même requête est encore en cours."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request or the document cannot be accepted. Error codes: `invalid_request`, `idempotency_key_reused`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "Requête invalide : voir le détail.",
                      "details": [
                        {
                          "loc": [
                            "body",
                            "transfer",
                            "amount"
                          ],
                          "msg": "Input should be greater than 0",
                          "type": "greater_than"
                        }
                      ]
                    }
                  },
                  "idempotency_key_reused": {
                    "value": {
                      "error": "idempotency_key_reused",
                      "message": "Cette clé d'idempotence a servi à une autre requête."
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Cases"
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "Accepted": {
        "properties": {
          "case_id": {
            "type": "string",
            "format": "uuid",
            "title": "Case Id",
            "description": "Identifier of the case."
          },
          "state": {
            "type": "string",
            "title": "State",
            "description": "`received`: the analysis is waiting or running."
          },
          "revision": {
            "type": "integer",
            "title": "Revision",
            "description": "Revision of the case: it increases with each document and each new analysis."
          }
        },
        "type": "object",
        "required": [
          "case_id",
          "state",
          "revision"
        ],
        "title": "Accepted",
        "description": "The case has been received; its analysis runs asynchronously."
      },
      "AnalysisOut": {
        "properties": {
          "analysis_id": {
            "type": "string",
            "format": "uuid",
            "title": "Analysis Id",
            "description": "Identifier of this analysis, to quote in a decision."
          },
          "status": {
            "type": "string",
            "title": "Status",
            "description": "`consistent`, `incomplete`, `inconsistent` or `suspect`: the most serious effect among the findings."
          },
          "status_label": {
            "type": "string",
            "title": "Status Label",
            "description": "French label of the status."
          },
          "forgery_check": {
            "type": "string",
            "title": "Forgery Check",
            "description": "`normal`, `modified` or `conforming` (matches the issuer's originals)."
          },
          "forgery_label": {
            "type": "string",
            "title": "Forgery Label",
            "description": "French label of the forgery check."
          },
          "findings": {
            "items": {
              "$ref": "#/components/schemas/FindingOut"
            },
            "type": "array",
            "title": "Findings",
            "description": "What does not fit, what is missing, what is suspicious, and the points to watch."
          },
          "document_kind": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Document Kind",
            "description": "Type of document recognised (`invoice`, `payslip`, `lease`… 41 named types)."
          },
          "extracted": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Extracted",
            "description": "Values read from the document. Each one was found back in the document's text."
          },
          "client_question": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Client Question",
            "description": "Question to ask the customer, for an incomplete case only. Never for a suspicious case."
          },
          "rules_version": {
            "type": "string",
            "title": "Rules Version",
            "description": "Version of the rules and settings applied: `<institution>-<date>`."
          },
          "analyzed_at": {
            "type": "string",
            "format": "date-time",
            "title": "Analyzed At",
            "description": "Time the analysis was published."
          }
        },
        "type": "object",
        "required": [
          "analysis_id",
          "status",
          "status_label",
          "forgery_check",
          "forgery_label",
          "findings",
          "document_kind",
          "extracted",
          "client_question",
          "rules_version",
          "analyzed_at"
        ],
        "title": "AnalysisOut",
        "description": "A pre-analysis. No score and no point: the findings explain the status."
      },
      "CaseIn": {
        "properties": {
          "transfer_ref": {
            "type": "string",
            "maxLength": 255,
            "minLength": 1,
            "title": "Transfer Ref",
            "description": "Your stable identifier of the transfer. One case per transfer: a second case with the same value returns `409 transfer_already_received`."
          },
          "transfer": {
            "$ref": "#/components/schemas/Transfer",
            "description": "The transfer to justify."
          },
          "client": {
            "$ref": "#/components/schemas/Client",
            "description": "Your customer, who sent or received the transfer."
          },
          "prior_transfers": {
            "items": {
              "$ref": "#/components/schemas/Transfer"
            },
            "type": "array",
            "maxItems": 500,
            "title": "Prior Transfers",
            "description": "Optional. Your customer's past transfers, in the same format, up to 500. They reveal a supplier paid on a new account, a first payment to a counterparty, or an invoice already paid."
          },
          "document": {
            "$ref": "#/components/schemas/DocumentIn",
            "description": "The supporting document."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "transfer_ref",
          "transfer",
          "client",
          "document"
        ],
        "title": "CaseIn",
        "description": "A transfer to justify, your customer, their past transfers and the supporting document."
      },
      "CaseOut": {
        "properties": {
          "case_id": {
            "type": "string",
            "format": "uuid",
            "title": "Case Id",
            "description": "Identifier of the case."
          },
          "transfer_ref": {
            "type": "string",
            "title": "Transfer Ref",
            "description": "Your identifier of the transfer."
          },
          "state": {
            "type": "string",
            "title": "State",
            "description": "`received` (analysis waiting or running), `analyzed` or `failed`."
          },
          "revision": {
            "type": "integer",
            "title": "Revision",
            "description": "Revision of the case."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Creation time of the case."
          },
          "analysis": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AnalysisOut"
              },
              {
                "type": "null"
              }
            ],
            "description": "The latest published pre-analysis, or `null` before the first one."
          }
        },
        "type": "object",
        "required": [
          "case_id",
          "transfer_ref",
          "state",
          "revision",
          "created_at",
          "analysis"
        ],
        "title": "CaseOut",
        "description": "A case and its latest published pre-analysis."
      },
      "Client": {
        "properties": {
          "ref": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Ref",
            "description": "Your internal, opaque identifier of the customer. Never an email address or a phone number."
          },
          "kind": {
            "$ref": "#/components/schemas/ClientType",
            "description": "`individual` or `company`."
          },
          "name": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Name",
            "description": "Name of the customer. No other personal detail is needed."
          },
          "siren": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Siren",
            "description": "SIREN number, required for a French company."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "ref",
          "kind",
          "name"
        ],
        "title": "Client",
        "description": "Your customer. No contact detail beyond the name."
      },
      "ClientType": {
        "type": "string",
        "enum": [
          "individual",
          "company"
        ],
        "title": "ClientType",
        "description": "`individual` or `company`."
      },
      "DecisionIn": {
        "properties": {
          "analysis_id": {
            "type": "string",
            "format": "uuid",
            "title": "Analysis Id",
            "description": "The analysis your analyst read (`analysis.analysis_id`)."
          },
          "decision": {
            "type": "string",
            "enum": [
              "validated",
              "refused",
              "document_requested",
              "reopened"
            ],
            "title": "Decision",
            "description": "`validated`, `refused`, `document_requested` (a document was asked for) or `reopened`."
          },
          "analyst_ref": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Analyst Ref",
            "description": "Your identifier of the analyst, kept in the case's trail."
          },
          "comment": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Comment",
            "description": "Optional comment, up to 2,000 characters."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "analysis_id",
          "decision",
          "analyst_ref"
        ],
        "title": "DecisionIn",
        "description": "Your analyst's decision on an analysis."
      },
      "DecisionOut": {
        "properties": {
          "decision_id": {
            "type": "string",
            "format": "uuid",
            "title": "Decision Id",
            "description": "Identifier of the decision."
          }
        },
        "type": "object",
        "required": [
          "decision_id"
        ],
        "title": "DecisionOut",
        "description": "A recorded decision."
      },
      "DocumentIn": {
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "minLength": 1,
            "pattern": "^[^/\\\\\\x00-\\x1f]+$",
            "title": "Filename",
            "description": "File name, kept for the analyst."
          },
          "content_base64": {
            "type": "string",
            "minLength": 4,
            "title": "Content Base64",
            "description": "The whole file in base 64 (truncated in the examples). PDF, JPEG, PNG or HEIC, 10 MB at most."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "filename",
          "content_base64"
        ],
        "title": "DocumentIn",
        "description": "A supporting document, in base 64."
      },
      "FindingOut": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Stable code of the finding, in capitals (`IBAN_MISMATCH`)."
          },
          "effect": {
            "type": "string",
            "title": "Effect",
            "description": "`suspect`, `inconsistent`, `incomplete`, `vigilance` (a point to watch, which alone changes nothing) or `positive`."
          },
          "effect_label": {
            "type": "string",
            "title": "Effect Label",
            "description": "French label of the effect."
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Explanation for the analyst, in French."
          },
          "document": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Document",
            "description": "File name of the document concerned, if any."
          },
          "field": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Field",
            "description": "Field concerned, if any."
          },
          "found": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Found",
            "description": "Value read from the document, if any."
          },
          "expected": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expected",
            "description": "Value expected from the transfer, if any."
          }
        },
        "type": "object",
        "required": [
          "code",
          "effect",
          "effect_label",
          "message",
          "document",
          "field",
          "found",
          "expected"
        ],
        "title": "FindingOut",
        "description": "One finding of the analysis."
      },
      "Problem": {
        "properties": {
          "error": {
            "type": "string",
            "title": "Error",
            "description": "Stable error code: rely on it."
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Explanation, in French."
          }
        },
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "title": "Problem",
        "description": "An error: a stable code and a message. Some errors add a field: `case_id` (`transfer_already_received`), `published_analysis_id` (`stale_analysis`, `analysis_running`), `details` (`invalid_request`)."
      },
      "Transfer": {
        "properties": {
          "amount": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Amount",
            "gt": 0,
            "description": "Decimal string, greater than 0, in `currency`: `\"48000.00\"`."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$",
            "title": "Currency",
            "description": "ISO 4217 code. The document may be in another currency: Justilio converts at the ECB reference rate of the execution date."
          },
          "execution_date": {
            "type": "string",
            "format": "date",
            "title": "Execution Date",
            "description": "Execution date of the transfer."
          },
          "direction": {
            "$ref": "#/components/schemas/TransferDirection",
            "default": "outgoing",
            "description": "`outgoing` (your customer pays, the default) or `incoming` (your customer is paid)."
          },
          "counterparty_name": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Counterparty Name",
            "description": "The other party: the payee of an outgoing transfer, the payer of an incoming one."
          },
          "counterparty_iban": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Counterparty Iban",
            "description": "The counterparty's IBAN, without spaces. Outside the IBAN area, send `counterparty_account` and `counterparty_bic` instead."
          },
          "counterparty_account": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Counterparty Account",
            "description": "Account number of a counterparty without IBAN (United States, China, India…)."
          },
          "counterparty_bic": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Counterparty Bic",
            "description": "BIC of the counterparty's bank, with `counterparty_account`."
          },
          "reference": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reference",
            "description": "Payment details or reference of the transfer, as your customer entered them."
          },
          "declared_purpose": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Declared Purpose",
            "description": "Purpose of the transfer declared by your customer, if any."
          },
          "vop_result": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/VopResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "Result of the Verification of Payee you ran before an outgoing transfer, if any."
          },
          "vop_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Vop Name",
            "description": "Account holder's name returned by the Verification of Payee on a `close_match`."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "amount",
          "currency",
          "execution_date",
          "counterparty_name"
        ],
        "title": "Transfer",
        "description": "A transfer sent or received by your customer. Give `counterparty_iban`, or `counterparty_account` and `counterparty_bic`. `vop_result` only concerns an outgoing transfer."
      },
      "TransferDirection": {
        "type": "string",
        "enum": [
          "outgoing",
          "incoming"
        ],
        "title": "TransferDirection",
        "description": "Direction of the transfer, from your customer's point of view."
      },
      "VopResult": {
        "type": "string",
        "enum": [
          "match",
          "close_match",
          "no_match",
          "unavailable"
        ],
        "title": "VopResult",
        "description": "Result of the Verification of Payee run by your institution before the transfer."
      },
      "CaseAnalyzedEvent": {
        "type": "object",
        "title": "CaseAnalyzedEvent",
        "description": "Sent when a pre-analysis is published: after a case is created, a document added or a new analysis run.",
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Event identifier, also in `Justilio-Event-Id`. The same event may arrive more than once: deduplicate on it."
          },
          "type": {
            "type": "string",
            "const": "case.analyzed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/CaseAnalyzedData"
          }
        }
      },
      "CaseAnalyzedData": {
        "type": "object",
        "title": "CaseAnalyzedData",
        "required": [
          "case_id",
          "run_id",
          "status",
          "rules_version"
        ],
        "properties": {
          "case_id": {
            "type": "string",
            "format": "uuid"
          },
          "run_id": {
            "type": "string",
            "format": "uuid",
            "description": "The analysis, `analysis_id` in the case."
          },
          "status": {
            "type": "string",
            "enum": [
              "consistent",
              "incomplete",
              "inconsistent",
              "suspect"
            ]
          },
          "rules_version": {
            "type": "string"
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "jst_<prefix>_<secret>",
        "description": "Your institution's API key."
      }
    }
  },
  "tags": [
    {
      "name": "Cases",
      "description": "Send a transfer and its document, then read the pre-analysis."
    },
    {
      "name": "Decisions",
      "description": "Record what your analyst decided. It feeds the history."
    },
    {
      "name": "Webhooks",
      "description": "Events Justilio sends to your server, signed."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "webhooks": {
    "case.analyzed": {
      "post": {
        "operationId": "caseAnalyzed",
        "tags": [
          "Webhooks"
        ],
        "summary": "case.analyzed",
        "description": "Justilio sends this event to the HTTPS address your institution registered, as soon as a pre-analysis is published. Read the case with `GET /v1/cases/{case_id}`.\n\n**Verify the signature** before trusting the event: compute the HMAC-SHA256 of `<t>.<raw body>` with your webhook secret, compare it in constant time with `v1`, and refuse a timestamp more than 5 minutes away from your clock.\n\n**Answer with a 2xx status quickly.** Any other answer, a timeout or a redirect (never followed) triggers a retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, within 24 hours. After that, the case stays readable with `GET /v1/cases/{case_id}`.",
        "security": [],
        "parameters": [
          {
            "name": "Justilio-Event-Id",
            "in": "header",
            "required": true,
            "description": "Event identifier, as in the body's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6a0d9e44-2c71-4b5f-a8d3-91e7f0b25c6a"
          },
          {
            "name": "Justilio-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<Unix timestamp>, v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.",
            "schema": {
              "type": "string"
            },
            "example": "t=1794042861, v1=5f1c2d6e9a0b47c8e3d2f1a09b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseAnalyzedEvent"
              },
              "example": {
                "id": "6a0d9e44-2c71-4b5f-a8d3-91e7f0b25c6a",
                "type": "case.analyzed",
                "created_at": "2026-11-06T09:14:21Z",
                "data": {
                  "case_id": "4f6c2a0e-8b1d-4c3e-9a57-2d8e1b6f0c41",
                  "run_id": "b2d7e3f1-5a64-4e8b-8c19-0f3a9d2c7e58",
                  "status": "suspect",
                  "rules_version": "examplebank-2026-11-01"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Answer quickly, then read the case."
          },
          "default": {
            "description": "Any other answer triggers a retry."
          }
        }
      }
    }
  }
}
