Aller au contenu

Dernière mise à jour : 15 octobre 2026

Audit Trail API

Port local (Docker Compose) : 30006 | Repo : solyntek/djinn/services/djinn-audit-trail

Journal d'audit structuré, immutable, multi-tenant avec conformité RGPD et preuve d'intégrité cryptographique.

Endpoints

Méthode Route Description
GET /health Health check
POST /v1/events Enregistrer un événement
POST /v1/events/batch Enregistrer N événements
GET /v1/events Requêter, paginé (filtres : acteur, action, type et id de ressource, sévérité)
GET /v1/events/{id} Détail d'un événement
POST /v1/gdpr/export Export données personnelles (RGPD)
POST /v1/gdpr/anonymize Anonymiser un sujet
GET /v1/stats Statistiques d'activité
GET /v1/proof/verify Vérifier la chaîne d'intégrité du tenant
GET /v1/events/{id}/proof Preuve d'un événement (haché stocké vs haché recalculé)
POST /v1/exports/compliance Dossier de preuve (Markdown) sur une période

Toutes les routes sauf /health exigent X-API-Key. tenant_id est obligatoire (corps ou query string) et doit être le tenant de votre clé, sinon la réponse est 403 (/v1/events/{id}/proof prend le tenant depuis la clé). Les événements écrits par les autres services Djinn arrivent dans le même journal.

Modèle AuditEvent

{
  "tenant_id": "fenyks",
  "actor_id": "user-123",
  "actor_type": "user",
  "action": "update",
  "resource_type": "contract",
  "resource_id": "ctr-456",
  "changes": {"status": {"old": "draft", "new": "active"}},
  "severity": "info"
}

actor_type vaut user par défaut, severity vaut info, warning ou critical (défaut info), metadata est un objet libre. L'événement stocké ajoute id, timestamp, anonymized et les champs de chaîne chain_seq, prev_hash, event_hash.

Immutable

Les événements ne peuvent pas être modifiés ni supprimés (append-only) ; seule modification possible : l'anonymisation RGPD des identifiants (/v1/gdpr/anonymize).

Pagination

GET /v1/events renvoie un tableau JSON, du plus récent au plus ancien (created_at décroissant). Paginez avec limit (1 à 1000, défaut 50) et offset (défaut 0) : demandez la page suivante avec offset += limit jusqu'à recevoir moins de limit événements. Filtres : actor_id, action, resource_type, resource_id, severity. GET /v1/events/{id} exige aussi tenant_id ; un événement inconnu répond 404.

curl "https://api.dev.djinn.solyntek.io/v1/events?tenant_id=fenyks&action=update&limit=100&offset=0" \
  -H "X-API-Key: your-api-key"

Preuve d'intégrité

