Skip to content

Last update: October 15, 2026

Audit Trail API

Local port (Docker Compose): 30006 | Repo: solyntek/djinn/services/djinn-audit-trail

Structured, immutable, multi-tenant audit log with GDPR compliance and a cryptographic integrity proof.

Endpoints

Method Route Description
GET /health Health check
POST /v1/events Record an event
POST /v1/events/batch Record N events
GET /v1/events Query, paginated (filters: actor, action, resource type and id, severity)
GET /v1/events/{id} Event details
POST /v1/gdpr/export Export personal data (GDPR)
POST /v1/gdpr/anonymize Anonymize a subject
GET /v1/stats Activity statistics
GET /v1/proof/verify Verify the tenant's integrity chain
GET /v1/events/{id}/proof Proof of one event (stored hash vs recomputed hash)
POST /v1/exports/compliance Proof dossier (Markdown) over a period

Every route except /health requires X-API-Key. tenant_id is mandatory (body or query string) and must be the tenant of your key, otherwise the answer is 403 (/v1/events/{id}/proof takes the tenant from the key). Events written by the other Djinn services reach the same log.

AuditEvent Model

{
  "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 defaults to user, severity is info, warning or critical (default info), metadata is a free object. The stored event adds id, timestamp, anonymized and the chain fields chain_seq, prev_hash, event_hash.

Immutable

Events cannot be modified or deleted (append-only); the only change is the GDPR anonymisation of identifiers (/v1/gdpr/anonymize).

Pagination

GET /v1/events returns a JSON array, most recent first (created_at descending). Page with limit (1 to 1000, default 50) and offset (default 0): request the next page with offset += limit until fewer than limit events come back. Filters: actor_id, action, resource_type, resource_id, severity. GET /v1/events/{id} also needs tenant_id; an unknown event answers 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"

Integrity proof

Each event is chained to the previous one of its tenant by a hash (chain_seq, prev_hash, event_hash). Any change or deletion in the history breaks the chain.

  • GET /v1/proof/verify?tenant_id=... verifies the whole chain from the first event to the head and returns ok, checked, scope (full), head_seq, head_hash, unchained_count (older events written before the chain existed, outside it) and, on failure, first_broken_seq, first_broken_id and reason. With from_seq / to_seq it verifies a window of at most 10,000 events (scope: "window", prefix_verified: false): only a full verification proves the chain from the origin. An inverted or too large window answers 422.
  • GET /v1/events/{id}/proof gives event_hash next to recomputed_hash and ok for that single event; it does not prove the link to the previous event. An unknown event, or one of another tenant, answers 404.
  • POST /v1/exports/compliance (tenant_id, period_start, period_end with an explicit time zone, at most 366 days) returns a Markdown proof dossier: event_count, by_action, by_resource_type, the chain verdict over the whole chain, and the list of the 1000 most recent events of the period (truncated says if the list was cut). It is an integrity finding, not a legal compliance attestation.

Verification reads the whole chain of the tenant: it is meant for audits, not for every request.

Errors

Errors use FastAPI's format, {"detail": "..."}:

Status Cause
401 Missing or invalid API key
403 tenant_id does not match the authenticated tenant
404 Event not found (or belonging to another tenant)
409 Chain integrity conflict on write (/v1/events, /v1/events/batch): the event cannot extend the tenant's chain; do not retry as is
422 Invalid request (period over 366 days, window problem on /v1/proof/verify, ...)

Interactive reference

Generated on every build from the versioned spec openapi/audit-trail-v1.json (ADR-011) — no route is documented by hand.

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