BurnLedger API (1.0.0)

Download OpenAPI specification:

BurnLedger Support: support@burnledger.io

BurnLedger produces cryptographic verification records that prove a data subject's records existed at attestation time and were absent at verification time. Certificates are signed with Ed25519 and appended to an RFC 6962 Merkle transparency log.

Authentication

Customer API — all /v1/ endpoints except public paths require a Bearer token:

Authorization: Bearer dp_<hex>

Public endpoints/health, /healthz, /ready, /.well-known/burnledger-keys, /v1/certificates/{id}/status, /v1/enclave/attestation, /v1/contact, and all /v1/log/ paths require no authentication.

Request and rate limits

Every authenticated /v1/ route is rate limited per API key, and the public routes per client IP. Each response, admitted or refused, carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a refusal is 429 with code RATE_LIMITED and a Retry-After header.

Request bodies are capped (1 MB by default). A larger body, whether declared by Content-Length or discovered while reading, is refused with 413 and code PAYLOAD_TOO_LARGE.

Both are middleware behaviour and apply to every operation below even where the operation does not list 413 or 429 explicitly. The 413 is listed on the operations whose bodies a customer builds programmatically and can plausibly grow past the cap.

Idempotency

Any authenticated POST may carry an Idempotency-Key header (up to 256 characters, unique per key holder and team). The first request with a key runs the handler and stores its response for 24 hours; a later request with the same key, path and body gets that stored response back, with Idempotent-Replayed: true, and runs nothing. Both SDKs send a fresh UUID key on every POST and reuse it across their own retries.

  • A second request that arrives while the first is still running is refused at once with 409 and code IDEMPOTENCY_IN_FLIGHT; it is not queued. Retry after a short delay.
  • Reusing a key with a different path or body is 422 with code IDEMPOTENCY_KEY_REUSE.
  • A 5xx or 429 is never stored: the key is released so a retry runs the handler again.
  • A handler that panics, or is cut off by the request timeout before it answers, leaves the key reserved: every retry answers 409 until the 24-hour TTL lapses, after which a fresh attempt runs. This is deliberate. The middleware cannot tell whether the handler already committed a verification record or a quota decrement, so a wedge that clears itself is preferred to a silent duplicate issuance. Fall back to a new key only when you have confirmed (for example by listing attestations) that the first attempt left nothing behind.

Systems

Register and manage external data sources

Register a system

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string <= 120 characters

Human-readable system name

connector_type
required
string
Enum: "postgresql" "mongodb" "s3" "redis" "elasticsearch" "mysql" "dynamodb" "bigquery" "snowflake" "cassandra" "azure_blob" "gcs" "sqlserver" "oracle" "redshift" "teradata" "databricks" "hbase" "marklogic" "neo4j"
connection_config
object

Connector-specific configuration, submitted in the clear and encrypted at rest by the enclave. This crosses the BurnLedger host process, which holds the plaintext for the length of the request; use sealed_connection_config to avoid that.

sealed_connection_config
string <byte>

The same configuration, already sealed to the enclave's X25519 config-seal key, which the SDKs take from a verified Nitro attestation document. The host relays these bytes and cannot open them. Base64.

When this is used the host cannot run its own SSRF pre-check or transport classification; both run inside the enclave instead, and the transport level reported on the system record is the enclave's measurement.

subject_query
required
string

Query template with $1 (SQL) or $IDENTIFIER / {identifier} placeholder for the subject identifier

hash_scope
string
Default: "existence"
Enum: "full" "existence"
phi_in_scope
boolean
Default: false

Set this when the system holds data you treat as PHI. It changes the default proof mode for attestations that include this system from count to merkle. That is not a per-record proof: merkle mode adds a signed root over hashed records to the verification record, and no inclusion proof against that root is served (see proof_mode on the attestation request). An explicit proof_mode on the attestation always wins.

max_records
integer <int64>
Default: 1000000
max_bytes
integer <int64>
Default: 10737418240
query_timeout
string
Default: "30s"

