StabisoDocs

API reference

Platform

For the platform operator only, with a platform key or a session of the operator portal: provision organizations, list and open them, suspend and reactivate them, invite their administrators, mark a demo, export an organization's data for its offboarding, read the audit record. A tenant's keys and sessions do not work here, and a platform credential works nowhere else.

Operation Summary Scope Idempotency-Key
GET /v1/platform/audit List the operator's audit record platform:read
POST /v1/platform/auth/google Sign in to the operator portal with Google
POST /v1/platform/auth/logout Sign out of the operator portal platform:read
GET /v1/platform/charts List chart templates platform:read
GET /v1/platform/health Service health: SLOs, error budget, latency, uptime and deploy platform:read
GET /v1/platform/me Who the platform credential is platform:read
GET /v1/platform/models Show the models behind the built-in agents platform:read
PUT /v1/platform/models Choose the models behind the built-in agents platform:provision
POST /v1/platform/models/check Check that a model answers platform:provision
GET /v1/platform/organizations List organizations platform:read
POST /v1/platform/organizations Provision an organization platform:provision required
GET /v1/platform/organizations/{org_id} Get an organization platform:read
POST /v1/platform/organizations/{org_id}/admins Invite or show again an organization's first administrator platform:provision
POST /v1/platform/organizations/{org_id}/delete Delete an organization platform:provision
GET /v1/platform/organizations/{org_id}/exports List an organization's exports platform:read
POST /v1/platform/organizations/{org_id}/exports Export an organization's data platform:provision required
GET /v1/platform/organizations/{org_id}/exports/{run_id} Get an organization's export platform:read
GET /v1/platform/organizations/{org_id}/exports/{run_id}/download Download an organization's export platform:provision
GET /v1/platform/organizations/{org_id}/exports/{run_id}/link Get a link to download an organization's export platform:provision
POST /v1/platform/organizations/{org_id}/mark-demo Mark an organization as a demo platform:provision
POST /v1/platform/organizations/{org_id}/passkey-reset Reset a person's passkeys platform:provision
POST /v1/platform/organizations/{org_id}/reactivate Reactivate an organization platform:provision
POST /v1/platform/organizations/{org_id}/suspend Suspend an organization platform:provision
POST /v1/platform/organizations/{org_id}/undo-delete Undo the deletion of an organization platform:provision

GET /v1/platform/audit

List the operator's audit record

What the operator did and who tried, newest first: sign-ins (and refused ones), sign-outs, provisioning, suspension, reactivation, marking a demo (and refusals), inviting an administrator, opening an organization, and purging a demo. Each row names who (an operator's address or a platform key), from which client address, which organization and the outcome. The record is append-only. 404 for an unknown before. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id list_audit_v1_platform_audit_get

Parameters

In Name Type Required Description
query org_id string (uuid) or null Only the record of this organization; omit for everything.
query limit integer Most rows returned, 1 to 500. (min 1; max 500; default 100)
query before string (uuid) or null Continue after this row (its id, the last of the previous page); omit for the newest.

Example request

curl "$ERP_URL/v1/platform/audit" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK array of PlatformAuditOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "at": "2026-09-21T09:30:00Z",
    "action": "string",
    "outcome": "denied",
    "actor": {},
    "address": "string",
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "org_name": "string",
    "detail": {}
  }
]

POST /v1/platform/auth/google

Sign in to the operator portal with Google

Exchange a Google ID token for an operator session cookie. For the operator portal (/admin), not for scripts: a script uses a platform key.

The token is verified as the web UI's is (signature, audience, expiry, verified email), and the account must also belong to its own Google Workspace (the hd claim is the address's domain), be of an allowed domain when the deployment limits them, and be on the list of platform operators (ERP_PLATFORM_OPERATORS). On success the cookie erp_platform_session is set (HttpOnly, SameSite=Strict, path /v1/platform; Secure on https and in the cloud): it lasts 8 hours and ends after 30 idle minutes, and it authenticates /v1/platform and nothing else. Every attempt, successful or refused, is recorded in the platform audit. Needs the X-ERP-UI: 1 header (403 csrf without it). Refused with 403 for a token that does not verify or an account that is not an operator; 422 when Google sign-in is not configured.

Required scope any valid credential (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id operator_google_login_v1_platform_auth_google_post

Request body

application/json (required): OperatorGoogleLogin

Field Type Required Description
credential string yes Google ID token from Google Identity Services. (min length 1)

Example request

curl -X POST "$ERP_URL/v1/platform/auth/google" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij..."
}'

Responses

Status Meaning Body
200 OK OperatorSignedIn
401 Missing or invalid API key (unauthorized). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "status": "signed_in",
  "email": "string",
  "expires_at": "2026-09-21T09:30:00Z"
}

POST /v1/platform/auth/logout

Sign out of the operator portal

End the operator session whose cookie accompanies the request (it is revoked, not only forgotten) and clear the cookie; recorded in the platform audit. With a platform key there is no session and nothing happens. The response has no body. Needs platform:read and, for a session, the X-ERP-UI: 1 header.

Required scope platform:read (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id operator_logout_v1_platform_auth_logout_post

Example request

curl -X POST "$ERP_URL/v1/platform/auth/logout" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
204 No content
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope

GET /v1/platform/charts

List chart templates

The chart-of-accounts templates a new entity can start from, the value of chart in POST /v1/platform/organizations. With country, the country's standard chart is marked standard: the one an entity of that country gets when the request names no chart (the Swiss KMU chart for CH and LI, the German HGB chart for DE, the French, Italian and Spanish national charts for FR, IT and ES, the generic IFRS chart for a country without one of its own). Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id list_charts_v1_platform_charts_get

Parameters

In Name Type Required Description
query country string or null ISO country code: marks the chart an entity of that country starts from (standard). (pattern ^[A-Z]{2}$)

Example request

curl "$ERP_URL/v1/platform/charts" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK array of ChartTemplate
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "id": "string",
    "name": "string",
    "countries": [
      "string"
    ],
    "standard": false
  }
]

GET /v1/platform/health

Service health: SLOs, error budget, latency, uptime and deploy

How the two services are doing against their objectives, for the operator's console. Per service (erp-api, datavnext-api): each service level objective with its internal target, its compliance over the rolling 28 days and the share of its error budget still unspent (availability: requests that are not a 5xx, among the requests of real callers; latency: the share of reads or writes the application answered under a threshold; the objectives and thresholds are defined in Terraform and named in each entry), the 95th percentile of the last 24 hours by request class (read, report, write, heavy), and the version, revision and time of the last deploy. Also the synthetic uptime checks (probes from outside, every five minutes).

The figures come from Cloud Monitoring and Cloud Run, read with the service's own identity, and are cached for 60 seconds. A part that cannot be read is named in problems and the rest is still answered (status is then degraded; unavailable when nothing could be read, which is also the answer where monitoring is not configured). Counts and durations of requests only: no organization, person or amount. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id service_health_v1_platform_health_get

Example request

curl "$ERP_URL/v1/platform/health" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK ServiceHealth
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope

Example response (shape)

