Audit organizations
One audit org per end customer: create it, list it, archive it, set its retention and check its sequence for gaps.
/v1/audit/orgsCreate an audit orgGET/v1/audit/orgsList audit orgsPATCH/v1/audit/orgs/{id}Rename an audit orgDELETE/v1/audit/orgs/{id}Delete an audit orgPOST/v1/audit/orgs/{id}/archiveArchive an audit orgPOST/v1/audit/orgs/{id}/unarchiveUnarchive an audit orgGET/v1/audit/orgs/{id}/integrityCheck an org's sequence integrityPUT/v1/audit/orgs/{id}/retentionSet an org's retention/v1/audit/orgsCreate an audit org
Registers one end-customer org under your own organization_id and returns its aorg_ id and starting retention.
Content-TypeMust be application/json.
organization_idYour own id for the end customer; unique per tenant and used in every later call. external_id is accepted as a legacy alias.
nameDisplay name for the org; trimmed before storage.
- The org and its seq counter are created in one transaction; ingest for the org works as soon as the 201 returns.
- Unknown body keys and wrong types are rejected by the framework before the handler runs, with a plain-text 4xx rather than a JSON error body.
- The org count is read before the insert, so two concurrent creates at the cap can both succeed.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const org = await client.audit.orgs.create({
organizationId: "org_8472",
name: "Acme Robotics",
});
console.log(org.id, org.retention_days);
{
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"organization_id": "org_8472",
"external_id": "org_8472",
"name": "Acme Robotics",
"retention_days": 90,
"archived_at": null
}
idInternal org id, aorg_ followed by a ULID; accepted wherever an org id is expected.
organization_idYour own id for the end customer, as given at create time.
external_idSame value as organization_id, kept for older clients.
nameDisplay name, or null when none is set.
retention_daysStarting retention: 90 days, or the plan's retention cap when that is lower.
archived_atWhen the org was archived, or null while it is active.
invalid_organization_idorganization_id is empty after trimming or longer than 255 characters.
invalid_namename is longer than 255 characters.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
too_many_orgsThe tenant already has as many audit orgs as its plan allows.
org_existsAn org with this organization_id already exists for the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgsList audit orgs
Returns up to 100 of the tenant's audit orgs, newest first, hiding archived orgs unless asked.
include_archivedSet to true to include orgs whose archived_at is set.
- There is no pagination; the list is capped at 100 rows.
- The Python SDK sends include_archived=true only when the flag is set; the Go SDK omits the parameter entirely when false.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.orgs.list({ includeArchived: true });
for (const org of result.orgs as Array<Record<string, unknown>>) {
console.log(org.id, org.organization_id, org.archived_at);
}
{
"orgs": [
{
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"organization_id": "org_8472",
"external_id": "org_8472",
"name": "Acme Robotics",
"retention_days": 90,
"created_at": "2026-09-22T08:10:00.418212+00:00",
"archived_at": null
}
]
}
orgsThe orgs, ordered by created_at descending.
orgs[].idInternal org id, aorg_ followed by a ULID; accepted wherever an org id is expected.
orgs[].organization_idYour own id for the end customer, as given at create time.
orgs[].external_idSame value as organization_id, kept for older clients.
orgs[].nameDisplay name, or null when none is set.
orgs[].retention_daysHow many days the org's events are kept (hot plus cold) before they are purged.
orgs[].created_atWhen the org was created, written with a +00:00 offset.
orgs[].archived_atWhen the org was archived, or null while it is active.
insufficient_scopeThe key has neither audit:read nor audit:write; a ledger key with only read or write is rejected on audit routes.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}Rename an audit org
Sets, changes or clears the org's display name and returns the updated org.
Content-TypeMust be application/json.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
nameThe new display name; JSON null, an empty string or whitespace clears it. The key must be present.
- The name is display only; events reference the org by id, so renaming never touches signed data.
- Renaming works on archived orgs too.
- The Java SDK's builder refuses to build unless name(...) or clearName() was called, and clearName() sends name null; the .NET SDK always sends the name key, so a null Name clears it.
- The verb is PATCH; a PUT to this path is not routed.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const org = await client.audit.orgs.update("org_8472", { name: "Acme Robotics Ltd" });
console.log(org.name);
// Pass null to clear the name.
await client.audit.orgs.update("org_8472", { name: null });
{
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"organization_id": "org_8472",
"external_id": "org_8472",
"name": "Acme Robotics Ltd",
"retention_days": 90,
"created_at": "2026-09-22T08:10:00.418212+00:00",
"archived_at": null
}
idInternal org id, aorg_ followed by a ULID; accepted wherever an org id is expected.
organization_idYour own id for the end customer, as given at create time.
external_idSame value as organization_id, kept for older clients.
nameDisplay name, or null when none is set.
retention_daysHow many days the org's events are kept (hot plus cold) before they are purged.
created_atWhen the org was created, written with a +00:00 offset.
archived_atWhen the org was archived, or null while it is active.
nothing_to_updateThe body has no name key.
invalid_namename is longer than 255 characters.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}Delete an audit org
Removes an org only when doing so destroys no signed history, and returns the deleted id.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
- Deletion is allowed in two cases: the org never had an event signed (seq counter at 0 and no accepted event in the last 24 hours), or it is archived and retention has purged every row and cold segment.
- For an org with history the path is archive, wait for retention_days to elapse, then delete; the three 409 messages say which condition blocks the call.
- Streams, portal sessions and export rows of the org are removed with it, and the export files are deleted from storage afterwards on a best-effort basis.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.orgs.delete("org_8472");
console.log(result.deleted, result.id);
{
"deleted": true,
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM"
}
deletedAlways true on success.
idThe aorg_ id that was deleted.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
org_not_deletableThe org has signed history and is not archived; it is archived but live rows or cold segments remain; or an event was accepted for it within the last 24 hours and may still be queued for signing.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/archiveArchive an audit org
Marks the org archived so it accepts no new events, streams, portal links or exports, while its history stays readable and verifiable.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
- Archiving is idempotent; a second call keeps the original archived_at.
- While archived: ingest returns 409 org_archived, stream create and test return 409, portal sessions and exports return 409; list, get, verify, integrity, rename and retention keep working.
- A portal session minted just before archiving stays valid until it expires; the viewer sees archived_at on GET /v1/audit/portal/org.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const org = await client.audit.orgs.archive("org_8472");
console.log(org.archived_at);
{
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"organization_id": "org_8472",
"external_id": "org_8472",
"name": "Acme Robotics",
"retention_days": 90,
"created_at": "2026-09-22T08:10:00.418212+00:00",
"archived_at": "2026-09-22T09:02:11.507314+00:00"
}
idInternal org id, aorg_ followed by a ULID; accepted wherever an org id is expected.
organization_idYour own id for the end customer, as given at create time.
external_idSame value as organization_id, kept for older clients.
nameDisplay name, or null when none is set.
retention_daysHow many days the org's events are kept (hot plus cold) before they are purged.
created_atWhen the org was created, written with a +00:00 offset.
archived_atWhen the org was archived, or null while it is active.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/unarchiveUnarchive an audit org
Clears archived_at so the org accepts writes again, and returns the org.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
- Unarchiving is idempotent; calling it on an active org returns the org unchanged.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const org = await client.audit.orgs.unarchive("org_8472");
console.log(org.archived_at);
{
"id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"organization_id": "org_8472",
"external_id": "org_8472",
"name": "Acme Robotics",
"retention_days": 90,
"created_at": "2026-09-22T08:10:00.418212+00:00",
"archived_at": null
}
idInternal org id, aorg_ followed by a ULID; accepted wherever an org id is expected.
organization_idYour own id for the end customer, as given at create time.
external_idSame value as organization_id, kept for older clients.
nameDisplay name, or null when none is set.
retention_daysHow many days the org's events are kept (hot plus cold) before they are purged.
created_atWhen the org was created, written with a +00:00 offset.
archived_atWhen the org was archived, or null while it is active.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/integrityCheck an org's sequence integrity
Scans the org's hot log for missing seq values between the retention floor and the high-water mark and reports every gap.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
fromFirst seq to check; raised to 1 and to evacuated_through_seq + 1 when lower.
toLast seq to check; lowered to last_seq when higher.
- seq is assigned in commit order with no gaps, so a missing value between the floor and last_seq means a row was removed from the hot log.
- The scan cannot detect removal of the newest rows if the org's counter was also rolled back; the note field says so on every response.
- When last_seq is 0 or the clamped range is empty, the response has count 0, expected 0, contiguous true, an empty gaps array and no gaps_truncated key.
- The SDKs call this without from or to; pass them with a raw HTTP call when you need a sub-range.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const report = await client.audit.orgs.integrity("org_8472");
console.log(report.contiguous, report.count, report.expected, report.gaps);
{
"org_id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"from": 1,
"to": 42,
"last_seq": 42,
"evacuated_through_seq": 0,
"count": 42,
"expected": 42,
"contiguous": true,
"gaps": [],
"gaps_truncated": false,
"note": "A hole below the high-water mark proves deletion. An attacker who also resets the per-org seq counter can hide truncation of the newest events; only the Tier-3 signed checkpoint closes that. Integrity is 'Signed', not 'complete', until it ships."
}
org_idThe aorg_ id that was scanned.
fromFirst seq actually checked, after clamping.
toLast seq actually checked, after clamping.
last_seqThe org's high-water mark: the last seq the signer assigned.
evacuated_through_seqThe retention floor; seq values at or below it were moved to cold storage or purged and are not counted as gaps.
countRows found in the checked range.
expectedto - from + 1, the number of rows a complete range would hold.
contiguousTrue when gaps is empty.
gapsMissing ranges in ascending order.
gaps[].startFirst missing seq of the range.
gaps[].endLast missing seq of the range.
gaps[].lengthend - start + 1.
gaps_truncatedTrue when 1,000 gap ranges were found and the scan stopped; omitted when the range was empty.
noteFixed text describing what the scan can and cannot detect.
insufficient_scopeThe key has neither audit:read nor audit:write; a ledger key with only read or write is rejected on audit routes.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/retentionSet an org's retention
Sets how many days the org's events are kept before purge, clamped to the plan's cap, and reports the value applied.
Content-TypeMust be application/json.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
daysRequested retention in days, hot and cold storage combined.
- Retention counts from ingested_at; rows older than the plan's hot window move to cold storage, and rows older than retention_days are purged by a nightly worker.
- The hot window is set by the plan and cannot be changed here.
- Lowering retention below the age of existing rows lets the next retention run purge them.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.orgs.setRetention("org_8472", 365);
console.log(result.retention_days, result.clamped, result.plan_cap_days);
{
"org_id": "aorg_01J0XW9K3RQ5T7V8Y2C4E6G8HM",
"retention_days": 365,
"requested_days": 365,
"clamped": false,
"plan_cap_days": 365
}
org_idThe aorg_ id that was updated.
retention_daysThe value stored: min(days, plan_cap_days).
requested_daysThe days value you sent.
clampedTrue when retention_days is lower than requested_days.
plan_cap_daysThe tenant's cap: a per-tenant override when set, otherwise the plan's retention_days; 30 when neither can be read.
invalid_daysdays is less than 1.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/eventsIngest an audit eventGET/v1/audit/eventsList audit eventsGET/v1/audit/events/{id}Get an audit eventGET/v1/audit/events/{id}/verifyVerify an audit event/v1/audit/orgs/{id}/streamsCreate a webhook streamGET/v1/audit/orgs/{id}/streamsList an org's streamsDELETE/v1/audit/orgs/{id}/streams/{stream_id}Delete a streamPOST/v1/audit/orgs/{id}/streams/{stream_id}/testSend a test delivery/v1/audit/portal/exchangeExchange a portal link tokenGET/v1/audit/portal/eventsList events through the portalGET/v1/audit/portal/events/{id}Get an event through the portalGET/v1/audit/portal/events/{id}/verifyVerify an event through the portalGET/v1/audit/portal/orgGet the portal's org and issuerGET/v1/audit/portal/streamsList streams through the portalPOST/v1/audit/portal/streamsCreate a stream through the portalDELETE/v1/audit/portal/streams/{id}Delete a stream through the portalPOST/v1/audit/portal/streams/{id}/testTest a stream through the portal