Skip to content

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

MentionWhere it comes fromField in a row
Which modelEvery model call writes a log line: provider, model, task, tokens, estimated cost.provider, model, taskType, promptTokens, completionTokens, totalTokens, estimatedCostUsd
On which dataFor 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 whomThe log line carries the time, the user and the surface; personal data masked before leaving is counted.at, userId, userEmail, source, piiCount
Validated by whomHuman reviews recorded in the app: decision accepted, modified or rejected, reviewer and note.reviews[].decision, reviewerEmail, note, at
Which human changesThe 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.

API key (Bearer or x-api-key)proof

One row per model call of the workspace, with the proof reference and the human reviews attached. Filters combine with AND.

Query parameters

  • fromstring
    Start of the period, ISO 8601 (2026-09-01 or a full timestamp). Inclusive.
  • tostring
    End of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.
  • modelstring
    Exact model name, for example gpt-4o-mini.
  • providerstring
    Exact provider name, for example openai or ollama.
  • sourcestring
    The surface that made the call, as recorded in the register (the source field of a row).
  • reviewstring
    Keep only rows that have, or have not, at least one human review. Applied to the page after it is read: total still counts all rows matching the other filters.
    reviewedunreviewed
  • pageintegerdefault 1
    Page number, from 1.
  • pageSizeintegerdefault 50
    Between 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.

Example response
{
"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.
  • 402PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal.
  • 403scope_deniedThe key does not carry the proof scope.

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.

API keyproof

What 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

  • fromstring
    Start of the period, ISO 8601 (2026-09-01 or a full timestamp). Inclusive.
  • tostring
    End of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.
  • modelstring
    Exact model name, for example gpt-4o-mini.
  • providerstring
    Exact provider name, for example openai or ollama.
  • sourcestring
    The surface that made the call, as recorded in the register (the source field 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.

Example response
{
"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

  • 402PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal.
  • 403scope_deniedThe key does not carry the proof scope.

Notes

reviewed counts human reviews recorded in the period, not rows.

GET/v1/registre/export

The signed export, as a zip.

API keyproof

The 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

  • fromstring
    Start of the period, ISO 8601 (2026-09-01 or a full timestamp). Inclusive.
  • tostring
    End of the period, ISO 8601. Inclusive. A bare date means midnight at the start of that day.
  • modelstring
    Exact model name, for example gpt-4o-mini.
  • providerstring
    Exact provider name, for example openai or ollama.
  • sourcestring
    The surface that made the call, as recorded in the register (the source field of a row).
  • reviewstring
    Keep 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.zip

Responses

application/zip, named registre-usage-ia-<date>.zip, with the four files described below.

Errors

  • 402PLAN_UPGRADE_REQUIREDThe workspace plan does not include the proof journal.
  • 403scope_deniedThe key does not carry the proof scope.

What is in the zip

FileContent
registre.csvOne 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.jsonThe same rows as machine-readable JSON, with the filters that produced them and the summary.
attestation.txtThe 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.jsonThe 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.

attestation.json (abridged)
{
"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).

  1. The SHA-256 of each file equals csvSha256 and jsonSha256: the files were not edited.
  2. The SHA-256 of the canonical JSON of payload equals payloadHash: the attestation was not edited.
  3. The Ed25519 signature over payloadHash verifies 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 folder
const 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.jsonSha256
const payloadOk = sha256(canonicalJson(att.payload)) === att.payloadHash
const 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.

Related