{
  "generated_at": "2026-09-21T09:30:00Z",
  "status": "degraded",
  "services": [
    {
      "id": "string",
      "name": "string",
      "slos": [
        null
      ],
      "latency_p95_ms_24h": {},
      "deploy": null
    }
  ],
  "uptime_checks": [
    {
      "name": "string",
      "url": "string",
      "status": "down",
      "success_ratio_15m": 1.5,
      "success_ratio_24h": 1.5
    }
  ],
  "problems": [
    {
      "section": "string",
      "reason": "string"
    }
  ]
}

GET /v1/platform/me

Who the platform credential is

The operator behind the request: operator:<address> for a session of the operator portal (with the address as email), platform:<prefix> for a platform key. The portal asks it to learn whether the session is still valid; it ends after session_hours from sign-in and after idle_minutes without a request. workspace_domains are the deployment's own Google Workspace domains, whose people sign in with Google: the portal warns when a customer's administrator is invited on one. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id whoami_v1_platform_me_get

Example request

curl "$ERP_URL/v1/platform/me" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK OperatorMe
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope

Example response (shape)

{
  "id": "string",
  "name": "string",
  "email": "string",
  "via": "key",
  "scopes": [
    "string"
  ],
  "session_hours": 1,
  "idle_minutes": 1,
  "workspace_domains": [
    "string"
  ]
}

GET /v1/platform/models

Show the models behind the built-in agents

The models the built-in agents (bill capture, reconciliation, close review, Q&A) ask, in order: the first answers unless it fails or does not answer within two minutes, then the next (a refusal is an answer: no other model is asked). source says whether they are the operator's latest choice or the deployment's defaults. available lists the Gemini models Vertex AI offers at the region now, so that a newer one can be chosen without a deploy (the list is cached for ten minutes; Claude models are not listed by Google). history is the latest choices. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id get_models_v1_platform_models_get

Example request

curl "$ERP_URL/v1/platform/models" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK ModelSettings
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope

Example response (shape)

{
  "models": [
    "string"
  ],
  "source": "deployment",
  "last_resort": [
    "string"
  ],
  "not_listed": [
    "string"
  ],
  "deployment_default": [
    "string"
  ],
  "region": "string",
  "configured": true,
  "available": [
    "string"
  ],
  "available_error": "string",
  "history": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "models": [
        "string"
      ],
      "created_at": "2026-09-21T09:30:00Z",
      "created_by": {},
      "reason": "string"
    }
  ]
}

PUT /v1/platform/models

Choose the models behind the built-in agents

Choose the models the built-in agents ask, in order (1 to 4 Vertex AI model ids of the Gemini or Claude family, each once). They are used from the next agent run on, for every organization, and replace the deployment's defaults; a Claude model must be enabled in Model Garden first. Check a model with POST /v1/platform/models/check before choosing it. A choice is never changed or deleted: a new one replaces it, and each is recorded in the platform audit (platform.choose_models, with the previous models). Only an operator signed in to the portal may: a platform key is refused with 403 (and recorded). 422 for an id that is not a Gemini or Claude model id, or one named twice. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id choose_models_v1_platform_models_put

Request body

application/json (required): ModelChoiceInput

Field Type Required Description
models array of string yes The models to ask, in order: the first answers unless it fails or does not answer, then the next. 1 to 4 ids, each once. (min items 1; max items 4)
reason string or null Why, for the record (1 to 500 characters). (min length 1; max length 500)

Example request

curl -X PUT "$ERP_URL/v1/platform/models" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "models": [
    "gemini-3.8-flash",
    "gemini-3.7-flash"
  ],
  "reason": "3.7 as the fallback while 3.8 is in preview"
}'

Responses

Status Meaning Body
200 OK ModelChoiceOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "models": [
    "string"
  ],
  "created_at": "2026-09-21T09:30:00Z",
  "created_by": {},
  "reason": "string"
}

POST /v1/platform/models/check

Check that a model answers

Ask one model, without fallback, a short test question at the configured Vertex AI region, and say whether it answered and how long it took. No organization's data is sent. The check is recorded in the platform audit (platform.check_model, outcome ok or failed). A model that is unknown, not enabled or not served at the region answers ok: false with the provider's error. 503 when no Google Cloud project is configured. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id check_model_v1_platform_models_check_post

Request body

application/json (required): ModelCheckInput

