Test copies
A test organization with the organization's setup (companies, chart of accounts, customers, vendors, products, people) and empty books, to try things out without touching the real ones; removing one; and sample data for a test organization.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
POST /v1/entities/{entity}/sample-data |
Add sample data to a test organization | admin |
required |
GET /v1/test-copies |
List the test copies of the organization | admin |
|
POST /v1/test-copies |
Make a test copy of the organization | admin |
required |
DELETE /v1/test-copies/{copy_id} |
Remove a test copy | admin |
|
POST /v1/test-copies/{copy_id}/refresh |
Refresh a test copy from the organization | admin |
required |
PUT /v1/test-copies/{copy_id}/schedule |
Set when a test copy is refreshed by itself | admin |
POST /v1/entities/{entity}/sample-data
Add sample data to a test organization
Book a month of sample business into an entity of a test organization (a test copy or
a demo), to have something to look at and try things on: three sample customers and two
sample vendors (created once, codes SAMPLE-...), four sales invoices and two bills dated
in the current month up to today, the first customer's payment of the first invoice and the
payment of the first bill. Everything goes through the ordinary services, with the entity's
own VAT and accounts, booked as the system actor sample. Asking again adds another set.
Refused with 409 in an organization that is not a test organization (sample data never goes
into real books) or when today's period is closed; 403 for an agent. Needs the admin
scope and an Idempotency-Key. Recorded in the audit trail (sample_data.add).
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | add_sample_data_v1_entities__entity__sample_data_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | entity |
string | yes | Code (or id) of the entity, e.g. CH01. |
Example request
curl -X POST "$ERP_URL/v1/entities/{entity}/sample-data" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)"
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | SampleDataOut |
| 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)
{
"entity": "string",
"customers": 1,
"vendors": 1,
"invoices": 1,
"bills": 1,
"payments": 1
}
GET /v1/test-copies
List the test copies of the organization
The test organizations made from this one, oldest first. Needs the admin scope.
| Required scope | admin (admin implies every scope) |
| Operation id | list_test_copies_v1_test_copies_get |
Example request
curl "$ERP_URL/v1/test-copies" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | array of CopyOut |
| 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",
"created_at": "2026-09-21T09:30:00Z",
"created_by": {},
"masked": true,
"cloned_at": "2026-09-21T09:30:00Z",
"refresh_schedule": "off",
"refresh_problem": "refresh_failed",
"refresh_problem_at": "2026-09-21T09:30:00Z",
"remove_requested_at": "2026-09-21T09:30:00Z"
}
]
POST /v1/test-copies
Make a test copy of the organization
Make a new test organization that is a clone of this one, to try things out or work on without touching the real books: everything of the organization comes across exactly as it is, under new ids: its companies (entities), fiscal years and periods, the chart of accounts, settings, customers, vendors, products, contracts and subscriptions, every journal entry, invoice, bill, payment, asset, bank statement, VAT return, stored document, plan and close, the audit trail, and its people with their roles (as invited: they sign in to the copy with their usual sign-in). The copy's books are proven to be the organization's own before the answer is given: every hash chain verifies and every account of every company has the same balance in every currency (409 otherwise, and nothing is made). API keys, sessions, the security log and exports stay behind.
With masked, who the business partners are does not come across: the names, e-mails,
e-invoicing addresses, streets and numbers, IBANs and contacts of customers, vendors and
prospects, the counterparties of bank lines and the addresses documents were sent to become
mock values (Customer 001, contact-001@example.com), the same real value always the same
mock, wherever they appear in free text (descriptions, notes, payment files, the audit trail);
notes about them, remittances and the descriptions of journal entries typed by hand or booked
from a bank line (a salary naming its employee) become (masked), wherever they were copied,
and stored files a placeholder; the audit trail and approvals keep only what happened
(amounts, codes, dates, statuses), not about whom, and a change of a partner's details still
waiting arrives rejected. Nothing else changes: codes, references, currencies and actions keep
their values. The organization's own setup (companies, bank accounts, chart of
accounts, products, settings, including a partner's name it holds, such as an account named
after a customer), its intercompany partners, partners' codes, amounts, dates, tax data and the
people who sign in stay exactly as they are, so the books are still proven to be the
organization's own. For testing with
people who should not see the real customers and vendors.
Each person of the organization finds the test organization in the list of organizations
when signing in, labelled as a test. Nothing leaves a test copy: no e-mail to a customer, no
payment file and no e-invoice (its customers, vendors and banks are the real ones); remove it
with DELETE /v1/test-copies/{copy_id} when it is no longer needed. A test organization
cannot itself be copied (409), and an organization has at most 5 test copies at a time (409).
Needs the admin scope and a person: an agent key is refused (403). Recorded in the audit
trail of both organizations (org.test_copy).
A retry with the same Idempotency-Key returns the first answer and makes nothing more.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | make_test_copy_v1_test_copies_post |
Request body
application/json (required): CopyRequest
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | The name of the test organization, up to 200 characters. Default: the organization's name followed by (test). (max length 200) |
|
masked |
boolean | Mask the business partners: the names, e-mails, streets, IBANs and contacts of customers, vendors and prospects become mock values everywhere in the copy, and its stored files placeholders. Amounts, setup and the books stay the same. (default false) |
Example request
curl -X POST "$ERP_URL/v1/test-copies" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"masked": true,
"name": "Muster AG (UAT)"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 201 | Created | CopyOut |
| 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)
{
"org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"created_at": "2026-09-21T09:30:00Z",
"created_by": {},
"masked": true,
"cloned_at": "2026-09-21T09:30:00Z",
"refresh_schedule": "off",
"refresh_problem": "refresh_failed",
"refresh_problem_at": "2026-09-21T09:30:00Z",
"remove_requested_at": "2026-09-21T09:30:00Z"
}
DELETE /v1/test-copies/{copy_id}
Remove a test copy
Remove a test organization made from this one, with everything in it: its books, its
setup and its people's access to it. The answer is 202 with remove_requested_at set: from
then on nobody can sign in to the copy or change anything in it. It is deleted for good
between one and two hours later (one copy after the other, the one asked for first), and
then leaves GET /v1/test-copies; it no longer counts towards the 5 copies. Only test
copies of this organization can be removed (404 for any other id); a real organization
never can. Asking again changes nothing. Needs the admin scope and a person: an agent key
is refused (403). Recorded in the audit trail (org.test_copy_remove) and, when it is
deleted, in the platform's audit.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | remove_test_copy_v1_test_copies__copy_id__delete |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | copy_id |
string (uuid) | yes | Id (uuid) of the test copy. |
Example request
curl -X DELETE "$ERP_URL/v1/test-copies/{copy_id}" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 202 | Successful Response | CopyOut |
| 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",
"created_at": "2026-09-21T09:30:00Z",
"created_by": {},
"masked": true,
"cloned_at": "2026-09-21T09:30:00Z",
"refresh_schedule": "off",
"refresh_problem": "refresh_failed",
"refresh_problem_at": "2026-09-21T09:30:00Z",
"remove_requested_at": "2026-09-21T09:30:00Z"
}
POST /v1/test-copies/{copy_id}/refresh
Refresh a test copy from the organization
Clone this organization again into one of its test copies, as it is now: everything in
the copy is replaced, and whatever was done in it is lost (entries, documents, settings,
its audit trail; only its log of security events stays). The copy keeps its id and name, so
its people find it where it was; they are signed out of it. Its books are proven to be this
organization's own, as when a copy is made (409 otherwise, and the copy stays as it was). A
masked copy is masked again. cloned_at tells when it happened.
404 for anything that is not a test copy of this organization, 409 for a copy whose removal
was asked for. Needs the admin scope and a person: an agent key is refused (403). Recorded in
the audit trail of both organizations (org.test_copy_refresh). A retry with the same
Idempotency-Key returns the first answer and refreshes nothing more.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | required |
| Operation id | refresh_test_copy_v1_test_copies__copy_id__refresh_post |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | copy_id |
string (uuid) | yes | Id (uuid) of the test copy. |
Example request
curl -X POST "$ERP_URL/v1/test-copies/{copy_id}/refresh" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Idempotency-Key: $(uuidgen)"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CopyOut |
| 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",
"created_at": "2026-09-21T09:30:00Z",
"created_by": {},
"masked": true,
"cloned_at": "2026-09-21T09:30:00Z",
"refresh_schedule": "off",
"refresh_problem": "refresh_failed",
"refresh_problem_at": "2026-09-21T09:30:00Z",
"remove_requested_at": "2026-09-21T09:30:00Z"
}
PUT /v1/test-copies/{copy_id}/schedule
Set when a test copy is refreshed by itself
Have the copy refreshed by itself, as POST /v1/test-copies/{copy_id}/refresh does:
nightly refreshes it every night, weekly once a week (a week after it was last filled),
off stops it. Every refresh replaces everything in the copy, so whatever was done in it
since is lost; a copy that cannot be refreshed stays as it was until the next night. The
refreshes are recorded in the audit trail of both organizations (org.test_copy_refresh),
with who set the schedule. 404 and 409 as for a refresh. Needs the admin scope and a
person: an agent key is refused (403). Recorded in the audit trail
(org.test_copy_schedule); setting the schedule it has changes nothing.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | schedule_test_copy_v1_test_copies__copy_id__schedule_put |
Parameters
| In | Name | Type | Required | Description |
|---|---|---|---|---|
| path | copy_id |
string (uuid) | yes | Id (uuid) of the test copy. |
Request body
application/json (required): ScheduleRequest
| Field | Type | Required | Description |
|---|---|---|---|
refresh_schedule |
"nightly" | "off" | "weekly" |
yes | nightly: the copy is refreshed every night; weekly: once a week; off: only when someone refreshes it. |
Example request
curl -X PUT "$ERP_URL/v1/test-copies/{copy_id}/schedule" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"refresh_schedule": "nightly"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CopyOut |
| 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",
"created_at": "2026-09-21T09:30:00Z",
"created_by": {},
"masked": true,
"cloned_at": "2026-09-21T09:30:00Z",
"refresh_schedule": "off",
"refresh_problem": "refresh_failed",
"refresh_problem_at": "2026-09-21T09:30:00Z",
"remove_requested_at": "2026-09-21T09:30:00Z"
}
Schemas
CopyRequest
| Field | Type | Required | Description |
|---|---|---|---|
name |
string or null | The name of the test organization, up to 200 characters. Default: the organization's name followed by (test). (max length 200) |
|
masked |
boolean | Mask the business partners: the names, e-mails, streets, IBANs and contacts of customers, vendors and prospects become mock values everywhere in the copy, and its stored files placeholders. Amounts, setup and the books stay the same. (default false) |
SampleDataOut
What POST /v1/entities/{entity}/sample-data booked.
| Field | Type | Required | Description |
|---|---|---|---|
entity |
string | yes | The entity's code. |
customers |
integer | yes | Sample customers created (0 when they existed already). |
vendors |
integer | yes | Sample vendors created (0 when they existed already). |
invoices |
integer | yes | Sales invoices posted. |
bills |
integer | yes | Vendor bills posted. |
payments |
integer | yes | Payments booked: the first invoice's and the first bill's. |
ScheduleRequest
| Field | Type | Required | Description |
|---|---|---|---|
refresh_schedule |
"nightly" | "off" | "weekly" |
yes | nightly: the copy is refreshed every night; weekly: once a week; off: only when someone refreshes it. |