Download OpenAPI specification:
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.
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.
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.
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.
409 and code IDEMPOTENCY_IN_FLIGHT; it is not
queued. Retry after a short delay.422 with code
IDEMPOTENCY_KEY_REUSE.5xx or 429 is never stored: the key is released so a retry runs
the handler again.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.| 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 | 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 |
| 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
|
| 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 |
{- "name": "Production Users DB",
- "connector_type": "postgresql",
- "connection_config": {
- "host": "db.example.com",
- "port": 5432,
- "database": "myapp",
- "username": "dp_reader",
- "password": "secret"
}, - "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"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "connector_type": "postgresql",
- "subject_query": "string",
- "hash_scope": "full",
- "phi_in_scope": true,
- "max_records": 0,
- "max_bytes": 0,
- "query_timeout": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "health_status": "UNKNOWN",
- "health_checked_at": "2019-08-24T14:15:22Z",
- "health_error": "string",
- "transport_security": "unknown",
- "read_only_enforcement": "unknown",
- "recoverable_state": "unknown"
}
}| health | string Enum: "healthy" "unhealthy" "unknown" Return only systems in this health state. Matched case-insensitively, so the |
| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "connector_type": "postgresql",
- "subject_query": "string",
- "hash_scope": "full",
- "phi_in_scope": true,
- "max_records": 0,
- "max_bytes": 0,
- "query_timeout": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "health_status": "UNKNOWN",
- "health_checked_at": "2019-08-24T14:15:22Z",
- "health_error": "string",
- "transport_security": "unknown",
- "read_only_enforcement": "unknown",
- "recoverable_state": "unknown"
}
], - "next_cursor": "string",
- "unhealthy_count": 0
}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.
| 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 | 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 |
| 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
|
| 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 |
{- "name": "Production Users DB",
- "connector_type": "postgresql",
- "connection_config": {
- "host": "db.example.com",
- "port": 5432,
- "database": "myapp",
- "username": "dp_reader",
- "password": "secret"
}, - "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"
}{- "data": {
- "ok": true,
- "steps": [
- {
- "name": "string",
- "status": "passed",
- "detail": "string",
- "fix": "string"
}
], - "transport_security": "unknown",
- "read_only_enforcement": "unknown",
- "read_scope": [
- "string"
], - "replication": {
- "role": "unknown",
- "lag": "string",
- "detail": "string"
}
}
}| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "connector_type": "postgresql",
- "subject_query": "string",
- "hash_scope": "full",
- "phi_in_scope": true,
- "max_records": 0,
- "max_bytes": 0,
- "query_timeout": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "health_status": "UNKNOWN",
- "health_checked_at": "2019-08-24T14:15:22Z",
- "health_error": "string",
- "transport_security": "unknown",
- "read_only_enforcement": "unknown",
- "recoverable_state": "unknown"
}
}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.
| id required | string <uuid> Resource UUID |
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}Returns the stored result of the last health check without probing the system. The body is the health fields alone, not the full System.
| id required | string <uuid> Resource UUID |
{- "data": {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "health_status": "UNKNOWN",
- "health_checked_at": "2019-08-24T14:15:22Z",
- "health_error": "string"
}
}| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "connector_type": "postgresql",
- "subject_query": "string",
- "hash_scope": "full",
- "phi_in_scope": true,
- "max_records": 0,
- "max_bytes": 0,
- "query_timeout": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "health_status": "UNKNOWN",
- "health_checked_at": "2019-08-24T14:15:22Z",
- "health_error": "string",
- "transport_security": "unknown",
- "read_only_enforcement": "unknown",
- "recoverable_state": "unknown"
}
}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.
| 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. |
| 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"
Omit it and the default follows the data: |
| 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 |
{- "subject_identifier": "user@example.com",
- "system_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "proof_mode": "count",
- "expires_in": "72h",
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "subject_hash": "string",
- "proof_mode": "count",
- "status": "PENDING_VERIFICATION",
- "systems": [
- {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "system_name": "string",
- "record_count": 0,
- "hash_scope": "full",
- "observed_at": "2019-08-24T14:15:22Z"
}
], - "attested_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f"
}
}| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "subject_hash": "string",
- "proof_mode": "count",
- "status": "PENDING_VERIFICATION",
- "systems": [
- {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "system_name": "string",
- "record_count": 0,
- "hash_scope": "full",
- "observed_at": "2019-08-24T14:15:22Z"
}
], - "attested_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f"
}
], - "next_cursor": "string"
}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).
| 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. |
| 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 |
{- "subject_identifiers": [
- "string"
], - "system_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "proof_mode": "count",
- "expires_in": "72h"
}{- "data": {
- "attestations": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "subject_hash": "string",
- "proof_mode": "count",
- "status": "PENDING_VERIFICATION",
- "systems": [
- {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "system_name": "string",
- "record_count": 0,
- "hash_scope": "full",
- "observed_at": "2019-08-24T14:15:22Z"
}
], - "attested_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f"
}
], - "errors": [
- {
- "index": 0,
- "subject_identifier": "string",
- "error": "string"
}
]
}
}| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "subject_hash": "string",
- "proof_mode": "count",
- "status": "PENDING_VERIFICATION",
- "systems": [
- {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "system_name": "string",
- "record_count": 0,
- "hash_scope": "full",
- "observed_at": "2019-08-24T14:15:22Z"
}
], - "attested_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f"
}
}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.
| id required | string <uuid> Resource UUID |
| 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. |
| subject_identifier required | string Must match the identifier used at attestation time |
{- "subject_identifier": "string"
}{- "data": {
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "status": "PENDING_VERIFICATION",
- "systems": [
- {
- "system_id": "6ed1f7c4-3772-4fa8-92a5-defb7f18dbc4",
- "system_name": "string",
- "record_count": 0
}
], - "verification record": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "format_version": "9.0",
- "issued_at": "2019-08-24T14:15:22Z",
- "status": "ACTIVE",
- "transparency_status": "PENDING",
- "revoked_at": "2019-08-24T14:15:22Z",
- "revocation_reason": "string",
- "verification record": { },
- "status_statement": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}, - "nsm_attestation": "string"
}
}
}| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "format_version": "9.0",
- "issued_at": "2019-08-24T14:15:22Z",
- "status": "ACTIVE",
- "transparency_status": "PENDING",
- "revoked_at": "2019-08-24T14:15:22Z",
- "revocation_reason": "string",
- "verification record": { },
- "status_statement": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}, - "nsm_attestation": "string"
}
], - "next_cursor": "string"
}| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "format_version": "9.0",
- "issued_at": "2019-08-24T14:15:22Z",
- "status": "ACTIVE",
- "transparency_status": "PENDING",
- "revoked_at": "2019-08-24T14:15:22Z",
- "revocation_reason": "string",
- "verification record": { },
- "status_statement": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}, - "nsm_attestation": "string"
}
}| id required | string <uuid> Resource UUID |
{- "data": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "reason": "string"
}
}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.
| id required | string <uuid> Resource UUID |
{- "data": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}
}| id required | string <uuid> Resource UUID |
| reason required | string <= 1000 characters Human-readable reason for revocation |
{- "reason": "Issued in error"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "format_version": "9.0",
- "issued_at": "2019-08-24T14:15:22Z",
- "status": "ACTIVE",
- "transparency_status": "PENDING",
- "revoked_at": "2019-08-24T14:15:22Z",
- "revocation_reason": "string",
- "verification record": { },
- "status_statement": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}, - "nsm_attestation": "string"
}
}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.
| 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. |
| 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. |
{- "certificate_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "reason": "issued against a stale export"
}{- "data": {
- "certificates": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "attestation_id": "e21e05b4-d2f4-4bc0-ba2d-fc19366a584b",
- "format_version": "9.0",
- "issued_at": "2019-08-24T14:15:22Z",
- "status": "ACTIVE",
- "transparency_status": "PENDING",
- "revoked_at": "2019-08-24T14:15:22Z",
- "revocation_reason": "string",
- "verification record": { },
- "status_statement": {
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "status": "ACTIVE",
- "revoked_at": "2019-08-24T14:15:22Z",
- "replacement_certificate_id": "13cc5322-1cb4-40a1-bd9e-bc593de833f1",
- "revocation_log_index": 0,
- "statement_issued_at": "2019-08-24T14:15:22Z",
- "statement_expires_at": "2019-08-24T14:15:22Z",
- "sth_tree_size": 0,
- "sth_root_hash": "string",
- "signature": "string",
- "key_id": "string"
}, - "nsm_attestation": "string"
}
], - "errors": [
- {
- "index": 0,
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "error": "string"
}
]
}
}Returns aggregate counts and monthly issuance breakdown for the customer.
{- "data": {
- "total": 0,
- "active": 0,
- "revoked": 0,
- "transparency_included": 0,
- "transparency_pending": 0,
- "transparency_failed": 0,
- "issued_last_30_days": 0,
- "monthly_issuance": [
- {
- "month": "2026-03",
- "count": 0
}
]
}
}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.
_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.{"_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.
| 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. |
"string"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).
| url required | string <uri> HTTPS endpoint to receive events |
{
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "secret": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
}| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "secret": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}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.
| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "new_secret": "string",
- "rotation_expires_at": "2019-08-24T14:15:22Z"
}
}Retires the old secret; only the new secret signs from this point. Must be called within the rotation window.
| id required | string <uuid> Resource UUID |
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}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.
| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675",
- "event_type": "attestation.created",
- "error_message": "string",
- "attempts": 0,
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}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.
| id required | string <uuid> Resource UUID |
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675",
- "event_type": "attestation.created",
- "error_message": "string",
- "attempts": 0,
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
}Marks the failed delivery as resolved without retrying. Use this to discard deliveries that are no longer relevant.
| id required | string <uuid> Resource UUID |
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}When email verification is enabled, sends a verification code and returns a customer_id. When disabled, creates the customer and returns credentials directly.
| name required | string |
| email required | string <email> |
{- "name": "Acme Corp",
- "email": "admin@acme.com"
}{- "data": {
- "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
- "message": "Verification code sent to admin@acme.com"
}
}| customer_id required | string <uuid> |
| code required | string 6-digit verification code |
{- "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
- "code": "string"
}{- "data": {
- "customer": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}
}| email required | string <email> |
{- "email": "user@example.com"
}{- "data": {
- "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
- "message": "string"
}
}| customer_id required | string <uuid> |
| code required | string |
{- "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
- "code": "string"
}{- "data": {
- "customer": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}
}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.
| id_token required | string Google ID token from client-side OAuth flow |
{- "id_token": "string"
}{- "data": {
- "customer": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}, - "session_token": "string",
- "is_new": true,
- "totp_required": true,
- "totp_challenge": "string"
}
}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.
| challenge required | string The |
| code required | string Six-digit TOTP code or eight-character recovery code |
{- "challenge": "string",
- "code": "string"
}{- "data": {
- "customer": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}, - "session_token": "string",
- "is_new": true,
- "totp_required": true,
- "totp_challenge": "string"
}
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}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.
| name required | string |
| email required | string <email> |
{- "name": "string",
- "email": "user@example.com"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}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.
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}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.
| code required | string 6-digit code mailed to the pending address |
{- "code": "string"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}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.
| company | string <= 2000 characters |
| context | string <= 2000 characters |
{- "company": "string",
- "context": "string"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "email": "user@example.com",
- "pending_email": "user@example.com",
- "plan": "trial",
- "plan_started_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "plan_usage": {
- "certificates_used": 0,
- "certificates_limit": 0,
- "systems_used": 0,
- "systems_limit": 0,
- "trial_expires_at": "2019-08-24T14:15:22Z"
}
}
}| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "prefix": "dp_a1b2c3d4e",
- "key": "dp_a1b2c3d4e5f6...",
- "role": "read_write",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "created_at": "2019-08-24T14:15:22Z",
- "revoked_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}The raw key is returned exactly once in the key field. Store it
securely — it cannot be retrieved again.
| 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 |
{- "role": "read_write",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "prefix": "dp_a1b2c3d4e",
- "key": "dp_a1b2c3d4e5f6...",
- "role": "read_write",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "created_at": "2019-08-24T14:15:22Z",
- "revoked_at": "2019-08-24T14:15:22Z"
}
}Generates a new TOTP secret and recovery codes. The secret must be confirmed with a valid code before TOTP is active.
{- "data": {
- "secret": "string",
- "provision_uri": "string",
- "recovery_codes": [
- "string"
]
}
}| code required | string 6-digit TOTP code or recovery code |
{- "code": "string"
}{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}| code required | string 6-digit TOTP code or recovery code |
{- "code": "string"
}{- "data": {
- "message": "TOTP enabled"
}
}| code required | string 6-digit TOTP code or recovery code |
{- "code": "string"
}{- "data": {
- "message": "code valid"
}
}| code required | string 6-digit TOTP code or recovery code |
{- "code": "string"
}{- "data": {
- "message": "recovery code accepted"
}
}Returns a hosted Checkout URL for the requested plan. Plans are granted by Stripe on payment, never by this call.
| plan required | string |
{- "plan": "string"
}{
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "plan": "compliance",
- "status": "trialing",
- "current_period_start": "2019-08-24T14:15:22Z",
- "current_period_end": "2019-08-24T14:15:22Z",
- "cancel_at_period_end": true,
- "canceled_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
}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.
| plan required | string Target plan name |
{- "plan": "string"
}{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}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.
{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "plan": "compliance",
- "status": "trialing",
- "current_period_start": "2019-08-24T14:15:22Z",
- "current_period_end": "2019-08-24T14:15:22Z",
- "cancel_at_period_end": true,
- "canceled_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
}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.
| cursor | string Opaque pagination cursor from the previous page's |
| limit | integer [ 1 .. 100 ] Default: 25 Number of results per page (1–100, default 25) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "amount_cents": 0,
- "currency": "usd",
- "status": "draft",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "paid_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}| cursor | string Opaque pagination cursor from the previous page's |
| 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) |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "actor_type": "session",
- "actor_id": "04f37679-bfbf-4906-b749-01756515cecf",
- "actor_label": "ops-alice",
- "action": "certificate.revoked",
- "resource_type": "verification record",
- "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
- "metadata": { },
- "ip_address": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}The caller becomes the team's owner.
| name required | string |
{- "name": "string"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
}Redeems the single-use token issued by inviteTeamMember and joins the caller to the team.
| token required | string |
{- "token": "string"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
}| id required | string <uuid> Resource UUID |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
- "email": "user@example.com",
- "name": "string",
- "role": "owner",
- "joined_at": "2019-08-24T14:15:22Z"
}
]
}| id required | string <uuid> Resource UUID |
| member_id required | string <uuid> Customer UUID of the member |
| role required | string (TeamRole) Enum: "owner" "admin" "member" "viewer" |
{- "role": "owner"
}{- "data": null
}| id required | string <uuid> Resource UUID |
| member_id required | string <uuid> Customer UUID of the member |
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}Creates a pending invitation. The raw token is returned ONCE in this response and never again; hand it to the invitee out of band.
| id required | string <uuid> Resource UUID |
| email required | string <email> |
| role required | string Enum: "admin" "member" "viewer" Owner cannot be granted by invitation |
{- "email": "user@example.com",
- "role": "admin"
}{- "data": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "email": "user@example.com",
- "role": "owner",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "token": "string"
}
}| id required | string <uuid> Resource UUID |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "email": "user@example.com",
- "role": "owner",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
]
}| id required | string <uuid> Resource UUID |
| invitation_id required | string <uuid> Invitation UUID |
{- "error": {
- "code": "NOT_FOUND",
- "message": "attestation not found",
- "request_id": "8f3c2a1b-9d4e-4c6f-a2b7-1e5d3f7a9c0b"
}
}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.
| name required | string <= 120 characters |
| email required | string <email> <= 200 characters |
| company | string <= 160 characters |
| topic | string Unrecognised values are recorded as |
| message required | string [ 10 .. 5000 ] characters |
| website | string Honeypot. Leave empty. |
{- "name": "string",
- "email": "user@example.com",
- "company": "string",
- "topic": "string",
- "message": "stringstri",
- "website": "string"
}{- "data": {
- "received": true,
- "message": "string"
}
}| index required | integer <int64> >= 0 Zero-based log entry index |
{- "data": {
- "index": 0,
- "entry_type": "CERTIFICATE",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "hash": "string",
- "replacement_id": "8076ac29-0aa5-4b57-99a2-25a4517a9861",
- "appended_at": "2019-08-24T14:15:22Z"
}
}| start required | integer <int64> >= 0 |
| end required | integer <int64> >= 1 Exclusive end index |
{- "data": [
- {
- "index": 0,
- "entry_type": "CERTIFICATE",
- "certificate_id": "620df92a-0599-41ff-b912-58ec0e5bd21f",
- "hash": "string",
- "replacement_id": "8076ac29-0aa5-4b57-99a2-25a4517a9861",
- "appended_at": "2019-08-24T14:15:22Z"
}
]
}| index required | integer <int64> >= 0 |
| tree_size required | integer <int64> >= 1 |
{- "data": {
- "index": 0,
- "tree_size": 0,
- "inclusion_proof": [
- "string"
], - "signed_tree_head": {
- "tree_size": 0,
- "root_hash": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "signature": "string"
}
}
}| old_size required | integer <int64> >= 1 The smaller (older) tree size. |
| new_size required | integer <int64> >= 1 The larger (newer) tree size. |
{- "data": {
- "old_size": 0,
- "new_size": 0,
- "proof": [
- "string"
]
}
}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.
{- "data": {
- "as_of": "2019-08-24T14:15:22Z",
- "count": 0,
- "chain_head": "string"
}
}Hardware identity attestation (no authentication). What a caller uses to check for themselves that the signing key lives in an enclave.
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.
| 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> |
{- "threshold": 1,
- "member_keys": [
- "string"
], - "asserted_team_id": "c09651c9-69cf-43f3-b215-9f649e69be5b"
}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.
| 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> |
{- "prev_enrollment": { },
- "threshold": 1,
- "member_keys": [
- "string"
], - "signatures": {
- "property1": "string",
- "property2": "string"
}, - "asserted_team_id": "c09651c9-69cf-43f3-b215-9f649e69be5b"
}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.
| 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> |
{- "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"
}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.
| 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. |
{- "data": {
- "nsm_attestation": "string",
- "nonce": "string"
}
}Returns the active and retired Ed25519 signing keys for offline verification record verification.
{- "data": {
- "keys": [
- {
- "key_id": "string",
- "public_key": "string",
- "key_status": "active",
- "not_before": "2019-08-24T14:15:22Z",
- "not_after": "2019-08-24T14:15:22Z",
- "compromised_from": "2019-08-24T14:15:22Z"
}
]
}
}Last updated from the OpenAPI specification. Endpoint guide: Get a deletion certificate.