Last update: October 15, 2026
Doc Trust API¶
Local port (Docker Compose): 30008 | Repo: solyntek/djinn/services/djinn-doc-trust
Authenticity opinion on a supporting document (bank details slip, receipt, attestation): the answer is consistent, suspicious or inconsistent, with the signals behind it and an explanation.
An evaluation, never an authorisation
The opinion is an indication that calls for a human check. It never authorises or refuses anything by itself: "suspicious" and "inconsistent" mean "to check", not "fake". Files are analysed in memory and never stored.
Endpoints¶
| Method | Route | Description |
|---|---|---|
GET |
/health |
Health check (rules version, trust-list snapshot sequence) |
POST |
/v1/authenticity/analyze |
Analyse one document |
Analyse a document¶
POST /v1/authenticity/analyze, multipart/form-data, X-API-Key required.
| Field | Type | Rule |
|---|---|---|
file |
file | PDF, PNG, JPEG, TIFF or WEBP, up to 50 MiB; the real type is read from the file signature, not the extension |
document_type |
rib, receipt, attestation, resume, id_card, other |
Required; selects the rule profile |
fields |
JSON string (object) | Optional, up to 16 KiB: fields already extracted from the document (for example by Doc Extract), used to cross-check the content. Values are never logged |
curl -X POST https://api.dev.djinn.solyntek.io/v1/authenticity/analyze \
-H "X-API-Key: your-api-key" \
-F "file=@rib.pdf" \
-F "document_type=rib"
{
"verdict": "suspicious",
"score": 0.35,
"signals": [
{
"code": "pdf_online_editor",
"family": "metadata",
"strength": "strong",
"message": "Le document a été produit par un éditeur en ligne (Canva).",
"detail": {"producer": "Canva"}
}
],
"explanation": "…",
"rules_version": "2026.10.1",
"document_sha256": "…",
"document_type": "rib",
"analysed_at": "2026-10-05T09:12:44Z"
}
Verdict¶
| Verdict | Meaning |
|---|---|
consistent |
No signal calls for a check |
suspicious |
At least one strong signal, or at least two weak ones: to check |
inconsistent |
At least two strong signals from different families: to check first |
score goes from 0 to 1. It is a triage indicator (the higher, the more to check), not a probability. A signal has a family (metadata, structure, fonts, image, content, 2ddoc) and a strength (strong, weak, info); two signals from different families are independent. Signal messages are in French and never contain business data (IBAN, amount, name).
Errors¶
Business errors use {"detail": {"code": "...", "message": "..."}}.
| Status | Cause |
|---|---|
401 |
Missing or invalid API key |
413 |
File larger than 50 MiB (file_too_large) |
422 |
empty_file, unsupported_type, unreadable_file (encrypted, corrupted or truncated), invalid_fields, or invalid document_type (request validation) |
500 |
The audit event could not be written: no trace, no action |
A service that calls Doc Trust must treat any non-200 answer as "not evaluated", never as "no signal".
Interactive reference¶
Generated on every build from the versioned spec openapi/doc-trust-v1.json (ADR-011) — no route is documented by hand.
Djinn Doc Trust 0.1.0¶
Avis d'authenticité documentaire par règles (évaluation, jamais autorisation)
Endpoints¶
GET /health¶
Health
Description
Health check, sans authentification.
Responses
{
"status": "string",
"service": "string",
"version": "string",
"rules_version": "string",
"tsl_sequence": 0,
"tsl_issued": "string"
}
Schema of the response body
{
"properties": {
"status": {
"type": "string",
"title": "Status"
},
"service": {
"type": "string",
"title": "Service"
},
"version": {
"type": "string",
"title": "Version"
},
"rules_version": {
"type": "string",
"title": "Rules Version"
},
"tsl_sequence": {
"type": "integer",
"title": "Tsl Sequence"
},
"tsl_issued": {
"type": "string",
"title": "Tsl Issued",
"description": "Date d'émission de la TSL embarquée (AAAA-MM-JJ)."
}
},
"type": "object",
"required": [
"status",
"service",
"version",
"rules_version",
"tsl_sequence",
"tsl_issued"
],
"title": "HealthResponse",
"description": "Response for the health check endpoint."
}
POST /v1/authenticity/analyze¶
Analyze
Description
Avis d'authenticité d'un justificatif (PDF ou image) : évaluation, jamais autorisation.
Le type réel du fichier est lu dans ses octets, pas dans son nom. fields est un objet JSON
facultatif (16 Kio max) des champs déjà extraits ; ses valeurs ne sont jamais journalisées.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
X-API-Key |
header | No |
Request body
Schema of the request body
{
"properties": {
"file": {
"type": "string",
"contentMediaType": "application/octet-stream",
"title": "File"
},
"document_type": {
"type": "string",
"enum": [
"rib",
"receipt",
"attestation",
"resume",
"id_card",
"other"
],
"title": "Document Type"
},
"fields": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Fields"
}
},
"type": "object",
"required": [
"file",
"document_type"
],
"title": "Body_analyze_v1_authenticity_analyze_post"
}
Responses
{
"verdict": "consistent",
"score": 10.12,
"signals": [
{
"code": "string",
"family": "metadata",
"strength": "info",
"message": "string",
"detail": {}
}
],
"explanation": "string",
"rules_version": "string",
"document_sha256": "string",
"document_type": "rib",
"analysed_at": "2022-04-13T15:42:05.901Z"
}
Schema of the response body
{
"properties": {
"verdict": {
"type": "string",
"enum": [
"consistent",
"suspicious",
"inconsistent"
],
"title": "Verdict",
"description": "`consistent` (aucun signal notable), `suspicious` (à vérifier), `inconsistent` (au moins deux signaux forts de familles différentes)."
},
"score": {
"type": "number",
"maximum": 1.0,
"minimum": 0.0,
"title": "Score",
"description": "Indicateur de tri, pas une probabilité : min(1, 0,35 x signaux forts + 0,1 x signaux faibles). Plus il est haut, plus il y a à vérifier."
},
"signals": {
"items": {
"$ref": "#/components/schemas/SignalOut"
},
"type": "array",
"title": "Signals"
},
"explanation": {
"type": "string",
"title": "Explanation"
},
"rules_version": {
"type": "string",
"title": "Rules Version"
},
"document_sha256": {
"type": "string",
"title": "Document Sha256"
},
"document_type": {
"type": "string",
"enum": [
"rib",
"receipt",
"attestation",
"resume",
"id_card",
"other"
],
"title": "Document Type"
},
"analysed_at": {
"type": "string",
"format": "date-time",
"title": "Analysed At"
}
},
"type": "object",
"required": [
"verdict",
"score",
"signals",
"explanation",
"rules_version",
"document_sha256",
"document_type",
"analysed_at"
],
"title": "AnalyzeResponse",
"description": "Avis d'authenticité : une évaluation, jamais une autorisation."
}
Schemas¶
AnalyzeResponse¶
| Name | Type | Description |
|---|---|---|
analysed_at |
string(date-time) | |
document_sha256 |
string | |
document_type |
string | |
explanation |
string | |
rules_version |
string | |
score |
number | Indicateur de tri, pas une probabilité : min(1, 0,35 x signaux forts + 0,1 x signaux faibles). Plus il est haut, plus il y a à vérifier. |
signals |
Array<SignalOut> | |
verdict |
string | `consistent` (aucun signal notable), `suspicious` (à vérifier), `inconsistent` (au moins deux signaux forts de familles différentes). |
Body_analyze_v1_authenticity_analyze_post¶
| Name | Type | Description |
|---|---|---|
document_type |
string | |
fields |
||
file |
string |
ErrorBody¶
| Name | Type | Description |
|---|---|---|
code |
string | `empty_file`, `file_too_large`, `unsupported_type`, `unreadable_file` ou `invalid_fields`. |
message |
string |
ErrorDetail¶
| Name | Type | Description |
|---|---|---|
detail |
ErrorBody |
HealthResponse¶
| Name | Type | Description |
|---|---|---|
rules_version |
string | |
service |
string | |
status |
string | |
tsl_issued |
string | Date d'émission de la TSL embarquée (AAAA-MM-JJ). |
tsl_sequence |
integer | |
version |
string |
SignalOut¶
| Name | Type | Description |
|---|---|---|
code |
string | |
detail |
Métadonnées techniques seulement (producteur, nombre de révisions...). Jamais une valeur métier (IBAN, montant, nom). | |
family |
string | |
message |
string | Message en français, sans donnée métier. |
strength |
string |