Field Type Required Description
model string yes A Vertex AI model id of the Gemini or Claude family, e.g. gemini-3.8-flash or claude-opus-5-5. (pattern `^(gemini

Example request

curl -X POST "$ERP_URL/v1/platform/models/check" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "gemini-3.8-flash"
}'

Responses

Status Meaning Body
200 OK ModelCheck
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "model": "string",
  "ok": true,
  "seconds": 1.5,
  "error": "string"
}

GET /v1/platform/organizations

List organizations

The organizations on the platform, oldest first, with their lifecycle state (active or suspended), their kind (customer or demo), when and by whom each was created and why it was last suspended or reactivated. With usage=true each also has the number of entities, people, journal entries and documents and when it was last used (that read is recorded in the platform audit as org.list_usage). Never any accounting data beyond counts. Pages of limit: pass the last org_id as after for the next one. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id list_organizations_v1_platform_organizations_get

Parameters

In Name Type Required Description
query status "active" | "suspended" or null Only active or only suspended organizations; omit for all.
query kind "customer" | "demo" or null Only customer or only demo organizations; omit for all.
query usage boolean Add each organization's counts and last activity (usage). Each one is read in a transaction of its own, so ask for it with a page that you need. (default false)
query limit integer Most organizations returned, 1 to 500. (min 1; max 500; default 100)
query after string (uuid) or null Continue after this organization (its id, the last of the previous page); omit for the first page.

Example request

curl "$ERP_URL/v1/platform/organizations" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK array of OrganizationSummary
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "status": "active",
    "created_at": "2026-09-21T09:30:00Z",
    "created_by": {},
    "status_changed_at": "2026-09-21T09:30:00Z",
    "status_reason": "string",
    "kind": "customer",
    "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "remove_requested_at": "2026-09-21T09:30:00Z",
    "delete_after": "2026-09-21T09:30:00Z",
    "usage": {
      "entities": 1,
      "users": 1,
      "entries": 1,
      "documents": 1,
      "last_activity_at": null,
      "last_sign_in_at": null
    }
  }
]

POST /v1/platform/organizations

Provision an organization

Create an organization with its entities and invite its first people, in one step.

Each entity starts from a template (ch_sme, de_sme or generic: country, functional currency, chart of accounts, VAT method, fiscal calendar), which the request can override. The chart brings every account of the roles the services need. The first administrator (admin) and any users are invited by email. When password sign-in is configured, Google e-mails each of them the link that sets their password (password_email_sent), except to an address of the deployment's Google Workspace domains, whose people sign in with Google. The e-mails go out last, once everything is written; if Google refuses (502), nothing is created and the request can be repeated. No key is created; the administrator creates their own API key after the first sign-in, and it is bound to them.

All or nothing: if anything fails (an unknown chart, an entity code twice, an address invited twice) nothing is created. A retry with the same Idempotency-Key and body returns the stored response; the same key with another body answers 409. Needs the platform:provision scope. The creation is an audit event of the new organization (org.create, entity.create, user.invite, by platform:<prefix> or, from the operator portal, operator:<address>), a security event (platform.organization.provision) and a row of the platform audit. replayed is true when the answer is the stored one of an earlier request with the same key. Refused: 401 without a valid platform key or operator session, 403 without the scope, 409 for a reused key or an entity code or address twice, 422 for an unknown chart or a template without country or currency.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key required
Operation id provision_organization_v1_platform_organizations_post

Request body

application/json (required): ProvisionInput

Field Type Required Description
name string yes The organization's name, 1 to 200 characters. (min length 1; max length 200)
admin ProvisionAdmin yes The first administrator, who is invited with every scope and signs in with a password or Google. The organization has no API key: it creates its own, bound to this person, after the first sign-in.
users array of ProvisionUser Further people to invite, up to 20. Invite a controller here: new vendors and the people's work need a second person (four eyes), so the administrator cannot complete the onboarding alone. (max items 20)
entities array of ProvisionEntity The legal entities to create, each from a template. Default: none, and the first administrator sets the organization up at their first sign-in (POST /v1/setup): the country, accounting standard, currency, fiscal year and tax, then Production and its Test environment are made (ADR-0041). (max items 20)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "admin": {
    "email": "petra.muster@example.com",
    "name": "Petra Muster"
  },
  "entities": [
    {
      "code": "CH01",
      "fiscal_years": [
        2026
      ],
      "name": "Muster Treuhand AG",
      "template": "ch_sme",
      "vat_number": "CHE-123.456.788 MWST"
    }
  ],
  "name": "Muster Treuhand AG",
  "users": [
    {
      "email": "hans.keller@example.com",
      "name": "Hans Keller"
    }
  ]
}'

Responses

Status Meaning Body
201 Created Provisioned
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "organization": {
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "status": "active",
    "created_at": "2026-09-21T09:30:00Z",
    "created_by": {},
    "status_changed_at": "2026-09-21T09:30:00Z",
    "status_reason": "string",
    "kind": "customer",
    "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "remove_requested_at": "2026-09-21T09:30:00Z",
    "delete_after": "2026-09-21T09:30:00Z"
  },
  "entities": [
    {
      "code": "string",
      "name": "string",
      "country": "string",
      "functional_currency": "string",
      "chart": "string",
      "role_accounts": 1,
      "fiscal_years": [
        1
      ],
      "vat_method": "string"
    }
  ],
  "users": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "email": "string",
      "scopes": [
        "string"
      ],
      "status": "string",
      "password_email_sent": false
    }
  ],
  "replayed": false
}

GET /v1/platform/organizations/{org_id}

Get an organization

One organization as the operator sees it: its lifecycle state and kind, its entities (code, name, country, currency), its people (address, role, status, last sign-in), its counts and the evidence of use that decides whether it may be marked as a demo. Master data and counts only: no ledger line, amount or document. Opening an organization is recorded in the platform audit (org.view). 404 for an unknown id. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id get_organization_v1_platform_organizations__org_id__get

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Example request

curl "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK OrganizationDetail
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "status": "active",
  "created_at": "2026-09-21T09:30:00Z",
  "created_by": {},
  "status_changed_at": "2026-09-21T09:30:00Z",
  "status_reason": "string",
  "kind": "customer",
  "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "remove_requested_at": "2026-09-21T09:30:00Z",
  "delete_after": "2026-09-21T09:30:00Z",
  "usage": {
    "entities": 1,
    "users": 1,
    "entries": 1,
    "documents": 1,
    "last_activity_at": "2026-09-21T09:30:00Z",
    "last_sign_in_at": "2026-09-21T09:30:00Z"
  },
  "entities": [
    {
      "code": "string",
      "name": "string",
      "country": "string",
      "functional_currency": "string"
    }
  ],
  "users": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "email": "string",
      "name": "string",
      "role": "string",
      "status": "string",
      "created_at": "2026-09-21T09:30:00Z",
      "last_login_at": "2026-09-21T09:30:00Z"
    }
  ],
  "evidence": {
    "users": 1,
    "users_not_administrators": 1,
    "users_invited_by_people": 1,
    "users_signed_in": 1,
    "journal_entries": 1,
    "journal_entries_not_by_the_seed": 1,
    "documents": 1,
    "documents_not_by_the_seed": 1,
    "document_dispatches": 1,
    "issued_outputs_archived": 1,
    "bank_statements": 1,
    "first_posting": "2026-09-21",
    "last_posting": "2026-09-21",
    "customer_grade": [
      "string"
    ]
  }
}

POST /v1/platform/organizations/{org_id}/admins

Invite or show again an organization's first administrator

Invite an administrator of an existing organization, who signs in with the Google account of the address or, without one, with a password. A person who is not a user yet is added (invited); one who was invited and has not signed in is left as they are (already_invited) and the answer says again where to sign in. When password sign-in is configured, Google e-mails them the link that sets their password, a fresh one each time (password_email_sent; not to an address of the deployment's Google Workspace domains; 502 when Google refuses). Both are recorded in the platform audit; a new invitation is also an event of the organization's audit trail. Safe to repeat. 409 for a person who has signed in, who is disabled or who was invited with another role; 404 for an unknown organization. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id invite_admin_v1_platform_organizations__org_id__admins_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): AdminInvite

Field Type Required Description
email string yes The administrator's address; they sign in with the Google account of it, or, without one, with the address and a password. (pattern ^[^@\s]+@[^@\s]+\.[^@\s]+$)
name string or null Display name. (max length 200)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/admins" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "petra.muster@example.com",
  "name": "Petra Muster"
}'

Responses

Status Meaning Body
200 OK AdminInvited
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "user": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "scopes": [
      "string"
    ],
    "status": "string",
    "password_email_sent": false
  },
  "result": "already_invited",
  "sign_in_path": "string",
  "password_email_sent": false
}

POST /v1/platform/organizations/{org_id}/delete

Delete an organization

Delete an organization and its test copies, with everything they hold (ADR-0042). Only an operator signed in to the portal may: a platform key is refused with 403. confirm_name must be the organization's exact name (422 otherwise). An organization that shows signs of real use (the evidence of its detail) is refused with 409 customer_activity unless accept_activity is true. The answer is 202: from now on nobody reaches the organization. For an hour (until delete_after) the deletion can be undone (undo-delete); after that the owner's job, which runs every hour and at every deploy, deletes it with the demo purge over the owner connection (ADR-0022) and records how many rows it deleted, and nothing brings its books back but a restore of the database. Asking again changes nothing. The request and a refusal are recorded in the platform audit. 404 for an unknown id. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id delete_organization_v1_platform_organizations__org_id__delete_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): DeleteOrganizationInput

Field Type Required Description
confirm_name string yes The organization's exact name, as it is written (no trimming, no case folding): a guard against deleting the wrong one. (min length 1; max length 200)
accept_activity boolean Delete it although it shows signs of real use (the evidence of the organization's detail, customer_grade): its books are deleted with it. Without it, such an organization is refused with 409 customer_activity. (default false)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/delete" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "confirm_name": "Muster Treuhand AG"
}'

Responses

Status Meaning Body
202 Successful Response DeleteOrganizationResult
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "organization": {
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "status": "active",
    "created_at": "2026-09-21T09:30:00Z",
    "created_by": {},
    "status_changed_at": "2026-09-21T09:30:00Z",
    "status_reason": "string",
    "kind": "customer",
    "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "remove_requested_at": "2026-09-21T09:30:00Z",
    "delete_after": "2026-09-21T09:30:00Z"
  },
  "changed": true,
  "evidence": {
    "users": 1,
    "users_not_administrators": 1,
    "users_invited_by_people": 1,
    "users_signed_in": 1,
    "journal_entries": 1,
    "journal_entries_not_by_the_seed": 1,
    "documents": 1,
    "documents_not_by_the_seed": 1,
    "document_dispatches": 1,
    "issued_outputs_archived": 1,
    "bank_statements": 1,
    "first_posting": "2026-09-21",
    "last_posting": "2026-09-21",
    "customer_grade": [
      "string"
    ]
  }
}

GET /v1/platform/organizations/{org_id}/exports

List an organization's exports

The organization's exports, newest first, with their status, whoever asked for them. Pages of limit: pass the last id as after for the next one. Needs the platform:read scope. 404 for an unknown organization.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id list_organization_exports_v1_platform_organizations__org_id__exports_get

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).
query limit integer Most exports returned, 1 to 100. (min 1; max 100; default 25)
query after string (uuid) or null Continue after this export (its id, the last of the previous page); omit for the first page.

Example request

curl "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/exports" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK array of ExportOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "status": "expired",
    "via": "platform",
    "language": "de",
    "note": "string",
    "requested_by": {},
    "requested_at": "2026-09-21T09:30:00Z",
    "started_at": "2026-09-21T09:30:00Z",
    "finished_at": "2026-09-21T09:30:00Z",
    "stage": "string",
    "progress": {},
    "error": "string",
    "cutoff": "2026-09-21T09:30:00Z",
    "file_name": "string",
    "size_bytes": 1,
    "sha256": "string",
    "manifest_sha256": "string",
    "file_count": 1,
    "row_count": 1,
    "complete": true,
    "statement": {},
    "expires_at": "2026-09-21T09:30:00Z"
  }
]

POST /v1/platform/organizations/{org_id}/exports

Export an organization's data

Ask for the complete package of an organization's data, as the organization's own administrators can (POST /v1/exports): every table as CSV and JSON, the reports as PDF and CSV, every stored file checked against its SHA-256, the hash-chain verification, a manifest with the hash of every file, the README, and for a German entity the index.xml. Meant for offboarding: suspend the organization first (POST .../suspend), then export, then hand over. The export runs asynchronously from one consistent snapshot; follow it with GET .../exports/{id}.

The platform reads none of the books through this API: the job that builds the package does, over the database's row-level security, scoped to the one organization. When it has succeeded, the SHA-256 of the package is recorded in the platform's append-only audit record (platform_audit, action org.export), so that we can later attest what was handed over. One export at a time per organization (409 export_in_progress). Needs the platform:provision scope. Starting is an audit event of the organization (export.start, by platform:<prefix>), a security event (platform.export.start) and a row of the operator's record (org.export_start). 404 for an unknown organization. A retry with the same Idempotency-Key returns the first answer.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key required
Operation id start_organization_export_v1_platform_organizations__org_id__exports_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): ExportRequest

Field Type Required Description
language "de" | "en" | "fr" | "it" | "sv" or null The language of the package's README: en, de, fr, it or sv. Default: the language of the person who asks, else the country of the organization's first entity (de for CH and DE, fr for FR, it for IT, sv for SE), else en.
note string or null Why the export is made, up to 500 characters; kept with the request and in the audit trail. (max length 500)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/exports" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "language": "de",
  "note": "Archive copy at contract end"
}'

Responses

Status Meaning Body
202 Successful Response ExportOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "expired",
  "via": "platform",
  "language": "de",
  "note": "string",
  "requested_by": {},
  "requested_at": "2026-09-21T09:30:00Z",
  "started_at": "2026-09-21T09:30:00Z",
  "finished_at": "2026-09-21T09:30:00Z",
  "stage": "string",
  "progress": {},
  "error": "string",
  "cutoff": "2026-09-21T09:30:00Z",
  "file_name": "string",
  "size_bytes": 1,
  "sha256": "string",
  "manifest_sha256": "string",
  "file_count": 1,
  "row_count": 1,
  "complete": true,
  "statement": {},
  "expires_at": "2026-09-21T09:30:00Z"
}

GET /v1/platform/organizations/{org_id}/exports/{run_id}

Get an organization's export

One export with its status and, once it has succeeded, the file's SHA-256 and the hand-over statement. 404 for an unknown organization or an export of another one. Needs the platform:read scope.

Required scope platform:read (a platform key; no tenant credential works here)
Operation id get_organization_export_v1_platform_organizations__org_id__exports__run_id__get

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).
path run_id string (uuid) yes Id of an export (GET /v1/exports).

Example request

curl "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/exports/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK ExportOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "expired",
  "via": "platform",
  "language": "de",
  "note": "string",
  "requested_by": {},
  "requested_at": "2026-09-21T09:30:00Z",
  "started_at": "2026-09-21T09:30:00Z",
  "finished_at": "2026-09-21T09:30:00Z",
  "stage": "string",
  "progress": {},
  "error": "string",
  "cutoff": "2026-09-21T09:30:00Z",
  "file_name": "string",
  "size_bytes": 1,
  "sha256": "string",
  "manifest_sha256": "string",
  "file_count": 1,
  "row_count": 1,
  "complete": true,
  "statement": {},
  "expires_at": "2026-09-21T09:30:00Z"
}

GET /v1/platform/organizations/{org_id}/exports/{run_id}/download

Download an organization's export

The package of a succeeded export, as a ZIP, streamed through the API (use a client that writes to a file; for a large package prefer the link). Every download is an audit event of the organization (export.download), a security event (platform.export.download) and a row of the operator's record (org.export_download). Needs the platform:provision scope. 409 export_not_ready unless the export succeeded and has not expired.

Required scope platform:provision (a platform key; no tenant credential works here)
Operation id download_organization_export_v1_platform_organizations__org_id__exports__run_id__download_get

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).
path run_id string (uuid) yes Id of an export (GET /v1/exports).

Example request

curl "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/exports/3fa85f64-5717-4562-b3fc-2c963f66afa6/download" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 The package: a ZIP, streamed (SHA-256 in X-Content-SHA256). application/zip
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Get a link to download an organization's export

A signed link that downloads the package straight from the store without credentials, for seven days at most and never beyond the package's expiry: this is how the package is handed over to the customer, who has no platform key. Anyone who holds the link can download until it ends: send it over a channel you trust, together with the statement (GET .../exports/{id}: SHA-256 of the file). Giving the link is an audit event (export.link), a security event (platform.export.download) and a row of the operator's record (org.export_link). Needs the platform:provision scope. 409 export_not_ready unless the export succeeded and has not expired, 409 no_signed_link where links cannot be signed (use /download).

Required scope platform:provision (a platform key; no tenant credential works here)
Operation id organization_export_link_v1_platform_organizations__org_id__exports__run_id__link_get

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).
path run_id string (uuid) yes Id of an export (GET /v1/exports).

Example request

curl "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/exports/3fa85f64-5717-4562-b3fc-2c963f66afa6/link" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK ExportLink
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "url": "string",
  "expires_at": "2026-09-21T09:30:00Z",
  "file_name": "string",
  "size_bytes": 1,
  "sha256": "string"
}

POST /v1/platform/organizations/{org_id}/mark-demo

Mark an organization as a demo

Make an organization a demo: the one kind the operator may purge (with a command, never through this API; a customer's books are never deleted). Only an operator signed in to the portal may: a platform key is refused with 403. confirm_name must be the organization's exact name (422 otherwise). It is done by a database function that also checks the evidence of use: an organization that shows any sign of real use (an invoice sent to a customer, a file archived as issued, a person who is not an administrator or was invited by a person or a key, more than one person who has signed in, entries or documents not created by the demo seed) is refused with 409 customer_activity, and the evidence is in the answer. There is no way to accept it here: an organization that was used is marked only by the command erp platform mark-demo --accept-activity on the owner connection. A marking and a refusal are recorded in the platform audit. Marking a demo again changes and records nothing. 404 for an unknown id. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id mark_demo_v1_platform_organizations__org_id__mark_demo_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): MarkDemoInput

Field Type Required Description
confirm_name string yes The organization's exact name, as it is written (no trimming, no case folding): a guard against marking the wrong one. (min length 1; max length 200)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/mark-demo" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "confirm_name": "Muster Treuhand AG"
}'

Responses

Status Meaning Body
200 OK MarkDemoResult
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "organization": {
    "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "status": "active",
    "created_at": "2026-09-21T09:30:00Z",
    "created_by": {},
    "status_changed_at": "2026-09-21T09:30:00Z",
    "status_reason": "string",
    "kind": "customer",
    "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "remove_requested_at": "2026-09-21T09:30:00Z",
    "delete_after": "2026-09-21T09:30:00Z"
  },
  "changed": true,
  "evidence": {
    "users": 1,
    "users_not_administrators": 1,
    "users_invited_by_people": 1,
    "users_signed_in": 1,
    "journal_entries": 1,
    "journal_entries_not_by_the_seed": 1,
    "documents": 1,
    "documents_not_by_the_seed": 1,
    "document_dispatches": 1,
    "issued_outputs_archived": 1,
    "bank_statements": 1,
    "first_posting": "2026-09-21",
    "last_posting": "2026-09-21",
    "customer_grade": [
      "string"
    ]
  }
}

POST /v1/platform/organizations/{org_id}/passkey-reset

Reset a person's passkeys

For a person of the organization who lost every device with a passkey and has no administrator to ask, such as a company's only owner: remove their passkeys and end all their sessions, in every organization; their next sign-in creates a new first passkey. Only an operator signed in to the portal (403 for a platform key), with the platform:provision scope. 404 when the address is not a user of the organization. Recorded in the platform audit (org.passkey_reset), the organization's audit trail (user.passkey_reset) and as a security event (passkey.reset).

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id operator_reset_passkeys_v1_platform_organizations__org_id__passkey_reset_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): OperatorPasskeyReset

Field Type Required Description
email string yes The address of the person whose passkeys to reset.

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/passkey-reset" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "erin@example.com"
}'

Responses

Status Meaning Body
200 OK OperatorPasskeyResetDone
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "email": "string",
  "removed": 1
}

POST /v1/platform/organizations/{org_id}/reactivate

Reactivate an organization

Lift a suspension: the organization can change data again. The reason goes to the audit trail (org.reactivate) and to a security event. Safe to repeat: an organization that is already active is returned unchanged. 404 for an unknown id. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id reactivate_organization_v1_platform_organizations__org_id__reactivate_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): StatusChange

Field Type Required Description
reason string yes Why the organization is suspended or reactivated, 3 to 500 characters; kept in the organization's audit trail and in the platform's security events. (min length 3; max length 500)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/reactivate" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Invoice 2026-114 unpaid after two reminders"
}'

Responses

Status Meaning Body
200 OK TenantOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "status": "active",
  "created_at": "2026-09-21T09:30:00Z",
  "created_by": {},
  "status_changed_at": "2026-09-21T09:30:00Z",
  "status_reason": "string",
  "kind": "customer",
  "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "remove_requested_at": "2026-09-21T09:30:00Z",
  "delete_after": "2026-09-21T09:30:00Z"
}

POST /v1/platform/organizations/{org_id}/suspend

Suspend an organization

Make an organization read-only. From now on its API keys and sessions can read everything and change nothing: every request that would change state answers 403 (suspended); signing in and out still work. Nothing is deleted. The reason goes to the organization's audit trail (org.suspend) and to a security event. Safe to repeat: an organization that is already suspended is returned unchanged. 404 for an unknown id. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id suspend_organization_v1_platform_organizations__org_id__suspend_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Request body

application/json (required): StatusChange

Field Type Required Description
reason string yes Why the organization is suspended or reactivated, 3 to 500 characters; kept in the organization's audit trail and in the platform's security events. (min length 3; max length 500)

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/suspend" \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Invoice 2026-114 unpaid after two reminders"
}'

Responses

Status Meaning Body
200 OK TenantOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "status": "active",
  "created_at": "2026-09-21T09:30:00Z",
  "created_by": {},
  "status_changed_at": "2026-09-21T09:30:00Z",
  "status_reason": "string",
  "kind": "customer",
  "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "remove_requested_at": "2026-09-21T09:30:00Z",
  "delete_after": "2026-09-21T09:30:00Z"
}

POST /v1/platform/organizations/{org_id}/undo-delete

Undo the deletion of an organization

Undo a deletion asked for less than an hour ago (before delete_after): the organization, and the test copies deleted with it, are reachable again as they were. Only an operator signed in to the portal may: a platform key is refused with 403. After the hour the owner's job may be deleting it: 409. Safe to repeat: an organization whose deletion was not asked for is returned unchanged. Recorded in the platform audit (org.delete_undone). 404 for an unknown id. Needs the platform:provision scope.

Required scope platform:provision (a platform key; no tenant credential works here)
Idempotency-Key not used
Operation id undo_delete_organization_v1_platform_organizations__org_id__undo_delete_post

Parameters

In Name Type Required Description
path org_id string (uuid) yes Organization id (GET /v1/platform/organizations).

Example request

curl -X POST "$ERP_URL/v1/platform/organizations/3fa85f64-5717-4562-b3fc-2c963f66afa6/undo-delete" \
  -H "Authorization: Bearer $PLATFORM_KEY"

Responses

Status Meaning Body
200 OK TenantOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "status": "active",
  "created_at": "2026-09-21T09:30:00Z",
  "created_by": {},
  "status_changed_at": "2026-09-21T09:30:00Z",
  "status_reason": "string",
  "kind": "customer",
  "copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "remove_requested_at": "2026-09-21T09:30:00Z",
  "delete_after": "2026-09-21T09:30:00Z"
}

Schemas

AdminInvite

Field Type Required Description
email string yes The administrator's address; they sign in with the Google account of it, or, without one, with the address and a password. (pattern ^[^@\s]+@[^@\s]+\.[^@\s]+$)
name string or null Display name. (max length 200)

AdminInvited

Field Type Required Description
user ProvisionedUser yes The administrator, as invited.
result "already_invited" | "invited" yes invited: the person was just added. already_invited: they were invited before and have not signed in yet; nothing changed, and the act is recorded.
sign_in_path string yes Where they sign in, on the host that serves the API (/app/).
password_email_sent boolean True when Google e-mailed them the link that sets a password (a fresh one for someone invited before); false when password sign-in is not configured, or for an address of a Google Workspace domain, whose people sign in with Google. (default false)

DeleteOrganizationInput

Field Type Required Description
confirm_name string yes The organization's exact name, as it is written (no trimming, no case folding): a guard against deleting the wrong one. (min length 1; max length 200)
accept_activity boolean Delete it although it shows signs of real use (the evidence of the organization's detail, customer_grade): its books are deleted with it. Without it, such an organization is refused with 409 customer_activity. (default false)

DeleteOrganizationResult

Field Type Required Description
organization TenantOut yes The organization, with remove_requested_at set: deleted for good after delete_after, and until then the deletion can be undone.
changed boolean yes False when its deletion was asked for already.
evidence OrganizationEvidence yes The evidence of use that was measured.

DeployOut

What the Cloud Run service runs.

Field Type Required Description
version string or null yes The image tag of the latest revision: the first 12 characters of the commit.
latest_revision string or null yes The latest ready revision.
serving_revision string or null yes The revision with the largest traffic share. It differs from latest_revision during a canary and after a rollback.
updated_at string (date-time) or null yes When the service last changed: a deploy, a rollback or a settings change (ISO 8601, UTC).

EntitySummary

Field Type Required Description
code string yes The entity's code.
name string yes Legal name.
country string yes ISO 3166-1 alpha-2 country code.
functional_currency string yes ISO 4217 code of the books' currency.

MarkDemoInput

Field Type Required Description
confirm_name string yes The organization's exact name, as it is written (no trimming, no case folding): a guard against marking the wrong one. (min length 1; max length 200)

MarkDemoResult

Field Type Required Description
organization TenantOut yes The organization, now of kind demo.
changed boolean yes False when it already was a demo: nothing was changed.
evidence OrganizationEvidence yes The evidence of use that was measured.

ModelCheck

Field Type Required Description
model string yes The model asked.
ok boolean yes True when the model answered a short test request.
seconds number yes How long it took to answer or fail.
error string or null yes What went wrong, when it did not answer.

ModelCheckInput

Field Type Required Description
model string yes A Vertex AI model id of the Gemini or Claude family, e.g. gemini-3.8-flash or claude-opus-5-5. (pattern `^(gemini

ModelChoiceInput

Field Type Required Description
models array of string yes The models to ask, in order: the first answers unless it fails or does not answer, then the next. 1 to 4 ids, each once. (min items 1; max items 4)
reason string or null Why, for the record (1 to 500 characters). (min length 1; max length 500)

ModelChoiceOut

Field Type Required Description
id string (uuid) yes Id (uuid) of the choice.
models array of string yes The models, in the order they are asked.
created_at string (date-time) yes When it was chosen (ISO 8601, UTC).
created_by object yes Who chose it: type, id, name, email.
reason string or null yes Why, as given.

ModelSettings

Field Type Required Description
models array of string yes The models the built-in agents ask now, in order (the first, then each fallback).
source "deployment" | "operator" yes operator: the latest choice made here; deployment: none was made, so the defaults of the deployment apply (ERP_LLM_PROVIDER, ERP_LLM_MODEL, ERP_LLM_FALLBACK_MODELS).
last_resort array of string yes With an operator's choice, the deployment's defaults asked after it when every chosen model fails, so that an id Vertex AI does not serve cannot stop the agents.
not_listed array of string yes Chosen Gemini models that are not in available: possibly a typo or a retired model. Empty when the list could not be read.
deployment_default array of string yes The deployment's defaults, which apply when no choice was made.
region string yes The Vertex AI location the models are called at.
configured boolean yes False when no Google Cloud project is configured: the agents answer 503.
available array of string yes The Gemini text models Google lists for the project at region now (embedding models left out). Claude models are not listed by Google: enable one in Model Garden, then enter its id.
available_error string or null yes Why the list of available models could not be read, if it could not.
history array of ModelChoiceOut yes The latest choices, newest first.

OperatorGoogleLogin

Field Type Required Description
credential string yes Google ID token from Google Identity Services. (min length 1)

OperatorMe

Field Type Required Description
id string yes operator:<address> of a signed-in operator, platform:<prefix> of a key.
name string yes The operator's address, or the key's name.
email string or null yes The operator's address; null for a platform key.
via "key" | "session" yes What authenticated the request.
scopes array of string yes The platform scopes it holds.
session_hours integer yes How long a session lasts from sign-in.
idle_minutes integer yes How long a session may sit idle before it ends.
workspace_domains array of string The deployment's own Google Workspace domains: their people sign in with Google and never get a password, so an address there is no customer's administrator.

OperatorPasskeyReset

Field Type Required Description
email string yes The address of the person whose passkeys to reset.

OperatorPasskeyResetDone

Field Type Required Description
email string yes The person whose passkeys were reset.
removed integer yes How many passkeys were removed.

OperatorSignedIn

Field Type Required Description
status "signed_in" yes signed_in: the erp_platform_session cookie was set.
email string yes The operator's address.
expires_at string (date-time) yes When the session ends at the latest (ISO 8601, UTC).

OrganizationDetail

One organization as the operator sees it: master data and counts, never the books.

Field Type Required Description
org_id string (uuid) yes Id (uuid) of the organization.
name string yes The organization's name.
status "active" | "suspended" yes active, or suspended: the organization can read but not change anything until it is reactivated.
created_at string (date-time) yes When the organization was created (ISO 8601, UTC).
created_by object yes Who created it, as an actor object (type, id, name): a platform key (platform:<prefix>) or the command line.
status_changed_at string (date-time) or null yes When it was last suspended or reactivated; null if never.
status_reason string or null yes Why it was last suspended or reactivated; null if never.
kind "customer" | "demo" customer: its books are kept for the statutory period and never deleted. demo: made to be looked at. An operator deletes either in the portal (POST /v1/platform/organizations/{org_id}/delete, ADR-0042). (default "customer")
copy_of string (uuid) or null For a test copy (always a demo), the organization it was made from (ADR-0039); null for every other organization.
remove_requested_at string (date-time) or null When its deletion was asked for (POST /v1/platform/organizations/{org_id}/delete, or the removal of a test copy): nobody reaches it from then on, and an operator can undo it until delete_after (ADR-0042). Null otherwise.
delete_after string (date-time) or null When the owner's job may delete it for good: an hour after remove_requested_at. It runs every hour and at every deploy, so the organization is gone within the hour after this. Until then an operator can undo the deletion (POST /v1/platform/organizations/{org_id}/undo-delete). Null when no deletion was asked for.
usage OrganizationUsage or null Counts and last activity; null unless the list was asked with usage=true (each organization is read in a transaction of its own, so it is not free).
entities array of EntitySummary yes Its legal entities.
users array of OrganizationUser yes Its people, by address.
evidence OrganizationEvidence yes The evidence of use.

OrganizationEvidence

The signs of real use that decide whether an organization may be marked as a demo (org_activity, ADR-0022). A non-empty customer_grade means it looks like a customer.

Field Type Required Description
users integer yes People of the organization.
users_not_administrators integer yes People who are not administrators.
users_invited_by_people integer yes People invited by a person or an API key, not by the demo seed, the system or the platform: the initial administrators are not counted.
users_signed_in integer yes People who have signed in at least once.
journal_entries integer yes Journal entries.
journal_entries_not_by_the_seed integer yes Journal entries created by an actor that is neither demo:* nor system:*.
documents integer yes Invoices and bills.
documents_not_by_the_seed integer yes Invoices and bills created by such an actor.
document_dispatches integer yes Invoices handed to a customer.
issued_outputs_archived integer yes Files archived as issued.
bank_statements integer yes Bank statements imported.
first_posting string (date) or null yes Date of the first posted entry; null if none.
last_posting string (date) or null yes Date of the last posted entry; null if none.
customer_grade array of string yes Why it looks like a customer's organization; empty when it looks like a demo.

OrganizationSummary

A row of the operator's list: the lifecycle, and the usage when it was asked for.

Field Type Required Description
org_id string (uuid) yes Id (uuid) of the organization.
name string yes The organization's name.
status "active" | "suspended" yes active, or suspended: the organization can read but not change anything until it is reactivated.
created_at string (date-time) yes When the organization was created (ISO 8601, UTC).
created_by object yes Who created it, as an actor object (type, id, name): a platform key (platform:<prefix>) or the command line.
status_changed_at string (date-time) or null yes When it was last suspended or reactivated; null if never.
status_reason string or null yes Why it was last suspended or reactivated; null if never.
kind "customer" | "demo" customer: its books are kept for the statutory period and never deleted. demo: made to be looked at. An operator deletes either in the portal (POST /v1/platform/organizations/{org_id}/delete, ADR-0042). (default "customer")
copy_of string (uuid) or null For a test copy (always a demo), the organization it was made from (ADR-0039); null for every other organization.
remove_requested_at string (date-time) or null When its deletion was asked for (POST /v1/platform/organizations/{org_id}/delete, or the removal of a test copy): nobody reaches it from then on, and an operator can undo it until delete_after (ADR-0042). Null otherwise.
delete_after string (date-time) or null When the owner's job may delete it for good: an hour after remove_requested_at. It runs every hour and at every deploy, so the organization is gone within the hour after this. Until then an operator can undo the deletion (POST /v1/platform/organizations/{org_id}/undo-delete). Null when no deletion was asked for.
usage OrganizationUsage or null Counts and last activity; null unless the list was asked with usage=true (each organization is read in a transaction of its own, so it is not free).

OrganizationUsage

What an organization has been used for: counts only, never a ledger line or an amount.

Field Type Required Description
entities integer yes Number of legal entities.
users integer yes Number of people (invited, active or disabled).
entries integer yes Number of journal entries, drafts and posted.
documents integer yes Number of invoices, bills and credit notes.
last_activity_at string (date-time) or null yes The last event in the organization's audit trail that a person, a key or the demo seed caused (what the platform does inside it does not count); null if none.
last_sign_in_at string (date-time) or null yes The last sign-in of any of its people; null if nobody ever signed in.

OrganizationUser

Field Type Required Description
id string (uuid) yes Id (uuid) of the user.
email string yes The sign-in address, lower case.
name string or null yes Display name; null until set.
role string yes admin, controller, accountant, auditor or viewer when the scopes are exactly that preset, else custom.
status string yes invited (never signed in), active or disabled.
created_at string (date-time) yes When the user was invited (ISO 8601, UTC).
last_login_at string (date-time) or null yes The last sign-in; null if never.

PlatformAuditOut

Field Type Required Description
id string (uuid) yes Id (uuid) of the record.
at string (date-time) yes When (ISO 8601, UTC).
action string yes What was done: operator.sign_in, operator.sign_in_denied, operator.sign_out, org.provision, org.suspend, org.reactivate, org.mark_demo, org.mark_demo_refused, org.mark_customer, org.delete, org.delete_refused, org.delete_undone, org.purge_demo, org.invite_admin, org.reshow_invite, org.passkey_reset, org.view, platform.choose_models, platform.check_model.
outcome "denied" | "failed" | "ok" | "refused" yes ok, denied (not allowed to), refused (a guard said no) or failed.
actor object yes Who: type, id and name, and email for an operator.
address string or null yes The client address; null for the command line.
org_id string (uuid) or null yes The organization it concerns, if any.
org_name string or null yes Its name when it happened.
detail object yes What else is worth knowing; never a secret.

Problem

Field Type Required Description
section string yes What could not be read, for example slo:erp-api.
reason string yes Why, in a few words (the API's status code or request failed); never a response body.

ProvisionAdmin

Field Type Required Description
email string yes The first administrator's email address. They sign in with a password whose link Google e-mails them when they are invited (when password sign-in is configured), or, for an address of the deployment's Google Workspace domains, with Google; the address is stored in lower case. (pattern ^[^@\s]+@[^@\s]+\.[^@\s]+$)
name string or null Display name; filled from Google at sign-in. (max length 200)

ProvisionEntity

Field Type Required Description
street string or null Street of the entity's address, at most 70 characters. (max length 70)
building_number string or null Building number of the address, at most 16 characters. (max length 16)
postal_code string or null Postal code of the address, at most 16 characters. (max length 16)
town string or null Town of the address, at most 35 characters. (max length 35)
contact_name string or null Seller contact (BT-41).
contact_phone string or null Seller contact phone (BT-42).
contact_email string or null Seller contact email (BT-43).
electronic_address string or null Seller electronic address (BT-34); defaults to contact_email.
electronic_address_scheme string or null EAS code, e.g. EM (email), 9930 (German VAT id), 0204.
template "ch_sme" | "de_sme" | "generic" yes The preset the entity starts from. ch_sme: Switzerland, CHF, monthly periods, VAT by the effective method per quarter. de_sme: Germany, EUR, monthly periods, VAT with the standard method per month and a monthly recapitulative statement. generic: no country or currency; send country and functional_currency. The chart is the standard chart of the entity's country (GET /v1/platform/charts: ch_kmu for CH and LI, de_hgb for DE, fr_pcg for FR, it_civil_code for IT, es_pgc for ES, se_bas for SE, fi_kpl for FI, dk_standard for DK, no_ns4102 for NO, ifrs_generic where a country has none). Every value of a preset can be overridden below.
code string yes Short unique code, 2 to 20 characters of A-Z, 0-9, _ and -, e.g. CH01; it names the entity in every URL. (pattern ^[A-Z0-9_-]{2,20}$)
name string yes Legal name of the entity. (min length 1)
country string or null ISO 3166-1 alpha-2 country code; default: the template's. (pattern ^[A-Z]{2}$)
subdivision string or null ISO 3166-2 code of the state or canton, e.g. DE-BY; null when omitted. (pattern ^[A-Z]{2}-[A-Z0-9]{1,3}$)
functional_currency string or null ISO 4217 code of the currency the books are kept in; default: the template's. Cannot be changed later. (pattern ^[A-Z]{3}$)
chart string or null Chart template id (GET /v1/platform/charts); default: the standard chart of the entity's country.
fiscal_year_start_month integer Month the fiscal year starts, 1 to 12; default 1. (min 1; max 12; default 1)
vat_number string or null The entity's VAT number; null when omitted.
fiscal_years array of integer The fiscal years whose periods are opened at once, e.g. [2026], each 2000 to 2100; default: the one that holds today where the company is, on its fiscal_year_start_month.
fiscal_calendar FiscalCalendar or null How to divide those years into periods; default: monthly.
vat VatSettingsInput or null VAT settings of the entity (method, period); default: the template's. generic has none, so the country's defaults apply until they are saved.

ProvisionInput

Field Type Required Description
name string yes The organization's name, 1 to 200 characters. (min length 1; max length 200)
admin ProvisionAdmin yes The first administrator, who is invited with every scope and signs in with a password or Google. The organization has no API key: it creates its own, bound to this person, after the first sign-in.
users array of ProvisionUser Further people to invite, up to 20. Invite a controller here: new vendors and the people's work need a second person (four eyes), so the administrator cannot complete the onboarding alone. (max items 20)
entities array of ProvisionEntity The legal entities to create, each from a template. Default: none, and the first administrator sets the organization up at their first sign-in (POST /v1/setup): the country, accounting standard, currency, fiscal year and tax, then Production and its Test environment are made (ADR-0041). (max items 20)

ProvisionUser

Field Type Required Description
email string yes The first administrator's email address. They sign in with a password whose link Google e-mails them when they are invited (when password sign-in is configured), or, for an address of the deployment's Google Workspace domains, with Google; the address is stored in lower case. (pattern ^[^@\s]+@[^@\s]+\.[^@\s]+$)
name string or null Display name; filled from Google at sign-in. (max length 200)
role "accountant" | "admin" | "auditor" | "controller" | "viewer" Preset scopes of the user: viewer, accountant, controller (the second person who approves what the administrator creates) or admin. (default "controller")

Provisioned

Field Type Required Description
organization TenantOut yes The new organization, active.
entities array of ProvisionedEntity yes Its entities, with their charts.
users array of ProvisionedUser yes The people invited, the administrator first. Google e-mails each of them the link that sets their password (password_email_sent), except people of the deployment's Google Workspace domains, who sign in with Google.
replayed boolean True when this is the stored answer of an earlier request with the same Idempotency-Key: nothing was created now. (default false)

ProvisionedEntity

Field Type Required Description
code string yes The entity's code.
name string yes Legal name.
country string yes ISO 3166-1 alpha-2 country code.
functional_currency string yes ISO 4217 code of the books' currency.
chart string yes The chart template its accounts were copied from.
role_accounts integer yes Number of accounts with a role: every role the services need is covered.
fiscal_years array of integer yes The fiscal years whose periods were opened.
vat_method string or null yes The VAT method saved for the entity; null when the country's default applies.

ProvisionedUser

Field Type Required Description
id string (uuid) yes Id (uuid) of the user.
email string yes The sign-in address, lower case.
scopes array of string yes The scopes the user holds.
status string yes invited until the person has signed in.
password_email_sent boolean True when Google e-mailed the person the link that sets their password (false when password sign-in is not configured, or for an address of a Google Workspace domain, whose people sign in with Google). (default false)

ServiceHealth

Field Type Required Description
generated_at string (date-time) yes When the figures were read from Cloud Monitoring (ISO 8601, UTC). The endpoint answers from a cache of at most 60 seconds.
status "degraded" | "ok" | "unavailable" yes ok; degraded when some part could not be read (see problems); unavailable when nothing could (monitoring not configured, or no permission).
services array of ServiceOut yes Both services, ERP first.
uptime_checks array of UptimeCheckOut yes The synthetic checks.
problems array of Problem yes Parts that could not be read; empty when ok.

ServiceOut

Field Type Required Description
id string yes The Cloud Run service: erp-api or datavnext-api.
name string yes The service's name for people.
slos array of SloOut yes Its objectives, availability first.
latency_p95_ms_24h map of number or null yes The 95th percentile of the application's response time over the last 24 hours, in milliseconds, by request class: read, report (statements, aging, analytics, FX exposure), write, heavy (PDFs, exports, imports, VAT returns, period close; no objective). Null for a class without requests.
deploy DeployOut or null yes The deploy state; null when Cloud Run could not be read (see problems).

SloOut

One service level objective of a service, over its rolling window.

Field Type Required Description
id string yes The objective's id in Cloud Monitoring, for example availability.
name string yes What it promises, in words.
goal number yes The internal target as a fraction of good events, for example 0.999. The customer-facing commitment is lower on purpose (docs/legal/sla-draft.md).
window_days integer yes The rolling window the compliance is measured over.
compliance number or null yes The fraction of good events in the window so far; null without data.
budget_remaining number or null yes The share of the error budget still unspent: 1 untouched, 0 spent, negative when the objective is missed; null without data.
status "at_risk" | "breached" | "no_data" | "ok" yes ok; at_risk when less than a quarter of the budget is left; breached when the budget is spent; no_data when the window holds no valid events yet.

StatusChange

Field Type Required Description
reason string yes Why the organization is suspended or reactivated, 3 to 500 characters; kept in the organization's audit trail and in the platform's security events. (min length 3; max length 500)

TenantOut

An organization as the platform sees it: its lifecycle, never its books.

Field Type Required Description
org_id string (uuid) yes Id (uuid) of the organization.
name string yes The organization's name.
status "active" | "suspended" yes active, or suspended: the organization can read but not change anything until it is reactivated.
created_at string (date-time) yes When the organization was created (ISO 8601, UTC).
created_by object yes Who created it, as an actor object (type, id, name): a platform key (platform:<prefix>) or the command line.
status_changed_at string (date-time) or null yes When it was last suspended or reactivated; null if never.
status_reason string or null yes Why it was last suspended or reactivated; null if never.
kind "customer" | "demo" customer: its books are kept for the statutory period and never deleted. demo: made to be looked at. An operator deletes either in the portal (POST /v1/platform/organizations/{org_id}/delete, ADR-0042). (default "customer")
copy_of string (uuid) or null For a test copy (always a demo), the organization it was made from (ADR-0039); null for every other organization.
remove_requested_at string (date-time) or null When its deletion was asked for (POST /v1/platform/organizations/{org_id}/delete, or the removal of a test copy): nobody reaches it from then on, and an operator can undo it until delete_after (ADR-0042). Null otherwise.
delete_after string (date-time) or null When the owner's job may delete it for good: an hour after remove_requested_at. It runs every hour and at every deploy, so the organization is gone within the hour after this. Until then an operator can undo the deletion (POST /v1/platform/organizations/{org_id}/undo-delete). Null when no deletion was asked for.

UptimeCheckOut

A synthetic probe from outside Google's network, every five minutes.

Field Type Required Description
name string yes The check's name, for example data-vNext erp.
url string or null yes What it fetches.
status "down" | "no_data" | "up" yes up when at least 80% of the probes of the last 15 minutes passed (one region failing is a blip, as in the alert); down otherwise; no_data.
success_ratio_15m number or null yes Passed probes in the last 15 minutes.
success_ratio_24h number or null yes Passed probes in the last 24 hours.
Stabiso, the Enterprise System of Record · generated from the code and its guides · release 20b7dd5b0c4d