Go duration string. Must be positive and at most 5m; anything longer is refused with 400, since a connector query is held open for the whole timeout.

Responses

Request samples

Content type
application/json
{
  • "name": "Production Users DB",
  • "connector_type": "postgresql",
  • "connection_config": {
    },
  • "sealed_connection_config": "string",
  • "subject_query": "SELECT id, email FROM users WHERE email = $1",
  • "hash_scope": "full",
  • "phi_in_scope": false,
  • "max_records": 1000000,
  • "max_bytes": 10737418240,
  • "query_timeout": "30s"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List systems

Authorizations:
bearerAuth
query Parameters
health
string
Enum: "healthy" "unhealthy" "unknown"

Return only systems in this health state. Matched case-insensitively, so the health_status value a system was served with (UNHEALTHY) works as well as the lowercase spelling. Any other value is a 400 rather than an ignored filter. unhealthy_count in the response is the team-wide total regardless of this filter.

cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string",
  • "unhealthy_count": 0
}

Test a system connection without registering it

Runs every admission gate a registration would run — DSN parsing, transport-security floor, connectivity, read-only enforcement — and persists nothing. Answers 200 even when a gate failed: the request succeeded in telling the caller what is wrong, and a non-2xx would make "your password is wrong" indistinguishable from "the test could not run". Check ok and walk steps.

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string <= 120 characters

Human-readable system name

connector_type
required
string
Enum: "postgresql" "mongodb" "s3" "redis" "elasticsearch" "mysql" "dynamodb" "bigquery" "snowflake" "cassandra" "azure_blob" "gcs" "sqlserver" "oracle" "redshift" "teradata" "databricks" "hbase" "marklogic" "neo4j"
connection_config
object

Connector-specific configuration, submitted in the clear and encrypted at rest by the enclave. This crosses the BurnLedger host process, which holds the plaintext for the length of the request; use sealed_connection_config to avoid that.

sealed_connection_config
string <byte>

The same configuration, already sealed to the enclave's X25519 config-seal key, which the SDKs take from a verified Nitro attestation document. The host relays these bytes and cannot open them. Base64.

When this is used the host cannot run its own SSRF pre-check or transport classification; both run inside the enclave instead, and the transport level reported on the system record is the enclave's measurement.

subject_query
required
string

Query template with $1 (SQL) or $IDENTIFIER / {identifier} placeholder for the subject identifier

hash_scope
string
Default: "existence"
Enum: "full" "existence"
phi_in_scope
boolean
Default: false

Set this when the system holds data you treat as PHI. It changes the default proof mode for attestations that include this system from count to merkle. That is not a per-record proof: merkle mode adds a signed root over hashed records to the verification record, and no inclusion proof against that root is served (see proof_mode on the attestation request). An explicit proof_mode on the attestation always wins.

max_records
integer <int64>
Default: 1000000
max_bytes
integer <int64>
Default: 10737418240
query_timeout
string
Default: "30s"

Go duration string. Must be positive and at most 5m; anything longer is refused with 400, since a connector query is held open for the whole timeout.

Responses

Request samples

