Authentication
Send an API key on every call except the public proof and key endpoints. Keys carry scopes and share one rate limit per tenant.
Put the key in the Authorization header with the Bearer scheme. X-API-Key works only when no Authorization header is sent.
| Format | invoance_live_<8 characters>_<43 characters> |
|---|---|
| Length | 66 characters |
| Storage | Shown once at creation; the server stores only its SHA-256. |
| No key needed | The /v1/proof routes and GET /keys/{domain}. |
| Browsers | The API key routes carry no CORS layer, so browser JavaScript cannot call them cross-origin. |
curl https://api.invoance.com/v1/me \
-H "Authorization: Bearer $INVOANCE_API_KEY"
AuthorizationBearer scheme followed by the API key, for example Authorization: Bearer invoance_live_Qm3xZ9aK_<43 characters>; read first when both headers are present.
X-API-KeyThe raw API key; used only when no Authorization header is present.
Ledger scopes (read, write) and audit scopes (audit:read, audit:write) are separate sets. A key without the scope an endpoint checks gets 403 insufficient_scope.
| read | Get, list and verify events, documents and AI attestations, download a stored original, read the raw attestation bytes, and get, list and download proofs of traces; a key with write passes every read check. |
|---|---|
| write | Ingest events, anchor documents, ingest AI attestations, and create, seal and delete traces. |
audit:read | Get, list and verify audit events, create and fetch exports, list audit organizations and their streams, and read organization integrity; a key with audit:write passes every audit:read check. |
audit:write | Ingest audit events, create, update, archive, unarchive and delete audit organizations, set retention, create, delete and test streams, and create portal sessions. |
Open API & Access
Sign in at app.invoance.com as an owner or admin. In the sidebar open Settings, then API & Access.
Create token
Click Create token. Give it a name, pick its scopes (Read is preselected), and add IP addresses or CIDR ranges if it should only work from known hosts. Empty allows any address.
Copy it now
The token is shown once. A lost token cannot be recovered; revoke it and create another.
Set INVOANCE_API_KEY
Every SDK reads INVOANCE_API_KEY from the environment. Keep it out of source control.
Check it
Call GET /v1/me. It checks no scope, so any active key answers with its organization, scopes and rate limit.
export INVOANCE_API_KEY="invoance_live_Qm3xZ9aK_..."
| Revoke | The next request with that key gets 401 api_key_revoked. |
|---|---|
| Change scopes or IP rules | Applies on the next request. |
| Change plan or rate limit | Waits for the 900 second key cache unless billing clears it. |
| Per second | The plan's rate_limit_per_sec: developer 10, builder 50, growth 200, compliance 500, enterprise 500; or the organization's override when one is set. |
|---|---|
| Per minute | 60 times the per-second figure. |
| Shared by | Every key under the tenant. GET /v1/me reports the figure as limits.rate_limit_per_sec. |
| Monthly quotas | Separate. A used-up quota is 429 quota_exceeded with no Retry-After. |
- Buckets are fixed windows on the wall clock: one Redis counter per second and one per minute, keyed by tenant id, incremented atomically before the handler runs.
- The limiter runs before authentication and looks the key up in the auth cache; a key that is not cached yet (its first request, or the first after a revoke, scope or IP rule change) is counted in one shared anonymous bucket at 10 requests per second, and so is a request with no key.
Retry-AfterSeconds to wait before retrying: 1 when the per-second bucket is full, the seconds left in the current minute when the minute bucket is full.
X-RateLimit-LimitThe per-second limit that applied to the request; sent only on a 429.
X-RateLimit-RemainingAlways 0 on a 429; the header is not sent on successful responses.
Send an Idempotency-Key header on POST /v1/events, POST /v1/document/anchor and POST /v1/ai/attestations (optional) and on POST /v1/audit/events (required); a retry with the same key and the same request gets the stored status and body back instead of creating a second record.
Idempotency-KeyCaller-chosen unique string, for example a UUID; stored per tenant, API key and endpoint, so the same value on another key or endpoint is a different key.
| Kept for | 24 hours |
|---|
- The same key with a different request hash returns 409 idempotency_key_reuse_mismatch with message "idempotency key was already used with a different request body".
- A retry that arrives while the first request is still in flight gets 202 {"status":"processing"}; that reservation lives 120 seconds if the first request never finishes, and is deleted at once when the first request fails with an error, so the next retry is processed again.
- POST /v1/audit/events requires the header (400 missing_idempotency_key when absent, 400 invalid_idempotency_key over 255 characters) and folds the audit organization into the key.
- The in-process cache keys entries by tenant, API key and value only, so reusing one Idempotency-Key value on two different endpoints from the same key can be answered with 409 idempotency_key_reuse_mismatch; use a fresh value per request.
| Code | Status | When |
|---|---|---|
| missing_api_key | 401 | Neither an Authorization header nor an X-API-Key header was sent. |
| invalid_authorization_scheme | 401 | An Authorization header is present but does not start with Bearer. |
| invalid_api_key_format | 401 | The key does not start with invoance_live_. |
| invalid_api_key | 401 | No key with that value exists, or GET /v1/me found no row for the authenticated key. |
| api_key_revoked | 401 | The key exists but has been revoked. |
| ip_not_allowed | 403 | The key has IP rules and the request came from an address that is not listed. |
| insufficient_scope | 403 | The key lacks the scope the endpoint checks: read, write, audit:read or audit:write. |
/v1/meIntrospect the API key
Returns the organization, tenant, key metadata and effective rate limit behind the calling key.
- No scope is checked, so a key limited to audit:read or audit:write can call it; every SDK's validate() probes this endpoint and treats a 403 or 429 as proof that the key authenticated.
- The response is sent with Cache-Control: no-store.
- Sibling keys, dashboard users, billing details and the key's IP rules are not returned.
- created_at and last_used_at are RFC 3339 as chrono writes them: a +00:00 offset rather than a trailing Z, with fractional seconds when the stored time has them.
- Node returns a typed MeResponse; Python, Ruby and PHP return the decoded JSON as a dict, Hash or array; Go returns map[string]any; Java returns Map<String, Object>; Rust returns serde_json::Value; .NET returns a JsonNode.
- The .NET README says ValidateAsync probes GET /v1/events?limit=1; the code probes GET /v1/me like the other SDKs.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const me = await client.me();
console.log(me.organization.primary_domain, me.organization.plan_tier);
console.log(me.api_key.scopes, me.limits.rate_limit_per_sec);
{
"valid": true,
"organization": {
"id": "9b2f6c41-3d7e-4a58-8c0b-1e5f7a29d6c3",
"name": "Acme Corp",
"issuer_name": "Acme Corp",
"primary_domain": "acme.com",
"domain_verified": true,
"plan_tier": "growth"
},
"tenant": {
"id": "4e8a1d27-6b3c-4f9e-a2d5-7c0b9e3f1a68",
"name": "Acme Corp"
},
"api_key": {
"id": "c7d3e9f1-2a4b-4c6d-8e0f-1b3d5f7a9c2e",
"name": "production",
"key_prefix": "invoance_live_Qm3xZ9aK",
"key_last4": "7hT2",
"scopes": [
"read",
"write"
],
"created_at": "2026-05-14T09:30:12Z",
"last_used_at": "2026-09-22T08:14:07Z"
},
"limits": {
"rate_limit_per_sec": 200
}
}
validAlways true on a 200; the SDK validate() helpers map it directly.
organizationThe organization the key belongs to.
organization.idOrganization id.
organization.nameOrganization display name.
organization.issuer_nameIssuer name shown on proof pages.
organization.primary_domainPrimary domain, the value GET /keys/{domain} resolves.
organization.domain_verifiedTrue when domain ownership has been verified.
organization.plan_tierPlan tier as stored. One of developer, builder, growth, compliance, enterprise.
tenantThe tenant that owns the signing key, the API keys and the rate limit budget.
tenant.idTenant id.
tenant.nameTenant name.
api_keyMetadata of the calling key.
api_key.idId of the calling key.
api_key.nameKey name given at creation; null when the stored name is empty.
api_key.key_prefixinvoance_live_ plus the first 8 characters of the secret.
api_key.key_last4Last 4 characters of the key.
api_key.scopesScopes exactly as stored. One of read, write, audit:read, audit:write.
api_key.created_atWhen the key was created.
api_key.last_used_atLast use recorded before this call; omitted when the key was never used.
limitsEffective limits for the key.
limits.rate_limit_per_secRequests per second the tenant may make: the organization override when set, otherwise the plan value, at least 1.
missing_api_keyNo Authorization header and no X-API-Key header was sent.
invalid_authorization_schemeThe Authorization header does not use the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not exist, or no row was found for it.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has IP rules and the request came from another address.
rate_limitedThe tenant's per-second or per-minute budget is used up; Retry-After is set.
api_key_lookup_failedThe key lookup query failed.
api_key_requiredThe authenticated context carries no API key id.
db_errorThe introspection query failed.