Chaque événement est chaîné au précédent de son tenant par un haché (chain_seq, prev_hash, event_hash). Toute modification ou suppression dans l'historique casse la chaîne.

  • GET /v1/proof/verify?tenant_id=... vérifie toute la chaîne, du premier événement à la tête, et renvoie ok, checked, scope (full), head_seq, head_hash, unchained_count (événements plus anciens, écrits avant la chaîne et hors d'elle) et, en cas d'échec, first_broken_seq, first_broken_id et reason. Avec from_seq / to_seq, elle vérifie une fenêtre d'au plus 10 000 événements (scope: "window", prefix_verified: false) : seule une vérification complète prouve la chaîne depuis l'origine. Une fenêtre inversée ou trop grande répond 422.
  • GET /v1/events/{id}/proof donne event_hash à côté de recomputed_hash et ok pour cet événement seul ; elle ne prouve pas le lien avec l'événement précédent. Un événement inconnu, ou d'un autre tenant, répond 404.
  • POST /v1/exports/compliance (tenant_id, period_start, period_end avec fuseau horaire explicite, 366 jours au plus) renvoie un dossier de preuve en Markdown : event_count, by_action, by_resource_type, le verdict de la chaîne sur toute la chaîne, et la liste des 1000 événements les plus récents de la période (truncated indique si la liste est tronquée). C'est un constat d'intégrité, pas une attestation de conformité juridique.

La vérification lit toute la chaîne du tenant : elle est faite pour les audits, pas pour chaque requête.

Erreurs

Les erreurs suivent le format FastAPI, {"detail": "..."} :

Statut Cause
401 Clé API absente ou invalide
403 tenant_id différent du tenant authentifié
404 Événement introuvable (ou d'un autre tenant)
409 Conflit d'intégrité de chaîne à l'écriture (/v1/events, /v1/events/batch) : l'événement ne peut pas prolonger la chaîne du tenant ; ne pas rejouer tel quel
422 Requête invalide (période de plus de 366 jours, fenêtre invalide sur /v1/proof/verify, ...)

Référence interactive

Générée à chaque build depuis la spec versionnée openapi/audit-trail-v1.json (ADR-011) — aucune route documentée à la main.

Djinn Audit Trail 0.1.0

Journal d'audit immutable et conformité RGPD


Endpoints


GET /health

Health

Description

Health check endpoint.

The relay block is the signal that was missing since T1a: enabled reports whether a relay is configured at all ({"enabled": false} is a mode — tests, local dev — not a failure); when enabled, running and stats report the background task's own view of itself, and pending is the outbox depth (audit_outbox rows still delivered_at IS NULL) — a relay that died with a depth that keeps growing is visible here, which is the entire point of this chantier.

The anchor block (bucket: the name only; last_anchored_at: newest anchor of any tenant, read from audit_chain_anchors) makes a missing cron visible: an anchoring nobody schedules fails silently.

Responses

{
    "status": "string",
    "service": "string",
    "version": "string",
    "relay": null,
    "anchor": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "status": {
            "type": "string",
            "title": "Status",
            "default": "ok"
        },
        "service": {
            "type": "string",
            "title": "Service",
            "default": "audit-trail"
        },
        "version": {
            "type": "string",
            "title": "Version",
            "default": "0.1.0"
        },
        "relay": {
            "anyOf": [
                {
                    "additionalProperties": true,
                    "type": "object"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Relay"
        },
        "anchor": {
            "anyOf": [
                {
                    "additionalProperties": true,
                    "type": "object"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Anchor"
        }
    },
    "type": "object",
    "title": "HealthResponse",
    "description": "Response for health check."
}

POST /v1/events

Create Event

Description

Record a single audit event.

Input parameters

Parameter In Type Default Nullable Description
X-API-Key header No

Request body

{
    "tenant_id": "string",
    "actor_id": "string",
    "actor_type": "string",
    "action": "string",
    "resource_type": "string",
    "resource_id": "string",
    "changes": null,
    "metadata": {},
    "severity": "info"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "actor_id": {
            "type": "string",
            "title": "Actor Id",
            "description": "ID de l'auteur de l'action"
        },
        "actor_type": {
            "type": "string",
            "title": "Actor Type",
            "description": "user | system | api_key",
            "default": "user"
        },
        "action": {
            "type": "string",
            "title": "Action",
            "description": "create | read | update | delete | export | login | ..."
        },
        "resource_type": {
            "type": "string",
            "title": "Resource Type",
            "description": "invoice | contract | user | ..."
        },
        "resource_id": {
            "type": "string",
            "title": "Resource Id",
            "description": "ID de la ressource concernée"
        },
        "changes": {
            "anyOf": [
                {
                    "additionalProperties": true,
                    "type": "object"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Changes",
            "description": "Changements : {field: {old: x, new: y}}"
        },
        "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata",
            "description": "IP, user-agent, request_id, etc."
        },
        "severity": {
            "$ref": "#/components/schemas/AuditSeverity",
            "default": "info"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "actor_id",
        "action",
        "resource_type",
        "resource_id"
    ],
    "title": "AuditEventCreate",
    "description": "Requête de création d'un événement d'audit."
}

Responses

{
    "tenant_id": "string",
    "actor_id": "string",
    "actor_type": "string",
    "action": "string",
    "resource_type": "string",
    "resource_id": "string",
    "changes": null,
    "metadata": {},
    "severity": "info",
    "id": "string",
    "timestamp": "2022-04-13T15:42:05.901Z",
    "anonymized": true,
    "chain_seq": null,
    "prev_hash": null,
    "event_hash": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "actor_id": {
            "type": "string",
            "title": "Actor Id",
            "description": "ID de l'auteur de l'action"
        },
        "actor_type": {
            "type": "string",
            "title": "Actor Type",
            "description": "user | system | api_key",
            "default": "user"
        },
        "action": {
            "type": "string",
            "title": "Action",
            "description": "create | read | update | delete | export | login | ..."
        },
        "resource_type": {
            "type": "string",
            "title": "Resource Type",
            "description": "invoice | contract | user | ..."
        },
        "resource_id": {
            "type": "string",
            "title": "Resource Id",
            "description": "ID de la ressource concernée"
        },
        "changes": {
            "anyOf": [
                {
                    "additionalProperties": true,
                    "type": "object"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Changes",
            "description": "Changements : {field: {old: x, new: y}}"
        },
        "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata",
            "description": "IP, user-agent, request_id, etc."
        },
        "severity": {
            "$ref": "#/components/schemas/AuditSeverity",
            "default": "info"
        },
        "id": {
            "type": "string",
            "title": "Id"
        },
        "timestamp": {
            "type": "string",
            "format": "date-time",
            "title": "Timestamp"
        },
        "anonymized": {
            "type": "boolean",
            "title": "Anonymized",
            "default": false
        },
        "chain_seq": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Chain Seq",
            "description": "Position dans la chaîne d'intégrité du tenant"
        },
        "prev_hash": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Prev Hash",
            "description": "Haché de l'événement précédent"
        },
        "event_hash": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Event Hash",
            "description": "Haché de cet événement"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "actor_id",
        "action",
        "resource_type",
        "resource_id",
        "id",
        "timestamp"
    ],
    "title": "AuditEvent",
    "description": "Événement d'audit complet avec ID et timestamp."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

GET /v1/events

Query Events

Description

Query audit events with filters.

Input parameters

Parameter In Type Default Nullable Description
action query No
actor_id query No
limit query integer 50 No
offset query integer 0 No
resource_id query No
resource_type query No
severity query No
tenant_id query string No
X-API-Key header No

Responses

[
    {
        "tenant_id": "string",
        "actor_id": "string",
        "actor_type": "string",
        "action": "string",
        "resource_type": "string",
        "resource_id": "string",
        "changes": null,
        "metadata": {},
        "severity": "info",
        "id": "string",
        "timestamp": "2022-04-13T15:42:05.901Z",
        "anonymized": true,
        "chain_seq": null,
        "prev_hash": null,
        "event_hash": null
    }
]
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "array",
    "items": {
        "$ref": "#/components/schemas/AuditEvent"
    },
    "title": "Response Query Events V1 Events Get"
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

POST /v1/events/batch

Create Events Batch

Description

Record multiple audit events.

Input parameters

Parameter In Type Default Nullable Description
X-API-Key header No

Request body

{
    "events": [
        {
            "tenant_id": "string",
            "actor_id": "string",
            "actor_type": "string",
            "action": "string",
            "resource_type": "string",
            "resource_id": "string",
            "changes": null,
            "metadata": {},
            "severity": "info"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "events": {
            "items": {
                "$ref": "#/components/schemas/AuditEventCreate"
            },
            "type": "array",
            "minItems": 1,
            "title": "Events"
        }
    },
    "type": "object",
    "required": [
        "events"
    ],
    "title": "BatchCreateRequest",
    "description": "Requête de création d'événements en batch."
}

Responses

{
    "created": 0,
    "events": [
        {
            "tenant_id": "string",
            "actor_id": "string",
            "actor_type": "string",
            "action": "string",
            "resource_type": "string",
            "resource_id": "string",
            "changes": null,
            "metadata": {},
            "severity": "info",
            "id": "string",
            "timestamp": "2022-04-13T15:42:05.901Z",
            "anonymized": true,
            "chain_seq": null,
            "prev_hash": null,
            "event_hash": null
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "created": {
            "type": "integer",
            "title": "Created"
        },
        "events": {
            "items": {
                "$ref": "#/components/schemas/AuditEvent"
            },
            "type": "array",
            "title": "Events"
        }
    },
    "type": "object",
    "required": [
        "created",
        "events"
    ],
    "title": "BatchCreateResponse",
    "description": "Réponse de création en batch."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

GET /v1/events/{event_id}

Get Event

Description

Get a single audit event by ID, scoped to a tenant.

Input parameters

Parameter In Type Default Nullable Description
event_id path string No
tenant_id query string No
X-API-Key header No

Responses

{
    "tenant_id": "string",
    "actor_id": "string",
    "actor_type": "string",
    "action": "string",
    "resource_type": "string",
    "resource_id": "string",
    "changes": null,
    "metadata": {},
    "severity": "info",
    "id": "string",
    "timestamp": "2022-04-13T15:42:05.901Z",
    "anonymized": true,
    "chain_seq": null,
    "prev_hash": null,
    "event_hash": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "actor_id": {
            "type": "string",
            "title": "Actor Id",
            "description": "ID de l'auteur de l'action"
        },
        "actor_type": {
            "type": "string",
            "title": "Actor Type",
            "description": "user | system | api_key",
            "default": "user"
        },
        "action": {
            "type": "string",
            "title": "Action",
            "description": "create | read | update | delete | export | login | ..."
        },
        "resource_type": {
            "type": "string",
            "title": "Resource Type",
            "description": "invoice | contract | user | ..."
        },
        "resource_id": {
            "type": "string",
            "title": "Resource Id",
            "description": "ID de la ressource concernée"
        },
        "changes": {
            "anyOf": [
                {
                    "additionalProperties": true,
                    "type": "object"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Changes",
            "description": "Changements : {field: {old: x, new: y}}"
        },
        "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata",
            "description": "IP, user-agent, request_id, etc."
        },
        "severity": {
            "$ref": "#/components/schemas/AuditSeverity",
            "default": "info"
        },
        "id": {
            "type": "string",
            "title": "Id"
        },
        "timestamp": {
            "type": "string",
            "format": "date-time",
            "title": "Timestamp"
        },
        "anonymized": {
            "type": "boolean",
            "title": "Anonymized",
            "default": false
        },
        "chain_seq": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Chain Seq",
            "description": "Position dans la chaîne d'intégrité du tenant"
        },
        "prev_hash": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Prev Hash",
            "description": "Haché de l'événement précédent"
        },
        "event_hash": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Event Hash",
            "description": "Haché de cet événement"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "actor_id",
        "action",
        "resource_type",
        "resource_id",
        "id",
        "timestamp"
    ],
    "title": "AuditEvent",
    "description": "Événement d'audit complet avec ID et timestamp."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

POST /v1/gdpr/export

Gdpr Export

Description

Export all data for a subject (RGPD droit d'accès).

Input parameters

Parameter In Type Default Nullable Description
X-API-Key header No

Request body

{
    "tenant_id": "string",
    "subject_id": "string",
    "subject_type": "actor_id"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "subject_id": {
            "type": "string",
            "title": "Subject Id",
            "description": "ID de la personne concernee"
        },
        "subject_type": {
            "type": "string",
            "enum": [
                "actor_id",
                "resource_id"
            ],
            "title": "Subject Type",
            "description": "Champ a chercher : actor_id | resource_id",
            "default": "actor_id"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "subject_id"
    ],
    "title": "GDPRExportRequest",
    "description": "Requête d'export des données personnelles (droit d'accès RGPD)."
}

Responses

{
    "subject_id": "string",
    "event_count": 0,
    "events": [
        {}
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "subject_id": {
            "type": "string",
            "title": "Subject Id"
        },
        "event_count": {
            "type": "integer",
            "title": "Event Count"
        },
        "events": {
            "items": {
                "additionalProperties": true,
                "type": "object"
            },
            "type": "array",
            "title": "Events"
        }
    },
    "type": "object",
    "required": [
        "subject_id",
        "event_count",
        "events"
    ],
    "title": "GDPRExportResponse",
    "description": "Réponse d'export RGPD."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

POST /v1/gdpr/anonymize

Gdpr Anonymize

Description

Anonymize a data subject (RGPD droit à l'oubli).

Input parameters

Parameter In Type Default Nullable Description
X-API-Key header No

Request body

{
    "tenant_id": "string",
    "subject_id": "string",
    "subject_type": "actor_id",
    "replacement": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "subject_id": {
            "type": "string",
            "title": "Subject Id",
            "description": "ID de la personne à anonymiser"
        },
        "subject_type": {
            "type": "string",
            "enum": [
                "actor_id",
                "resource_id"
            ],
            "title": "Subject Type",
            "description": "Champ à anonymiser : actor_id | resource_id",
            "default": "actor_id"
        },
        "replacement": {
            "type": "string",
            "title": "Replacement",
            "description": "Valeur de remplacement",
            "default": "[ANONYMIZED]"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "subject_id"
    ],
    "title": "GDPRAnonymizeRequest",
    "description": "Requête d'anonymisation des données personnelles."
}

Responses

{
    "anonymized_count": 0,
    "subject_id": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "anonymized_count": {
            "type": "integer",
            "title": "Anonymized Count"
        },
        "subject_id": {
            "type": "string",
            "title": "Subject Id"
        }
    },
    "type": "object",
    "required": [
        "anonymized_count",
        "subject_id"
    ],
    "title": "AnonymizeResponse",
    "description": "Réponse d'anonymisation."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

GET /v1/stats

Stats

Description

Get audit statistics for a tenant.

Input parameters

Parameter In Type Default Nullable Description
tenant_id query string No
X-API-Key header No

Responses

Schema of the response body
{
    "type": "object",
    "additionalProperties": true,
    "title": "Response Stats V1 Stats Get"
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

GET /v1/proof/verify

Verify Proof

Description

Verify the tenant's integrity chain, optionally over a chain_seq window.

The head, the rows and the count are read in one transaction, streamed by pages of 1000 rows. ok only if the chain is intact and its last row is the head (a deleted tail is caught). Rows written before migration 004 are outside the chain and reported in unchained_count. A window proves the internal chaining of the window and its link to the stored hash of the row before it; only a full verification (no from_seq/to_seq) proves the chain from genesis: see scope and prefix_verified. A window is capped at 10000 rows (422) and an empty explicit window is not ok.

Input parameters

Parameter In Type Default Nullable Description
from_seq query No
tenant_id query string No
to_seq query No
X-API-Key header No

Responses

{
    "ok": true,
    "checked": 0,
    "first_broken_seq": null,
    "first_broken_id": null,
    "reason": null,
    "scope": "full",
    "prefix_verified": true,
    "unchained_count": 0,
    "head_seq": null,
    "head_hash": null
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "ok": {
            "type": "boolean",
            "title": "Ok"
        },
        "checked": {
            "type": "integer",
            "title": "Checked"
        },
        "first_broken_seq": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "First Broken Seq"
        },
        "first_broken_id": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "First Broken Id"
        },
        "reason": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Reason"
        },
        "scope": {
            "type": "string",
            "enum": [
                "full",
                "window"
            ],
            "title": "Scope",
            "default": "full"
        },
        "prefix_verified": {
            "type": "boolean",
            "title": "Prefix Verified",
            "default": true
        },
        "unchained_count": {
            "type": "integer",
            "title": "Unchained Count",
            "default": 0
        },
        "head_seq": {
            "anyOf": [
                {
                    "type": "integer"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Head Seq"
        },
        "head_hash": {
            "anyOf": [
                {
                    "type": "string"
                },
                {
                    "type": "null"
                }
            ],
            "title": "Head Hash"
        }
    },
    "type": "object",
    "required": [
        "ok",
        "checked"
    ],
    "title": "ChainReport",
    "description": "Verdict, scope and the tenant's rows outside the chain (written before 004).\n\n`scope` is \"full\" when the whole chain from genesis to the head was verified,\n\"window\" otherwise. A window proves its own internal chaining and its link\nto the STORED hash of the row before it; it does not re-verify the prefix\n(`prefix_verified` is false) nor, if it stops before the head, the tail.\nOnly a full verification proves the chain from genesis."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

GET /v1/events/{event_id}/proof

Event Proof

Description

Return one event's stored hash next to a fresh recomputation.

The row is read under the authenticated tenant's scope: an event of another tenant, an unknown id and a row outside the chain all answer 404 alike. A 403 would confirm the id exists, enough to probe a competitor's volume.

Input parameters

Parameter In Type Default Nullable Description
event_id path string No
X-API-Key header No

Responses

{
    "event_id": "string",
    "tenant_id": "string",
    "chain_seq": 0,
    "prev_hash": "string",
    "event_hash": "string",
    "recomputed_hash": "string",
    "ok": true
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "event_id": {
            "type": "string",
            "title": "Event Id"
        },
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "chain_seq": {
            "type": "integer",
            "title": "Chain Seq"
        },
        "prev_hash": {
            "type": "string",
            "title": "Prev Hash"
        },
        "event_hash": {
            "type": "string",
            "title": "Event Hash"
        },
        "recomputed_hash": {
            "type": "string",
            "title": "Recomputed Hash"
        },
        "ok": {
            "type": "boolean",
            "title": "Ok"
        }
    },
    "type": "object",
    "required": [
        "event_id",
        "tenant_id",
        "chain_seq",
        "prev_hash",
        "event_hash",
        "recomputed_hash",
        "ok"
    ],
    "title": "EventProof",
    "description": "Proof of one event: its stored hash next to a fresh recomputation.\n\n`ok` proves this row alone (recomputed hash equals the stored one). It does\nnot prove the link to the previous row nor the chain: use `/v1/proof/verify`."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

POST /v1/exports/compliance

Export Compliance

Description

Build the proof dossier (Markdown) for one tenant over one period.

The chain verdict covers the WHOLE chain from genesis (scope "full"), never only the period. The period is at most 366 days (422 otherwise). The dossier lists at most the 1000 most recent events of the period: beyond that it says so ("dossier tronqué à 1000 événements : N au total sur la période"), and event_count, by_action and by_resource_type still cover the whole period. Counts, list and verdict are read in one transaction (one instant). The full verification reads the tenant's whole chain on every call: O(N) in its rows (rate limiting is Kong's job). It is an integrity finding, not a legal compliance attestation.

Input parameters

Parameter In Type Default Nullable Description
X-API-Key header No

Request body

{
    "tenant_id": "string",
    "period_start": "2022-04-13T15:42:05.901Z",
    "period_end": "2022-04-13T15:42:05.901Z"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "period_start": {
            "type": "string",
            "format": "date-time",
            "title": "Period Start"
        },
        "period_end": {
            "type": "string",
            "format": "date-time",
            "title": "Period End"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "period_start",
        "period_end"
    ],
    "title": "ComplianceExportRequest",
    "description": "Requête de dossier de preuve : tenant et période bornée (UTC, fuseau explicite)."
}

Responses

{
    "tenant_id": "string",
    "period_start": "2022-04-13T15:42:05.901Z",
    "period_end": "2022-04-13T15:42:05.901Z",
    "generated_at": "2022-04-13T15:42:05.901Z",
    "event_count": 0,
    "listed_count": 0,
    "truncated": true,
    "chain": {
        "ok": true,
        "checked": 0,
        "first_broken_seq": null,
        "first_broken_id": null,
        "reason": null,
        "scope": "full",
        "prefix_verified": true,
        "unchained_count": 0,
        "head_seq": null,
        "head_hash": null
    },
    "by_action": {},
    "by_resource_type": {},
    "markdown": "string"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "tenant_id": {
            "type": "string",
            "title": "Tenant Id"
        },
        "period_start": {
            "type": "string",
            "format": "date-time",
            "title": "Period Start"
        },
        "period_end": {
            "type": "string",
            "format": "date-time",
            "title": "Period End"
        },
        "generated_at": {
            "type": "string",
            "format": "date-time",
            "title": "Generated At"
        },
        "event_count": {
            "type": "integer",
            "title": "Event Count"
        },
        "listed_count": {
            "type": "integer",
            "title": "Listed Count"
        },
        "truncated": {
            "type": "boolean",
            "title": "Truncated"
        },
        "chain": {
            "$ref": "#/components/schemas/ChainReport"
        },
        "by_action": {
            "additionalProperties": {
                "type": "integer"
            },
            "type": "object",
            "title": "By Action"
        },
        "by_resource_type": {
            "additionalProperties": {
                "type": "integer"
            },
            "type": "object",
            "title": "By Resource Type"
        },
        "markdown": {
            "type": "string",
            "title": "Markdown"
        }
    },
    "type": "object",
    "required": [
        "tenant_id",
        "period_start",
        "period_end",
        "generated_at",
        "event_count",
        "listed_count",
        "truncated",
        "chain",
        "by_action",
        "by_resource_type",
        "markdown"
    ],
    "title": "ComplianceExportResponse",
    "description": "Dossier de preuve d'un tenant pour une période.\n\n`chain` porte sur la chaîne ENTIÈRE (scope \"full\"), jamais sur la seule\npériode. `event_count` est le total sur la période ; `markdown` ne liste que\nles 1000 événements les plus récents (`truncated`)."
}

{
    "detail": [
        {
            "loc": [
                null
            ],
            "msg": "string",
            "type": "string",
            "input": null,
            "ctx": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "properties": {
        "detail": {
            "items": {
                "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
        }
    },
    "type": "object",
    "title": "HTTPValidationError"
}

Schemas

AnonymizeResponse

Name Type Description
anonymized_count integer
subject_id string

AuditEvent

Name Type Description
action string create | read | update | delete | export | login | ...
actor_id string ID de l'auteur de l'action
actor_type string user | system | api_key
anonymized boolean
chain_seq Position dans la chaîne d'intégrité du tenant
changes Changements : {field: {old: x, new: y}}
event_hash Haché de cet événement
id string
metadata IP, user-agent, request_id, etc.
prev_hash Haché de l'événement précédent
resource_id string ID de la ressource concernée
resource_type string invoice | contract | user | ...
severity AuditSeverity
tenant_id string
timestamp string(date-time)

AuditEventCreate

Name Type Description
action string create | read | update | delete | export | login | ...
actor_id string ID de l'auteur de l'action
actor_type string user | system | api_key
changes Changements : {field: {old: x, new: y}}
metadata IP, user-agent, request_id, etc.
resource_id string ID de la ressource concernée
resource_type string invoice | contract | user | ...
severity AuditSeverity
tenant_id string

AuditSeverity

Type: string

BatchCreateRequest

Name Type Description
events Array<AuditEventCreate>

BatchCreateResponse

Name Type Description
created integer
events Array<AuditEvent>

ChainReport

Name Type Description
checked integer
first_broken_id
first_broken_seq
head_hash
head_seq
ok boolean
prefix_verified boolean
reason
scope string
unchained_count integer

ComplianceExportRequest

Name Type Description
period_end string(date-time)
period_start string(date-time)
tenant_id string

ComplianceExportResponse

Name Type Description
by_action
by_resource_type
chain ChainReport
event_count integer
generated_at string(date-time)
listed_count integer
markdown string
period_end string(date-time)
period_start string(date-time)
tenant_id string
truncated boolean

EventProof

Name Type Description
chain_seq integer
event_hash string
event_id string
ok boolean
prev_hash string
recomputed_hash string
tenant_id string

GDPRAnonymizeRequest

Name Type Description
replacement string Valeur de remplacement
subject_id string ID de la personne à anonymiser
subject_type string Champ à anonymiser : actor_id | resource_id
tenant_id string

GDPRExportRequest

Name Type Description
subject_id string ID de la personne concernee
subject_type string Champ a chercher : actor_id | resource_id
tenant_id string

GDPRExportResponse

Name Type Description
event_count integer
events Array<>
subject_id string

HealthResponse

Name Type Description
anchor
relay
service string
status string
version string

HTTPValidationError

Name Type Description
detail Array<ValidationError>

ValidationError

Name Type Description
ctx
input
loc Array<>
msg string
type string