Content type
application/json
{
  • "name": "Production Users DB",
  • "connector_type": "postgresql",
  • "connection_config": {
    },
  • "sealed_connection_config": "string",
  • "subject_query": "SELECT id, email FROM users WHERE email = $1",
  • "hash_scope": "full",
  • "phi_in_scope": false,
  • "max_records": 1000000,
  • "max_bytes": 10737418240,
  • "query_timeout": "30s"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a system

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Deregister a system

Removes the system from the team: it stops being listed, health checks stop, its name and MaxSystems slot are freed, and it can no longer be attested against. A system that is referenced by attestations is retired rather than erased, so the records that name it stay verifiable; the caller sees no difference, and either way the system answers 404 afterwards.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Get system health status

Returns the stored result of the last health check without probing the system. The body is the health fields alone, not the full System.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Trigger an on-demand health check

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Attestations

Create and verify deletion attestations

Create an attestation

Queries all specified systems for the subject identifier and signs an attestation payload. Returns immediately with status PENDING_VERIFICATION. Call POST /v1/attestations/{id}/verify after data deletion to produce a verification record.

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
string <= 256 characters

Deduplicates retries of this request for 24 hours; see the Idempotency section above. Both SDKs set it on every POST.

Request Body schema: application/json
required
subject_identifier
required
string

The identifier of the data subject (e.g. email address, user ID)

system_ids
required
Array of strings <uuid> <= 100 items [ items <uuid > ]

Systems to query

proof_mode
string
Default: "count"
Enum: "count" "merkle"

count — the verification record carries the number of records the query matched per system, and nothing about their content.

merkle — each system additionally carries merkle_root, the root of a Merkle tree over the hashes of the matched records, computed inside the enclave and covered by the record's signature. What that root is: a signed commitment to the record set that was measured. What it is not: a per-record proof. No endpoint serves an inclusion proof or a leaf against it — the leaf hashes are discarded when the root is computed and the records they describe are deleted, so there is nothing to serve, and publishing either would disclose the commitments the root protects. On a 3.06.0 record a holder of every matching record can rebuild the tree and compare roots. From format 7.0 the leaves are keyed with a key drawn inside the enclave and destroyed with the attestation, so the root cannot be rebuilt by anyone, BurnLedger included: it binds what the enclave measured and is not evidence a third party can test. Do not choose merkle on the belief that it is. The full statement is Schedule A §4, https://burnledger.io/legal/scope/.

Omit it and the default follows the data: merkle if any system in the request is marked phi_in_scope, otherwise count. Sending an explicit value always wins, in either direction.

expires_in
string
Default: "72h"

How long the attestation is valid for verification (Go duration)

webhook_url
string or null <uri>

Optional per-attestation webhook URL for status events

Responses

Request samples

Content type
application/json
{
  • "subject_identifier": "user@example.com",
  • "system_ids": [
    ],
  • "proof_mode": "count",
  • "expires_in": "72h",
  • "webhook_url": "http://example.com"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List attestations

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Batch create attestations

Creates attestations for up to 100 subject identifiers concurrently. Individual failures are collected in the errors field and do not abort the batch: the response is 200 whether every subject succeeded or none did, so inspect errors rather than the status code. A 400 is returned only when the request itself is malformed (for example a system listed twice in system_ids).

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
string <= 256 characters

Deduplicates retries of this request for 24 hours; see the Idempotency section above. Both SDKs set it on every POST.

Request Body schema: application/json
required
subject_identifiers
required
Array of strings <= 100 items
system_ids
required
Array of strings <uuid> <= 100 items [ items <uuid > ]
proof_mode
string
Default: "count"
Enum: "count" "merkle"
expires_in
string

Responses

Request samples

Content type
application/json
{
  • "subject_identifiers": [
    ],
  • "system_ids": [
    ],
  • "proof_mode": "count",
  • "expires_in": "72h"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get an attestation

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Verify an attestation (produce verification record)

Re-queries all systems for the original subject identifier. If all systems return zero records the attestation transitions to CERTIFIED and a verification record is issued and appended to the transparency log. If any records remain the status is set to VERIFICATION_FAILED.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

header Parameters
Idempotency-Key
string <= 256 characters

Deduplicates retries of this request for 24 hours; see the Idempotency section above. Both SDKs set it on every POST.

Request Body schema: application/json
required
subject_identifier
required
string

Must match the identifier used at attestation time

Responses

Request samples

Content type
application/json
{
  • "subject_identifier": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Verification Records

Retrieve and revoke verification records

List verification records

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Get a verification record

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Download verification record as PDF

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Get revocation status

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get verification record status statement (public)

Unauthenticated, deliberately. Returns a signed status statement for the verification record — ACTIVE or REVOKED — valid for the window it states. The audience is a relying party holding the verification record: a regulator, an auditor, the customer's customer. Verify the signature against the published keys, and treat a missing or expired statement as revocation-unknown, never as valid.

path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke a verification record

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Request Body schema: application/json
required
reason
required
string <= 1000 characters

Human-readable reason for revocation

Responses

Request samples

Content type
application/json
{
  • "reason": "Issued in error"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke many verification records

Revokes up to 100 verification records in one call, under a single reason. Individual failures are collected in the errors field and do not abort the batch: the response is 200 whether every record was revoked or none was, so inspect errors rather than the status code. A 400 is returned only when the request itself is malformed (no reason, an empty list, more than 100 ids, or the same id twice) and in that case nothing is revoked.

Each record takes the same path as POST /v1/certificates/{id}/revoke — the same revocation entry in the transparency log, the same certificate.revoked webhook, the same audit row — so a batched revocation is indistinguishable from an individual one afterwards. Revoking an already-revoked record succeeds and changes nothing.

The returned records omit status_statement. Revoking invalidates the cached statement, so stapling one per record would mean a fresh signing round trip for every item in the batch; fetch GET /v1/certificates/{id}/status for the records you need a signed statement for.

Authorizations:
bearerAuth
header Parameters
Idempotency-Key
string <= 256 characters

Deduplicates retries of this request for 24 hours; see the Idempotency section above. Both SDKs set it on every POST.

Request Body schema: application/json
required
certificate_ids
required
Array of strings <uuid> <= 100 items [ items <uuid > ]

Must contain no duplicates.

reason
required
string <= 1000 characters

Human-readable reason, applied to every record in the batch. A bulk revocation is one decision, so there is no per-record reason.

Responses

Request samples

Content type
application/json
{
  • "certificate_ids": [
    ],
  • "reason": "issued against a stale export"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get verification record statistics

Returns aggregate counts and monthly issuance breakdown for the customer.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Export verification records

Streams ONE PAGE of verification records as CSV or JSONL, oldest first, up to 10,000 records. Optionally filter by status and date range.

Every response ends in a footer record, and a response without one is truncated. The footer carries the number of verification record records in that response and the cursor for the next page. Pass that cursor back as cursor and repeat until the footer's next cursor is empty; the concatenation of the pages is the complete export.

  • CSV: a final row whose first column is _export_footer, second column the record count, third column the next cursor (empty when the export is complete). It has the same column count as a record row, so a strict reader still parses the file.
  • JSONL: a final object {"_export_footer": true, "record_count": N, "next_cursor": "…" | null}.

The footer is in the body rather than a header on purpose: the file is the evidence, and it gets saved and handed on where headers do not follow. If the export fails part-way — the query dies after the 200 and the first records are already on the wire — the response ends with NO footer. That is the only signal left at that point, so a client must treat a footerless body as incomplete and retry rather than keep it.

Authorizations:
bearerAuth
query Parameters
format
string
Default: "jsonl"
Enum: "csv" "jsonl"
status
string
Enum: "ACTIVE" "REVOKED"
issued_after
string <date-time>
issued_before
string <date-time>
cursor
string

Opaque keyset cursor from the previous page's footer. Omit for the first page. It encodes (issued_at, verification record id): issued_at alone is not unique, so a cursor without the tiebreaker would skip or repeat records at a page boundary.

Responses

Response samples

Content type
"string"

Webhooks

Register and manage webhook endpoints

Register a webhook

The URL must be HTTPS and must not resolve to a private or loopback address (SSRF protection). The returned secret is shown only once; use it to verify the X-BurnLedger-Signature header on incoming events (see the Webhook.secret schema for the exact preimage and a worked example).

Authorizations:
bearerAuth
Request Body schema: application/json
required
url
required
string <uri>

HTTPS endpoint to receive events

Responses

Request samples

Content type
application/json

Response samples

Content type
application/json
{
  • "data": {
    }
}

List webhooks

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Delete a webhook

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Stage a secret rotation

Stages a new signing secret. Until rotation_expires_at (24 hours) every delivery carries two comma-separated digests in X-BurnLedger-Signature, the current secret's then the new one's, so the receiver can switch at any point in the window. Call commit-rotation once it has; if the window lapses first the staged secret is abandoned and deliveries carry the current secret's signature alone. Only one rotation can be staged at a time: while one is inside its window a second call is refused with 409 rather than replacing the secret already handed out.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Commit the staged secret rotation

Retires the old secret; only the new secret signs from this point. Must be called within the rotation window.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

List failed webhook deliveries

Returns unresolved failed deliveries (dead letter queue): events whose first delivery failed and that are either waiting for their next automatic retry (next_attempt_at set) or parked after exhausting the automatic attempts (next_attempt_at null). Use POST .../retry to queue another attempt or DELETE to acknowledge and discard.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Retry a failed webhook delivery

Queues the delivery for the next retry sweep (within about five minutes) regardless of how many automatic attempts it has used. The request returns as soon as the delivery is queued; the attempt itself runs in the background. Poll GET /v1/webhooks/deliveries/failed: the delivery disappears once it succeeds, or stays listed with an incremented attempts and updated error_message if it fails again.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Acknowledge a failed delivery

Marks the failed delivery as resolved without retrying. Use this to discard deliveries that are no longer relevant.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Auth

Registration and login (no authentication)

Register a new account

When email verification is enabled, sends a verification code and returns a customer_id. When disabled, creates the customer and returns credentials directly.

Request Body schema: application/json
required
name
required
string
email
required
string <email>

Responses

Request samples

Content type
application/json
{
  • "name": "Acme Corp",
  • "email": "admin@acme.com"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Verify email with code

Request Body schema: application/json
required
customer_id
required
string <uuid>
code
required
string

6-digit verification code

Responses

Request samples

Content type
application/json
{
  • "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Send a login code via email

Request Body schema: application/json
required
email
required
string <email>

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Verify login code

Request Body schema: application/json
required
customer_id
required
string <uuid>
code
required
string

Responses

Request samples

Content type
application/json
{
  • "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Authenticate with Google OAuth

Verifies a Google ID token. A returning user whose account has no second factor receives session_token. One whose account has TOTP enrolled receives totp_required: true and a totp_challenge instead, and no session until the challenge is presented with a code to POST /v1/auth/login/totp. The challenge is good for five minutes.

Request Body schema: application/json
required
id_token
required
string

Google ID token from client-side OAuth flow

Responses

Request samples

Content type
application/json
{
  • "id_token": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Complete a login with a TOTP code

Second step of a login that answered with totp_required. Takes the totp_challenge from that response together with the six-digit code from the authenticator app, or an eight-character recovery code, and returns the session token the first step withheld. Five consecutive wrong codes lock the account's second factor for fifteen minutes.

Request Body schema: application/json
required
challenge
required
string

The totp_challenge from the first-factor response

code
required
string

Six-digit TOTP code or eight-character recovery code

Responses

Request samples

Content type
application/json
{
  • "challenge": "string",
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Profile

Current user profile management

Log out everywhere

Revokes every active dashboard session for the caller. Unlike the other /v1/auth/ paths this one IS authenticated: it acts on the session that presents it.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get current user profile

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update current user profile

Changes the name at once. A different email does not replace the account's address: it is recorded as pending_email and a code is mailed to it; POST /v1/me/email/verify with that code completes the change. Sending the current email withdraws a pending change. Email changes are accepted only from a dashboard session — an API key gets 403.

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string
email
required
string <email>

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete current user account

Cancels the account's subscription, then deletes the account and everything it owns. Refused while the account owns a team that still has other members, so the team is not left without an owner. There is no ownership transfer: remove the other members first (or have them leave), then delete.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Confirm a pending email change

Consumes the code mailed to pending_email by PATCH /v1/me and makes that address the account's email, verified as of now. Only the latest code, and only if it was mailed to the address that is pending now, is accepted: changing the pending address invalidates codes sent to the previous one.

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string

6-digit code mailed to the pending address

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Submit account application context

Records optional free-text context about what the account intends to certify. Never changes the plan. Throttled to one accepted submission per account per hour.

Authorizations:
bearerAuth
Request Body schema: application/json
required
company
string <= 2000 characters
context
string <= 2000 characters

Responses

Request samples

Content type
application/json
{
  • "company": "string",
  • "context": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

API Keys

Customer-facing API key management

List API keys

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Create an API key

The raw key is returned exactly once in the key field. Store it securely — it cannot be retrieved again.

Authorizations:
bearerAuth
Request Body schema: application/json
optional
role
string
Default: "read_only"
Enum: "read_write" "read_only"

Defaults to read_only when omitted

team_id
string <uuid>

Pin the key to one team you belong to. A pinned key always acts for that team: an X-Team-ID header naming a different team is refused with 403 rather than honoured. Omitted means unpinned — the team is resolved per request from X-Team-ID, else your default team — which is how every key created before this field existed behaves. 403 FORBIDDEN if you are not a member of the team.

Responses

Request samples

Content type
application/json
{
  • "role": "read_write",
  • "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke an API key

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

TOTP

Two-factor authentication setup and validation

Get TOTP 2FA status

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Begin TOTP setup

Generates a new TOTP secret and recovery codes. The secret must be confirmed with a valid code before TOTP is active.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Disable TOTP

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string

6-digit TOTP code or recovery code

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Confirm TOTP setup with a valid code

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string

6-digit TOTP code or recovery code

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Validate a TOTP code

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string

6-digit TOTP code or recovery code

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Use a recovery code

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string

6-digit TOTP code or recovery code

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Billing

Subscription and invoice management

Start a Stripe Checkout session

Returns a hosted Checkout URL for the requested plan. Plans are granted by Stripe on payment, never by this call.

Authorizations:
bearerAuth
Request Body schema: application/json
required
plan
required
string

Responses

Request samples

Content type
application/json
{
  • "plan": "string"
}

Response samples

Content type
application/json
{}

Open the Stripe billing portal

Returns a hosted portal URL for self-serve card and cancellation management.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{}

Get current subscription

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Change subscription plan (not supported) Deprecated

Always refused. A plan change has to move both the price charged and the quota granted together. Subscribe to the higher tier in Checkout to move up; cancel in the billing portal and subscribe again to stop or step down. This endpoint remains only to answer callers written against the older contract.

Authorizations:
bearerAuth
Request Body schema: application/json
required
plan
required
string

Target plan name

Responses

Request samples

Content type
application/json
{
  • "plan": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Cancel subscription

Stops the subscription renewing in Stripe. Access continues until the end of the period already paid for; the plan is revoked at that boundary. Accounts with no paid subscription (trial, pay-as-you-go, comped) have nothing to cancel and are refused.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List invoices

Newest first. The cursor is a keyset over (created_at, id); a cursor that does not decode is a 400, never a silent restart from page one.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Audit

Audit log access

List audit log entries

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque pagination cursor from the previous page's next_cursor

limit
integer [ 1 .. 100 ]
Default: 25

Number of results per page (1–100, default 25)

resource_type
string

Filter by resource type (e.g. certificate, system)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Teams

Teams, members and invitations

Create a team

The caller becomes the team's owner.

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List teams the caller belongs to

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Accept a team invitation

Redeems the single-use token issued by inviteTeamMember and joins the caller to the team.

Authorizations:
bearerAuth
Request Body schema: application/json
required
token
required
string

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a team

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List team members

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Change a member's role

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

member_id
required
string <uuid>

Customer UUID of the member

Request Body schema: application/json
required
role
required
string (TeamRole)
Enum: "owner" "admin" "member" "viewer"

Responses

Request samples

Content type
application/json
{
  • "role": "owner"
}

Response samples

Content type
application/json
{
  • "data": null
}

Remove a member

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

member_id
required
string <uuid>

Customer UUID of the member

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Invite a member

Creates a pending invitation. The raw token is returned ONCE in this response and never again; hand it to the invitee out of band.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Request Body schema: application/json
required
email
required
string <email>
role
required
string
Enum: "admin" "member" "viewer"

Owner cannot be granted by invitation

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "role": "admin"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List pending invitations

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Revoke a pending invitation

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Resource UUID

invitation_id
required
string <uuid>

Invitation UUID

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Contact

Public contact form (no authentication)

Submit a contact message

Public and unauthenticated. Rate limited by IP and capped per origin. Leave website empty — it is a honeypot, and a value there is treated as an automated submission.

Request Body schema: application/json
required
name
required
string <= 120 characters
email
required
string <email> <= 200 characters
company
string <= 160 characters
topic
string

Unrecognised values are recorded as other

message
required
string [ 10 .. 5000 ] characters
website
string

Honeypot. Leave empty.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "email": "user@example.com",
  • "company": "string",
  • "topic": "string",
  • "message": "stringstri",
  • "website": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Log

Public transparency log endpoints (no authentication)

Get the signed tree head

Returns the latest signed tree head (STH) from the transparency log.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a log entry by index

path Parameters
index
required
integer <int64> >= 0

Zero-based log entry index

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a range of log entries

query Parameters
start
required
integer <int64> >= 0
end
required
integer <int64> >= 1

Exclusive end index

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get an inclusion proof for a log entry

query Parameters
index
required
integer <int64> >= 0
tree_size
required
integer <int64> >= 1

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a consistency proof between two tree sizes

query Parameters
old_size
required
integer <int64> >= 1

The smaller (older) tree size.

new_size
required
integer <int64> >= 1

The larger (newer) tree size.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get the digest of the status-issuance record

Unauthenticated. Every signed certificate status statement is written to an append-only issuance record on the issuing side (ADR-024). This returns a digest of that whole record — a hash chain over every row recorded at or before as_of — so that a party the issuer does not control can write it down where the issuer cannot revise it. The public witness workflow fetches this on every run and commits it into its Bitcoin-anchored record.

The digest is not an inclusion proof: a holder cannot place one statement under it without the rows before it. What it fixes is the record's contents at a moment, so a rewrite after that moment is detectable by anyone later shown the table. The rule is stated in docs/transparency-log.md and reproduces from a dump with jq and sha256sum.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Enclave

Hardware identity attestation (no authentication). What a caller uses to check for themselves that the signing key lives in an enclave.

Enrol a customer key group (ADR-025)

Vouches for a threshold group of Ed25519 public keys, returning two enclave-signed documents: the enrollment statement naming the group, and a bounded authorization making it usable.

Trust on first use, and the design says so. The enclave cannot tell a customer from a party impersonating one — it has no directory and every byte reaches it through this API. What it provides is a signed record of which group it was told to trust. Compare customer_key_id against the id you compute locally from your own member keys; a substituted group produces a document that fails that one comparison. The SDKs compute it offline (customerKeyId, CustomerKeyGroup.key_id) precisely so the check does not depend on us.

asserted_team_id is optional and, if present, must equal the authenticated team; it is refused rather than overridden.

Not yet enabled in production. The verbs exist ahead of use.

Authorizations:
bearerAuth
Request Body schema: application/json
required
threshold
required
integer >= 1

How many distinct members must sign. 1 <= threshold <= len(member_keys).

member_keys
required
Array of strings
asserted_team_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "threshold": 1,
  • "member_keys": [
    ],
  • "asserted_team_id": "c09651c9-69cf-43f3-b215-9f649e69be5b"
}

Rotate an enrolled key group to a successor (ADR-025)

Issues the chain link from one enrolled group to its replacement.

The outgoing group must authorise its own replacement, at its own threshold, over burnledger.key_rotation_request.v1. This is the one operation whose authority is the customer's rather than the enclave's: it grants authority to a key the enclave has never seen, so without that proof anyone relaying this request could name their own key as the successor. Both key ids are inside the signed payload, so signatures for one rotation cannot authorise another.

prev_enrollment is the enclave's own earlier enrollment statement; its signature is what makes the outgoing group trustworthy input.

Authorizations:
bearerAuth
Request Body schema: application/json
required
prev_enrollment
required
object

The enclave-signed enrollment statement for the outgoing group.

threshold
required
integer >= 1
member_keys
required
Array of strings
required
object

Hex signatures over the rotation payload, keyed by member fingerprint.

asserted_team_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "prev_enrollment": { },
  • "threshold": 1,
  • "member_keys": [
    ],
  • "signatures": {
    },
  • "asserted_team_id": "c09651c9-69cf-43f3-b215-9f649e69be5b"
}

Register what may later be measured (ADR-025)

Pins the tuple a later attestation or verification is allowed to measure: this sealed config, this query template, this connector, under this customer key.

The config travels sealed (from ENCRYPT_CONFIG) and is hashed after the enclave decrypts it, because the digest must be over what the enclave will later decrypt and compare against. A digest over ciphertext would pin the envelope, and the envelope is not what is being pinned.

The enclave keeps nothing: the certificate returned here is yours to store and hand back on every later request.

Authorizations:
bearerAuth
Request Body schema: application/json
required
system_id
required
string <uuid>
connection_config
required
string

base64 of the sealed config from ENCRYPT_CONFIG.

query_template
required
string
connector_type
required
string
authorization
required
object

The team authorization certificate from enrollment.

asserted_team_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
  • "connection_config": "string",
  • "query_template": "string",
  • "connector_type": "string",
  • "authorization": { },
  • "asserted_team_id": "c09651c9-69cf-43f3-b215-9f649e69be5b"
}

Attest the enclave's identity over your nonce (public)

Unauthenticated, deliberately. Returns a fresh AWS Nitro attestation document over a nonce you choose, binding the Ed25519 key that signs verification records. It is how a caller establishes, without trusting this API, that the signing key really is held by an enclave running an image with a given PCR0.

Draw 32 bytes from your own CSPRNG and pass them base64url-encoded. The nonce is what makes the answer a statement about now: every document this enclave has ever produced is genuine, including old ones from images since retired, so a verifier must require the document to carry the challenge it just sent. The nonce field beside the document is an echo for humans — verify against the nonce inside the signed document, never this one.

nsm_attestation is the standard base64 encoding of the COSE_Sign1 document. Verify it by: checking the ES384 signature; building the chain from the document's leaf certificate through its cabundle to the AWS Nitro Enclaves root CA (AWS_NitroEnclaves_Root-G1, obtained from AWS, not from us); comparing pcrs[0] against the measurement you expect; comparing nonce against the one you sent; and checking timestamp against your own clock. burnledger enclave attest --pcr0 <expected> does all of it and prints a verdict.

What a pass proves: the key is held by a Nitro enclave running an image measuring that PCR0, right now. What it does not prove: that the image was built from any particular source. The enclave source is unpublished (BL-1-007); the measurements BurnLedger has run are published with their dates at /docs/enclave-measurements/, and comparing against that page is a check against a dated public commitment, not a reproduction.

query Parameters
nonce
required
string
Example: nonce=3q2-796tvu_erb7v3q2-796tvu_erb7v3q2-796tvu8

Exactly 32 random bytes you drew yourself, base64url-encoded (padding optional). A nonce supplied by anyone else proves nothing.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Public

Health and key discovery (no authentication)

Health check

Responses

Response samples

Content type
application/json
{
  • "status": "ok"
}

Health check (Kubernetes spelling)

Identical to /health. Exists because most uptime checkers default to this path.

Responses

Response samples

Content type
application/json
{
  • "status": "ok"
}

Readiness check

Returns 200 when the service is ready to accept traffic (database connected).

Responses

Response samples

Content type
application/json
{
  • "status": "ok"
}

Public key discovery

Returns the active and retired Ed25519 signing keys for offline verification record verification.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Last updated from the OpenAPI specification. Endpoint guide: Get a deletion certificate.