Le Registre
The register of AI usage in your workspace (model, data, date, reviewer, human change), listable through the API and exportable as a signed zip whose attestation commits to the filters.
The AI Act expects an organisation that deploys AI to be able to say, for any output: which model, on which data, when, validated by whom, and with which human changes. Le Registre assembles that answer from what Subsidia already records, and exports it as a file an inspector, an insurer or a client can open.
It does not add a second system of record. Four of the five mentions come from data that exists anyway (every model call, every Gateway certificate, the access journal). The fifth, human review, is captured where it is honest to capture it: at the bottom of a Vérificateur report, in the app.
The five mentions
| Mention | Where it comes from | Field in a row |
|---|---|---|
| Which model | Every model call writes a log line: provider, model, task, tokens, estimated cost. | provider, model, taskType, promptTokens, completionTokens, totalTokens, estimatedCostUsd |
| On which data | For calls through the Gateway, the proof certificate commits to a hash of every passage the answer drew on. | proofId, grounding (number of passages committed) |
| When, by whom | The log line carries the time, the user and the surface; personal data masked before leaving is counted. | at, userId, userEmail, source, piiCount |
| Validated by whom | Human reviews recorded in the app: decision accepted, modified or rejected, reviewer and note. | reviews[].decision, reviewerEmail, note, at |
| Which human changes | The modified decision and its note. | reviews[].decision = "modified", reviews[].note |
Access, plan and scope
The routes need an API key with the proof scope (keys created before scopes existed also work). The register is a plan feature: it is included in the Cabinet plan, and a workspace on another plan gets 402 with code: "PLAN_UPGRADE_REQUIRED"; the key does not bypass it. Every export is written to the workspace access journal.
Within the app, reading the register is a right of every member of the workspace, for the same reason as the access journal: a register only the manager can read is not a counterweight.
Endpoints
GET/v1/registre
List register rows, newest first.
proofOne row per model call of the workspace, with the proof reference and the human reviews attached. Filters combine with AND.
Query parameters
fromstringStart of the period, ISO 8601 (2026-09-01or a full timestamp). Inclusive.tostringEnd of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.modelstringExact model name, for examplegpt-4o-mini.providerstringExact provider name, for exampleopenaiorollama.sourcestringThe surface that made the call, as recorded in the register (thesourcefield of a row).reviewstringKeep only rows that have, or have not, at least one human review. Applied to the page after it is read:totalstill counts all rows matching the other filters.reviewedunreviewedpageintegerdefault1Page number, from 1.pageSizeintegerdefault50Between 1 and 500.
Request examples
curl "https://api.subsidia.protypa.fr/v1/registre?from=2026-09-01&to=2026-10-01&provider=openai&pageSize=100" \n -H "Authorization: Bearer $SUBSIDIA_API_KEY"Responses
A page of rows.
{ "rows": [ { "id": "slog_7c1e90", "at": "2026-09-18T14:02:11.000Z", "provider": "openai", "model": "gpt-4o-mini", "taskType": "chat", "source": "gateway", "userId": "usr_21a", "userEmail": "claire@cabinet.example", "promptTokens": 1840, "completionTokens": 312, "totalTokens": 2152, "estimatedCostUsd": 0.000738, "piiCount": 3, "turnId": "pf_a059713a5f1c4e0b8d7a3c21e9b64f10", "proofId": "pf_a059713a5f1c4e0b8d7a3c21e9b64f10", "grounding": 4, "reviews": [ { "decision": "modified", "reviewerEmail": "paul@cabinet.example", "note": "Montant corrige", "at": "2026-09-18T15:40:00.000Z" } ] } ], "total": 1284, "page": 1, "pageSize": 100}Errors
- 401Missing or invalid key.
- 402
PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal. - 403
scope_deniedThe key does not carry theproofscope.
Notes
The source strings are the surfaces recorded by the platform (for example agent_chat for agent conversations). Read them from your own rows rather than hard-coding a list.
GET/v1/registre/summary
Totals for the same filters.
proofWhat a supervisor reads first: how many calls, how many went to an external provider, how much personal data was masked, how many reviews exist. externalCalls counts calls to openai, anthropic, mistral, groq and azure; everything else ran on the machine.
Query parameters
fromstringStart of the period, ISO 8601 (2026-09-01or a full timestamp). Inclusive.tostringEnd of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.modelstringExact model name, for examplegpt-4o-mini.providerstringExact provider name, for exampleopenaiorollama.sourcestringThe surface that made the call, as recorded in the register (thesourcefield of a row).
Request examples
curl "https://api.subsidia.protypa.fr/v1/registre/summary?from=2026-09-01&to=2026-10-01" \n -H "Authorization: Bearer $SUBSIDIA_API_KEY"Responses
The summary.
{ "summary": { "calls": 1284, "totalTokens": 3150012, "estimatedCostUsd": 1.92, "piiMasked": 407, "externalCalls": 212, "byModel": [ { "model": "qwen2.5:7b", "provider": "ollama", "calls": 1072, "totalTokens": 2480110 }, { "model": "gpt-4o-mini", "provider": "openai", "calls": 212, "totalTokens": 669902 } ], "bySource": [ { "source": "gateway", "calls": 900 }, { "source": "agent_chat", "calls": 384 } ], "reviewed": 96 }}Errors
- 402
PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal. - 403
scope_deniedThe key does not carry theproofscope.
Notes
reviewed counts human reviews recorded in the period, not rows.
GET/v1/registre/export
The signed export, as a zip.
proofThe file to hand to an inspector. It holds up to 5,000 rows matching the filters (narrow the period for larger volumes and export in several parts: each part carries its own filters in its attestation).
Query parameters
fromstringStart of the period, ISO 8601 (2026-09-01or a full timestamp). Inclusive.tostringEnd of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.modelstringExact model name, for examplegpt-4o-mini.providerstringExact provider name, for exampleopenaiorollama.sourcestringThe surface that made the call, as recorded in the register (thesourcefield of a row).reviewstringKeep only reviewed or unreviewed rows.reviewedunreviewed
Request examples
curl "https://api.subsidia.protypa.fr/v1/registre/export?from=2026-09-01&to=2026-10-01" \n -H "Authorization: Bearer $SUBSIDIA_API_KEY" \n -o registre.zipResponses
application/zip, named registre-usage-ia-<date>.zip, with the four files described below.
Errors
- 402
PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal. - 403
scope_deniedThe key does not carry theproofscope.
What is in the zip
| File | Content |
|---|---|
registre.csv | One row per call. Semicolon-separated with a UTF-8 byte-order mark so a French Excel opens it with accents intact. Columns: date, fournisseur, modele, tache, surface, utilisateur, jetons_entree, jetons_sortie, jetons_total, cout_usd, entites_masquees, certificat, passages_engages, relecture, relu_par, note. |
registre.json | The same rows as machine-readable JSON, with the filters that produced them and the summary. |
attestation.txt | The attestation in plain French for a reader who does not know what a hash is: period, row count, external calls, masked entities, reviews, and what the register does and does not establish. |
attestation.json | The signed record: payload, payloadHash, signature. |
The attestation and why it commits to the filters
The attestation is signed with the same Ed25519 key and chained in the same hash chain as Gateway proof certificates: each one embeds the hash of the previous record, so removing one leaves a visible hole.
Its payload commits to the exact fingerprint of registre.csv and registre.json (csvSha256, jsonSha256), to the row count, to a short summary, and to the filters (from, to, model, provider, source, review). The filters are in the signed part on purpose. Without them, an extract limited to September, to one model, or to reviewed rows only could be presented as the whole register. With them, the recipient can read in a signed document exactly what the extract is a slice of.
{ "payload": { "v": 1, "id": "rg_3f9a1c0b7d2e4a6851be90cd", "issuer": "subsidia-registre", "kind": "usage-register", "issuedAt": "2026-10-09T09:05:00.000Z", "chainIndex": 58, "prevHash": "e07b...", "workspaceId": "ws_...", "issuedBy": "usr_21a", "filters": { "from": "2026-09-01T00:00:00.000Z", "to": "2026-10-01T00:00:00.000Z", "model": null, "provider": null, "source": null, "review": null }, "rowCount": 1284, "summary": { "calls": 1284, "totalTokens": 3150012, "externalCalls": 212, "piiMasked": 407, "reviewed": 96 }, "csvSha256": "5c1d...", "jsonSha256": "b82e..." }, "payloadHash": "sha256(canonical-json(payload))", "signature": "base64 Ed25519 over payloadHash"}Verify an export
Three checks, none of which needs an account. Fetch the public key once from GET /v1/gateway/public-key (open route).
- The SHA-256 of each file equals
csvSha256andjsonSha256: the files were not edited. - The SHA-256 of the canonical JSON of
payloadequalspayloadHash: the attestation was not edited. - The Ed25519 signature over
payloadHashverifies with the public key: Subsidia issued it.
Then read payload.filters and payload.rowCount to know what the extract covers.
import crypto from 'node:crypto'import { readFileSync } from 'node:fs'
function canonicalJson(v: unknown): string { if (v === null || typeof v !== 'object') return JSON.stringify(v) if (Array.isArray(v)) return '[' + v.map(canonicalJson).join(',') + ']' const entries = Object.entries(v as Record<string, unknown>) .filter(([, x]) => x !== undefined) .sort(([a], [b]) => (a < b ? -1 : 1)) return '{' + entries.map(([k, x]) => JSON.stringify(k) + ':' + canonicalJson(x)).join(',') + '}'}const sha256 = (s: string) => crypto.createHash('sha256').update(s, 'utf8').digest('hex')
// files extracted from the zip into the current folderconst csv = readFileSync('registre.csv', 'utf8')const json = readFileSync('registre.json', 'utf8')const att = JSON.parse(readFileSync('attestation.json', 'utf8'))const publicKeyPem = readFileSync('public-key.pem', 'utf8') // from GET /v1/gateway/public-key
const filesOk = sha256(csv) === att.payload.csvSha256 && sha256(json) === att.payload.jsonSha256const payloadOk = sha256(canonicalJson(att.payload)) === att.payloadHashconst signatureOk = crypto.verify( null, Buffer.from(att.payloadHash, 'utf8'), crypto.createPublicKey(publicKeyPem), Buffer.from(att.signature, 'base64'),)
console.log({ filesOk, payloadOk, signatureOk, filters: att.payload.filters, rows: att.payload.rowCount })Recording a human review
Reviews are recorded in the app, at the bottom of a Vérificateur report, with the reviewer taken from the session and never from a request field: a review that can be signed in someone else's name is worthless. They are returned by GET /v1/verify/:id and appear in the reviews array of register rows and in the export. There is no API route to write a review.
Frequently asked
Is the register the same as the access journal?
No. The access journal records who or what read which client file. The register records AI calls: model, volume, masking, proof, review. They share the same philosophy (open to every member, AI reads and human reads distinguished) but answer different questions.
Does the register contain the prompts or answers?
No. It holds metadata and references: the proof id and the number of committed passages, not their content, and not the question or the answer.
Why are token counts and estimated cost in it?
They are what the platform measured for the call and are useful to an auditor to size usage. They are not billing amounts: customers are billed in questions, see Rate limits and quotas.