Rox
Register · Developers
PUBLIC DOCS
v1 · api.roxregister.com
Sign in to Platform
OverviewQuickstartsGuidesSDKsAPI referenceErrorsWebhooksJWS vectors
API reference
Base https://api.roxregister.com, API version 0.1.0. Generated at build time from the OpenAPI 3.1 contract emitted by the live API's schemas. JSON everywhere; errors are RFC 7807 problem+json with a stable RR-code. List endpoints use cursor pagination (?cursor=&limit= up to 200). Mutations carry the detached request JWS (test vectors) and honor Idempotency-Key for 48h per vault. Rate limit 600 req/min per key, then 429 with Retry-After. Authenticated endpoints are gated by API-key scopes, shown as a badge on each endpoint below: seal, read, admin, vaults.manage and billing.read. A scope applies only when granted on the key itself; in particular billing.read must be granted explicitly, seal or read keys do not gain it implicitly (RR-1005 otherwise).
Sealing is uniform. A seal request is validated and either rejected immediately or accepted with 202. Every accepted record then reaches exactly one terminal outcome, anchored with proof or failed, delivered by webhook or polling. There are no anchoring options to pick and no timing promises to rely on.
POST /v1/sealPOST /v1/seal/stampPOST /v1/seal/batchGET /v1/records/{idOrReference}GET /v1/recordsPOST /v1/envelopesGET /v1/envelopesGET /v1/envelopes/{id}POST /v1/envelopes/{id}/assemblePOST /v1/envelopes/{id}/voidPOST /v1/vaultsGET /v1/vaultPOST /v1/vaults/{id}/request-keysDELETE /v1/vaults/{id}/request-keys/{kid}GET /v1/vault/key-historyGET /v1/gasGET /v1/gas/ledgerGET /verify/{reference}POST /verify/hashPOST /verify/fileGET /healthzGET /v1/status
Sealing
The machine plane's write side. Bearer vault API key plus a detached request JWS on every call. A seal request is validated or rejected immediately; every accepted record reaches exactly one terminal outcome, anchored with proof or failed, delivered by webhook or polling. No timing promises.
POST/v1/sealAPI keyscope: sealJWS required
Seal a record digest
Seal a client-computed SHA-256 digest into your vault. The request is validated and either rejected immediately or accepted with 202 (record id, public reference, salt). Exactly one terminal outcome follows asynchronously: anchored (with proof) or failed, delivered by the record.anchored / record.failed webhooks or by polling the record. Honors Idempotency-Key, scoped per vault for 48h.
•Idempotency-Key honored for 48h, scoped per vault.
REQUEST BODY · application/json
record_typestringREQUIRED
A record type key configured for your vault.
external_refstring
Your own identifier for the record (policy number, order id, case id).
content_digest_hexstringREQUIRED
SHA-256 of the content bytes, lowercase hex.
field_hashesarray<object>
Selective disclosure: ordered salted field hashes. Field values and field salts never reach Rox. See the Selective disclosure guide.
namestringREQUIRED
salted_hash_hexstringREQUIRED
SHA-256(field_salt || utf8(name) || 0x1f || canonical_value), lowercase hex.
metadataany
Must validate against the record type's metadata schema; disallowed PII keys are rejected (RR-2003).
202Record accepted; resolution arrives via webhooks / GET /v1/records
RESPONSE FIELDS
record_idstringREQUIRED
Stable record id (rec_<ULID>).
referencestringREQUIRED
Public reference (rr_ref_<ULID>), safe to print on documents and in QR codes.
salt_hexstringREQUIRED
64-char hex, returned once at sealing time. Store it: verification by digest needs it. Never public, never on-chain.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
idempotent_replayconstant true
Present (true) when Idempotency-Key matched an earlier request; the original record is returned.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the seal scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-2002Record type unknown, or metadata violates its schema.
RR-2003Metadata contains a disallowed PII key.
RR-2101Idempotency key reused with a different content digest.
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
DIGEST=$(shasum -a 256 <your-file.pdf> | cut -d' ' -f1)
BODY='{"record_type":"<your-record-type>","external_ref":"<your-own-ref>","content_digest_hex":"'"$DIGEST"'"}'
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/seal\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/seal \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# 202: validated and accepted. Store salt_hex. Exactly one terminal
# outcome follows: record.anchored (webhook) or record.failed.
POST/v1/seal/stampAPI keyscope: sealJWS required
Stamp and seal a file
Upload the file instead of a digest (PDF only, max 50MB; anything else is rejected with 415). The server reserves the reference and salt, stamps a verify QR onto the document (the salt rides in the URL fragment so it never reaches server logs), hashes the STAMPED bytes as the content digest, and seals. Exactly one terminal outcome follows asynchronously: anchored (with proof) or failed, delivered by the record.anchored / record.failed webhooks or by polling the record. The original upload is never persisted; the stored stamped file expires after 1 day.
•PDF only, max 50MB; anything else is 415.
•The request JWS signs the sha256 of the RAW multipart body bytes.
•Idempotency-Key honored for 48h, scoped per vault. A replay returns the original record and a freshly presigned URL to the ORIGINAL stamped bytes; replays are never digest-compared (stamping is not byte-deterministic), so use one key per document.
•QR placement comes from the record type (verify_fields.qr_position: bottom-right default, bottom-left, top-right, top-left), not from the request. See the stamping guide for the end-to-end flow.
multipart/form-data: a file part (PDF) plus the /v1/seal fields minus content_digest_hex; metadata and field_hashes are JSON-encoded text fields.
202Stamped and sealed; stamped_file_url is presigned for 24h
RESPONSE FIELDS
record_idstringREQUIRED
Stable record id (rec_<ULID>).
referencestringREQUIRED
Public reference (rr_ref_<ULID>), safe to print on documents and in QR codes.
salt_hexstringREQUIRED
64-char hex, returned once at sealing time. Store it: verification by digest needs it. Never public, never on-chain.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
idempotent_replayconstant true
Present (true) when Idempotency-Key matched an earlier request; the original record is returned.
content_digest_hexstringREQUIRED
SHA-256 of the content bytes, lowercase hex.
stamped_file_urlstringREQUIRED
Presigned for 24h. The stored stamped file expires after 1 day; download and keep your copy.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the seal scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-2002Record type unknown, or metadata violates its schema.
RR-2003Metadata contains a disallowed PII key.
RR-2101Idempotency key reused with a different content digest.
curl
# The JWS covers the sha256 of the RAW multipart bytes, so build the body
# yourself with a fixed boundary instead of letting curl invent one.
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
B="rr-$(uuidgen | tr -d '-')"
{
  printf -- '--%s\r\nContent-Disposition: form-data; name="record_type"\r\n\r\n<your-record-type>\r\n' "$B"
  printf -- '--%s\r\nContent-Disposition: form-data; name="file"; filename="<your-file.pdf>"\r\nContent-Type: application/pdf\r\n\r\n' "$B"
  cat <your-file.pdf>
  printf -- '\r\n--%s--\r\n' "$B"
} > /tmp/rr-multipart
TS=$(($(date +%s) * 1000)); NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
BODY_HASH=$(shasum -a 256 /tmp/rr-multipart | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/seal/stamp\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/seal/stamp \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: multipart/form-data; boundary=$B" \
  -H "X-RR-Key-Id: ish-prod-2026-01" -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" -H "X-RR-Signature: $SIG" \
  --data-binary @/tmp/rr-multipart
# 202: content_digest_hex is the hash of the STAMPED bytes;
# stamped_file_url is presigned for 24h, download and keep that file.
POST/v1/seal/batchAPI keyscope: sealJWS required
Seal many digests
Seal up to 1,000 digest items under one request JWS. Items are validated individually; the response is 207 Multi-Status with one {status, body} per item in request order (a 202 seal body or a 4xx problem document). Each accepted item then follows the same path as a single seal: exactly one terminal outcome, anchored or failed. Idempotency is per item via the item-level idempotency_key field; the Idempotency-Key header is ignored here.
•Idempotency-Key header ignored; use the per-item idempotency_key field.
REQUEST BODY · application/json
itemsarray<any>REQUIRED
Array of /v1/seal request bodies, each with an optional idempotency_key field.
207Per-item results in request order
RESPONSE FIELDS
[]array<object>REQUIRED
Top-level array; one entry per item.
statusintegerREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
bodyanyREQUIRED
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the seal scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-2002Record type unknown, or metadata violates its schema.
RR-2003Metadata contains a disallowed PII key.
RR-2101Idempotency key reused with a different content digest.
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
BODY='{"items":[
  {"record_type":"<your-record-type>","content_digest_hex":"<sha256-hex-of-item-1>","idempotency_key":"<your-item-1-key>"},
  {"record_type":"<your-record-type>","content_digest_hex":"<sha256-hex-of-item-2>"}
]}'
BODY=$(printf '%s' "$BODY" | tr -d '\n')   # sign the exact bytes you send
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/seal/batch\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/seal/batch \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# 207: one {status, body} per item, in request order.
Records
Read your sealed records: status, anchor coordinates, cursor-paginated listings.
GET/v1/records/{idOrReference}API keyscope: read
Fetch one record
Fetch one record by id (rec_<ULID>) or public reference (rr_ref_<ULID>): full status, digest, salt, leaf hash and, once the terminal outcome is reached, its anchor coordinates. Polling this endpoint is the pull-side alternative to the record.anchored / record.failed webhooks.
PARAMETERS
idOrReference (path)stringREQUIRED
rec_<ULID> or rr_ref_<ULID>
200The record
RESPONSE FIELDS
record_idstringREQUIRED
Stable record id (rec_<ULID>).
referencestringREQUIRED
Public reference (rr_ref_<ULID>), safe to print on documents and in QR codes.
record_typestringREQUIRED
A record type key configured for your vault.
external_refstring | nullREQUIRED
Your own identifier for the record (policy number, order id, case id).
content_digest_hexstringREQUIRED
SHA-256 of the content bytes, lowercase hex.
salt_hexstringREQUIRED
64-char hex, returned once at sealing time. Store it: verification by digest needs it. Never public, never on-chain.
leaf_hash_hexstringREQUIRED
RFC 6962 leaf hash of the record's canonical leaf payload.
field_root_hexstring | nullREQUIRED
Root of the selective-disclosure field tree; null when no field_hashes were submitted.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
metadataany | nullREQUIRED
Must validate against the record type's metadata schema; disallowed PII keys are rejected (RR-2003).
batch_idstring | nullREQUIRED
Internal anchoring coordinate, set once the record is scheduled and anchored.
anchor_idstring | nullREQUIRED
Anchor row id, set once the record reaches its anchored outcome.
anchoredobject | nullREQUIRED
On-chain anchor evidence: tx signature, slot, block time and cluster. One shape on every plane that exposes it; null until the record is anchored.
txstringREQUIRED
slotinteger | nullREQUIRED
block_timestring | nullREQUIRED
clusterstring | nullREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
document_sealedbooleanREQUIRED
sealed_atstring | nullREQUIRED
When the seal request was accepted (ISO 8601).
anchored_atstring | nullREQUIRED
When the anchor finalized on RoxChain; null until the terminal outcome.
created_atstringREQUIRED
Row creation time (ISO 8601).
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
404Unknown record id or reference (plain problem document, no RR code).
curl
curl -sS https://api.roxregister.com/v1/records/rr_ref_<ULID> \
  -H "Authorization: Bearer $RR_API_KEY"
# Works with the record id (rec_<ULID>) or the public reference.
GET/v1/recordsAPI keyscope: read
List records
List the key-implied vault's records, newest first, with cursor pagination (limit 1-200, default 50) and filters on status, record type, external ref and creation window.
PARAMETERS
cursor (query)string
Last record id of the previous page
limit (query)integer
1-200, default 50
status (query)enum
pendingbatchedanchoredfailedrevokedsuperseded
record_type (query)string
Record type key
external_ref (query)string
from (query)string
created_at lower bound (ISO 8601)
to (query)string
created_at upper bound (ISO 8601)
200One page of records
RESPONSE FIELDS
recordsarray<object>REQUIRED
One page of records, newest first.
record_idstringREQUIRED
Stable record id (rec_<ULID>).
referencestringREQUIRED
Public reference (rr_ref_<ULID>), safe to print on documents and in QR codes.
record_typestringREQUIRED
A record type key configured for your vault.
external_refstring | nullREQUIRED
Your own identifier for the record (policy number, order id, case id).
content_digest_hexstringREQUIRED
SHA-256 of the content bytes, lowercase hex.
salt_hexstringREQUIRED
64-char hex, returned once at sealing time. Store it: verification by digest needs it. Never public, never on-chain.
leaf_hash_hexstringREQUIRED
RFC 6962 leaf hash of the record's canonical leaf payload.
field_root_hexstring | nullREQUIRED
Root of the selective-disclosure field tree; null when no field_hashes were submitted.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
metadataany | nullREQUIRED
Must validate against the record type's metadata schema; disallowed PII keys are rejected (RR-2003).
batch_idstring | nullREQUIRED
Internal anchoring coordinate, set once the record is scheduled and anchored.
anchor_idstring | nullREQUIRED
Anchor row id, set once the record reaches its anchored outcome.
anchoredobject | nullREQUIRED
On-chain anchor evidence: tx signature, slot, block time and cluster. One shape on every plane that exposes it; null until the record is anchored.
txstringREQUIRED
slotinteger | nullREQUIRED
block_timestring | nullREQUIRED
clusterstring | nullREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
document_sealedbooleanREQUIRED
sealed_atstring | nullREQUIRED
When the seal request was accepted (ISO 8601).
anchored_atstring | nullREQUIRED
When the anchor finalized on RoxChain; null until the terminal outcome.
created_atstringREQUIRED
Row creation time (ISO 8601).
next_cursorstring | nullREQUIRED
Pass back as ?cursor= for the next page; null on the last page.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS "https://api.roxregister.com/v1/records?status=anchored&limit=50" \
  -H "Authorization: Bearer $RR_API_KEY"
# Follow next_cursor with ?cursor= until it is null.
Sign envelopes
Rox Register Sign: create and track signature envelopes over your documents. An envelope carries the source PDF, its signer list and field layout, and an event trail. This surface covers envelope creation, reads and voiding; the signer ceremony itself and the sealed completion certificate are delivered by the Sign product's next phases. Envelope mutations sit behind the sign scope, granted explicitly on an API key.
POST/v1/envelopesAPI keyscope: signJWS required
Create and send an envelope
Create a signature envelope over a source PDF and send it in one call: multipart form with the file (PDF only, max 50MB), the envelope name, record type, signing order (sequential or parallel), an optional message and expiry, plus JSON-encoded signers and field placements. The server stores the source document, records its sha256 as source_digest_hex, and creates the envelope in status sent with its signers, field layout and the created/sent events. Assurance tier: only ses is accepted today; aes and qes are rejected explicitly until their phases ship. Requires the sign scope, granted explicitly on a key; seal or read keys are refused with RR-1005.
•PDF only, max 50MB; anything else is 415.
•The request JWS signs the sha256 of the RAW multipart body bytes.
•Idempotency-Key honored, scoped per vault: a replay returns the original envelope; the same key with a different document is refused (RR-2101).
•signers and fields are JSON-encoded text fields; field placement is in PDF points, origin bottom-left, and each field references its signer by signer_index (0-based index into the signers array).
•Billing: requires the sign_ses entitlement (RR-3101 otherwise) and HOLDS the sign_ses price, set in ROX (1 ROX = 1 USD parity), against the corporate's Rox coin wallet (RR-3102 names the shortfall). The hold is captured only when the envelope completes and assembles; it is released on void, decline and expiry, so an envelope that never completes is never charged.
multipart/form-data: a file part (PDF) plus name, record_type, signing_order, message?, expires_at?, signers (JSON array) and fields (JSON array).
201Envelope created and sent (the sign_ses price is held, not yet charged)
RESPONSE FIELDS
envelope_idstringREQUIRED
Stable envelope id (env_<ULID>).
statusenumREQUIRED
draftsentcompleteddeclinedvoidedexpired
signersarray<object>REQUIRED
The envelope's signers. On create: JSON array of {name, email, phone?, tier, order_index}; order_index must be unique per envelope.
idstringREQUIRED
namestringREQUIRED
emailstringREQUIRED
tierenumREQUIRED
Assurance tier for this signer. Only ses is accepted today; aes and qes are rejected explicitly until their phases ship.
sesaesqes
order_indexintegerREQUIRED
Position in the signing order (0-based, unique per envelope).
statusenumREQUIRED
pendingnotifiedviewedsigneddeclined
source_digest_hexstringREQUIRED
SHA-256 of the uploaded source PDF bytes, lowercase hex, computed server-side.
expires_atstring | nullREQUIRED
When the envelope expires (ISO 8601); null when no expiry was set.
402RR-3102: the gas wallet cannot cover the sign_ses price beyond live holds; the detail names the price, the available balance and the shortfall in ROX
RESPONSE FIELDS
typestringREQUIRED
URL of the error's documentation.
titlestringREQUIRED
Short human-readable error title.
statusintegerREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
detailstring
Human-readable specifics for this occurrence.
instancestring
The request path that produced the error.
codestring
Stable RR-xxxx code; see the Errors page.
403RR-3101: sign_ses is not enabled for this account
RESPONSE FIELDS
typestringREQUIRED
URL of the error's documentation.
titlestringREQUIRED
Short human-readable error title.
statusintegerREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
detailstring
Human-readable specifics for this occurrence.
instancestring
The request path that produced the error.
codestring
Stable RR-xxxx code; see the Errors page.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the sign scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-2002Record type unknown, or metadata violates its schema.
RR-2101Idempotency key reused with a different content digest.
RR-3101The sign_ses service is not enabled for this account (403); ask your Rox contact to enable it.
RR-3102Rox coin balance cannot cover the service price beyond funds already held (402); the detail names the price, the available balance and the shortfall in ROX.
curl
# The JWS covers the sha256 of the RAW multipart bytes, so build the body
# yourself with a fixed boundary instead of letting curl invent one.
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
SIGNERS='[{"name":"<signer-name>","email":"<signer-email>","tier":"ses","order_index":0}]'
FIELDS='[{"signer_index":0,"kind":"signature","page":1,"x":72,"y":120,"w":180,"h":48}]'
B="rr-$(uuidgen | tr -d '-')"
{
  printf -- '--%s\r\nContent-Disposition: form-data; name="name"\r\n\r\n<envelope-name>\r\n' "$B"
  printf -- '--%s\r\nContent-Disposition: form-data; name="record_type"\r\n\r\n<your-record-type>\r\n' "$B"
  printf -- '--%s\r\nContent-Disposition: form-data; name="signing_order"\r\n\r\nsequential\r\n' "$B"
  printf -- '--%s\r\nContent-Disposition: form-data; name="signers"\r\n\r\n%s\r\n' "$B" "$SIGNERS"
  printf -- '--%s\r\nContent-Disposition: form-data; name="fields"\r\n\r\n%s\r\n' "$B" "$FIELDS"
  printf -- '--%s\r\nContent-Disposition: form-data; name="file"; filename="<your-file.pdf>"\r\nContent-Type: application/pdf\r\n\r\n' "$B"
  cat <your-file.pdf>
  printf -- '\r\n--%s--\r\n' "$B"
} > /tmp/rr-envelope
TS=$(($(date +%s) * 1000)); NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
BODY_HASH=$(shasum -a 256 /tmp/rr-envelope | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/envelopes\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/envelopes \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: multipart/form-data; boundary=$B" \
  -H "X-RR-Key-Id: ish-prod-2026-01" -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" -H "X-RR-Signature: $SIG" \
  --data-binary @/tmp/rr-envelope
# 201: envelope created in status sent; store envelope_id and
# source_digest_hex (the sha256 of the uploaded source PDF).
GET/v1/envelopesAPI keyscope: read
List envelopes
List the key-implied vault's envelopes, newest first, with cursor pagination (limit 1-200, default 50) and an optional status filter.
PARAMETERS
cursor (query)string
Last envelope id of the previous page
limit (query)integer
1-200, default 50
status (query)enum
draftsentcompleteddeclinedvoidedexpired
200One page of envelopes
RESPONSE FIELDS
envelopesarray<object>REQUIRED
One page of envelopes, newest first.
envelope_idstringREQUIRED
Stable envelope id (env_<ULID>).
namestringREQUIRED
statusenumREQUIRED
draftsentcompleteddeclinedvoidedexpired
record_typestringREQUIRED
A record type key configured for your vault.
signing_orderenumREQUIRED
sequential: signers act in order_index order. parallel: all signers may act at once.
sequentialparallel
messagestring | nullREQUIRED
Optional note shown to signers.
source_digest_hexstringREQUIRED
SHA-256 of the uploaded source PDF bytes, lowercase hex, computed server-side.
expires_atstring | nullREQUIRED
When the envelope expires (ISO 8601); null when no expiry was set.
completed_record_idstring | nullREQUIRED
The sealed contract record linked when the envelope completes; null until then.
certificate_record_idstring | nullREQUIRED
created_atstringREQUIRED
Row creation time (ISO 8601).
updated_atstringREQUIRED
next_cursorstring | nullREQUIRED
Pass back as ?cursor= for the next page; null on the last page.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS "https://api.roxregister.com/v1/envelopes?status=sent&limit=50" \
  -H "Authorization: Bearer $RR_API_KEY"
# Follow next_cursor with ?cursor= until it is null.
GET/v1/envelopes/{id}API keyscope: read
Fetch one envelope
Fetch one envelope by id (env_<ULID>): envelope state, signers with their per-signer status, the field layout and the last 100 events, newest first. The document bytes never ride on this surface. Each signer carries signing_url, the link you hand to that signer: it opens the hosted signing surface where the ceremony runs. The link is stable across re-fetches, and possession only grants the ability to attempt the ceremony; signing itself is gated by an email one-time code sent to the signer. signing_url turns null once the signer or the envelope is terminal. Once a completed envelope is assembled, the view also carries completed_record_id (the sealed final signed document: collected values and signatures applied, seal stamp, platform signature; the sha256 of the final bytes is the sealed digest), certificate_record_id (the sealed completion certificate) and the expiring presigned downloads final_file_url and certificate_file_url.
•Signing links are consumed by the hosted signing surface, not by this API: hand each signer their signing_url and track progress here. The signing surface's own endpoints are internal and are not part of this reference.
•final_file_url and certificate_file_url expire; the Register does not hold customer documents long term, so download and archive both files.
PARAMETERS
id (path)stringREQUIRED
env_<ULID>
200The envelope
RESPONSE FIELDS
envelope_idstringREQUIRED
Stable envelope id (env_<ULID>).
namestringREQUIRED
statusenumREQUIRED
draftsentcompleteddeclinedvoidedexpired
record_typestringREQUIRED
A record type key configured for your vault.
signing_orderenumREQUIRED
sequential: signers act in order_index order. parallel: all signers may act at once.
sequentialparallel
messagestring | nullREQUIRED
Optional note shown to signers.
source_digest_hexstringREQUIRED
SHA-256 of the uploaded source PDF bytes, lowercase hex, computed server-side.
expires_atstring | nullREQUIRED
When the envelope expires (ISO 8601); null when no expiry was set.
completed_record_idstring | nullREQUIRED
The sealed contract record linked when the envelope completes; null until then.
certificate_record_idstring | nullREQUIRED
created_atstringREQUIRED
Row creation time (ISO 8601).
updated_atstringREQUIRED
signersarray<object>REQUIRED
The envelope's signers. On create: JSON array of {name, email, phone?, tier, order_index}; order_index must be unique per envelope.
idstringREQUIRED
order_indexintegerREQUIRED
Position in the signing order (0-based, unique per envelope).
namestringREQUIRED
emailstringREQUIRED
phonestring | nullREQUIRED
tierenumREQUIRED
Assurance tier for this signer. Only ses is accepted today; aes and qes are rejected explicitly until their phases ship.
sesaesqes
statusenumREQUIRED
pendingnotifiedviewedsigneddeclined
signed_atstring | nullREQUIRED
When this signer signed; null until then.
declined_reasonstring | nullREQUIRED
The signer's decline reason; null unless declined.
signing_urlstring | nullREQUIRED
The signer's link to the hosted signing surface. Stable across re-fetches; possession only grants the ability to attempt the ceremony (email OTP gates the signature itself). Null once the signer or envelope is terminal.
fieldsarray<object>REQUIRED
The envelope's field layout (signature/initials/date/text placements in PDF points, origin bottom-left).
idstringREQUIRED
signer_idstringREQUIRED
The signer a field or event belongs to (sgn_<ULID>); null for envelope-level events.
kindenumREQUIRED
signatureinitialsdatetext
pageintegerREQUIRED
1-based page number of the field placement.
xnumberREQUIRED
Field origin X in PDF points (origin bottom-left).
ynumberREQUIRED
Field origin Y in PDF points (origin bottom-left).
wnumberREQUIRED
Field width in PDF points.
hnumberREQUIRED
Field height in PDF points.
requiredbooleanREQUIRED
valuestring | nullREQUIRED
Filled by the signing ceremony; null until then.
eventsarray<object>REQUIRED
The envelope's event trail (last 100, newest first).
idstringREQUIRED
kindenumREQUIRED
createdsentviewedotp_sentotp_verifiedconsentedsigneddeclinedvoidedexpiredcompletedassembledassembly_failed
signer_idstring | nullREQUIRED
The signer a field or event belongs to (sgn_<ULID>); null for envelope-level events.
atstringREQUIRED
When the event happened (ISO 8601).
detailany | nullREQUIRED
Human-readable specifics for this occurrence.
final_file_urlstring | nullREQUIRED
certificate_file_urlstring | nullREQUIRED
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
404Unknown record id or reference (plain problem document, no RR code).
curl
curl -sS https://api.roxregister.com/v1/envelopes/env_<ULID> \
  -H "Authorization: Bearer $RR_API_KEY"
# Signers, field layout and the last 100 events; never the document bytes.
POST/v1/envelopes/{id}/assembleAPI keyscope: signJWS required
Assemble a completed envelope (recovery)
The assembly recovery path. When the last signer completes, the envelope is assembled automatically: the collected field values and signatures are drawn onto the source PDF, the seal stamp and the platform signature are applied, the final signed document and the completion certificate are sealed as records, and the held sign_ses price is captured. If that automatic run failed (an assembly_failed event on the envelope trail; the hold stays held and nothing is charged), POST this endpoint to re-run it. Idempotent: an already assembled envelope answers with its current state and already_assembled true; nothing re-runs and the fee is never captured twice. Only envelopes in status completed can be assembled. Requires the sign scope.
•No request body; the JWS covers the sha256 of the empty body.
•Both sealed records anchor through the normal pipeline; poll them or subscribe to record webhooks like any other record.
PARAMETERS
id (path)stringREQUIRED
env_<ULID>
200Envelope assembled (or already assembled): sealed record ids and fresh presigned downloads of the final signed document and the completion certificate
RESPONSE FIELDS
envelope_idstringREQUIRED
Stable envelope id (env_<ULID>).
statusconstant "completed"REQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
already_assembledbooleanREQUIRED
completed_record_idstringREQUIRED
The sealed contract record linked when the envelope completes; null until then.
certificate_record_idstringREQUIRED
final_file_urlstringREQUIRED
certificate_file_urlstringREQUIRED
409The envelope is not in status completed
RESPONSE FIELDS
typestringREQUIRED
URL of the error's documentation.
titlestringREQUIRED
Short human-readable error title.
statusintegerREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
detailstring
Human-readable specifics for this occurrence.
instancestring
The request path that produced the error.
codestring
Stable RR-xxxx code; see the Errors page.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the sign scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
404Unknown record id or reference (plain problem document, no RR code).
409Envelope is not in status completed (plain problem document, no RR code).
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)   # empty body
printf 'RRv1\nPOST\n/v1/envelopes/env_<ULID>/assemble\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/envelopes/env_<ULID>/assemble \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG"
# 200: sealed record ids + fresh presigned downloads of the final signed
# document and the completion certificate. Idempotent: an already assembled
# envelope answers already_assembled:true and nothing runs or charges twice.
POST/v1/envelopes/{id}/voidAPI keyscope: signJWS required
Void an envelope
Withdraw a sent envelope with a reason. Only envelopes in status sent can be voided; the void lands on the envelope's event trail and the audit log. Requires the sign scope.
•Voiding releases the sign_ses hold placed at creation; a voided envelope is never charged.
PARAMETERS
id (path)stringREQUIRED
env_<ULID>
REQUEST BODY · application/json
reasonstringREQUIRED
Why the envelope is being voided; recorded on the envelope's event trail.
200Envelope voided
RESPONSE FIELDS
envelope_idstringREQUIRED
Stable envelope id (env_<ULID>).
statusconstant "voided"REQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the sign scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
404Unknown record id or reference (plain problem document, no RR code).
409Envelope is not in status sent (plain problem document, no RR code).
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
BODY='{"reason":"<why-this-envelope-is-withdrawn>"}'
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/envelopes/env_<ULID>/void\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/envelopes/env_<ULID>/void \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# 200: status voided. Only envelopes in status sent can be voided.
Vaults
Provision and inspect vaults from the machine plane. Creating a vault is privileged (vaults.manage scope) and mirrors the Platform wizard, DKG ceremony and one-time API key included.
POST/v1/vaultsAPI keyscope: vaults.manageJWS required
Create a vault
Create a new vault for your corporate, exactly like the Platform wizard: the vault is created active, its 3-of-5 FROST-Ed25519 authority key is queued for DKG (the vault seals immediately; the authority key activates when the ceremony completes), the request-signing key you supply is registered active, and a fresh API key scoped to the NEW vault is minted. api_key_secret is returned exactly once; store it immediately. Privileged: the calling API key must carry the vaults.manage scope, which default seal/read keys never have.
•api_key_secret in the response is shown exactly once.
REQUEST BODY · application/json
namestringREQUIRED
slugstringREQUIRED
URL-safe identifier, unique within your corporate (lowercase letters, digits, dashes).
descriptionstring
first_request_key_jwkobjectREQUIRED
First request-signing key of the new vault, registered active during creation.
kidstringREQUIRED
Request-signing key id, the value you send in X-RR-Key-Id.
public_jwkanyREQUIRED
PUBLIC JWK of the new signing key. The private half never leaves you.
algenumREQUIRED
EdDSA (Ed25519, default) or ES256 for stacks without Ed25519.
EdDSAES256
201Vault created; api_key_secret is shown exactly once and the DKG ceremony is queued
RESPONSE FIELDS
vaultobjectREQUIRED
idstringREQUIRED
namestringREQUIRED
slugstringREQUIRED
URL-safe identifier, unique within your corporate (lowercase letters, digits, dashes).
descriptionstring | nullREQUIRED
statusenumREQUIRED
activepausedarchived
environmentenumREQUIRED
production or sandbox.
productionsandbox
authority_pubkey_b58string | nullREQUIRED
Current FROST authority public key (base58); null until the first DKG completes.
created_atstringREQUIRED
Row creation time (ISO 8601).
current_keyobject | nullREQUIRED
The vault's current authority key and its DKG state; null before the first key row exists.
statusenumREQUIRED
pending_dkgactiveretiredcompromised
algostringREQUIRED
thresholdintegerREQUIRED
Signatures required to anchor (3 of 5).
total_sharesintegerREQUIRED
Total MPC key shares (5).
pubkey_b58string | nullREQUIRED
Vault authority public key (base58) valid for the row's window.
activated_atstring | nullREQUIRED
When the key became active.
api_key_idstringREQUIRED
Id of the minted API key (revocable from the Platform).
api_key_secretstringREQUIRED
Full API key for the NEW vault. Shown exactly once; only a hash is stored.
dkgobjectREQUIRED
The queued key-generation ceremony for the new vault's authority key.
ceremony_idstringREQUIRED
DKG ceremony id (dkg_<ULID>).
statusconstant "requested"REQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the vaults.manage scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-2102Duplicate resource: the vault slug or request-signing kid already exists.
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
# Requires an API key carrying the vaults.manage scope. The JWS is
# signed by an active request-signing key of the CALLING key's vault;
# public_jwk is the PUBLIC half of the NEW vault's first signing key.
BODY='{"name":"<vault-name>","slug":"<vault-slug>","first_request_key_jwk":{"kid":"<new-key-id>","alg":"EdDSA","public_jwk":{"kty":"OKP","crv":"Ed25519","x":"<base64url-public-key>"}}}'
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/vaults\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/vaults \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# 201: api_key_secret for the NEW vault is shown exactly once; store it.
# dkg.status starts as requested; the authority key activates after DKG.
GET/v1/vaultAPI keyscope: read
Vault profile
The calling API key's vault profile: identity, slug, status, environment and the current authority key with its DKG state. A plain read; no request signing involved.
200The vault profile
RESPONSE FIELDS
idstringREQUIRED
namestringREQUIRED
slugstringREQUIRED
URL-safe identifier, unique within your corporate (lowercase letters, digits, dashes).
descriptionstring | nullREQUIRED
statusenumREQUIRED
activepausedarchived
environmentenumREQUIRED
production or sandbox.
productionsandbox
authority_pubkey_b58string | nullREQUIRED
Current FROST authority public key (base58); null until the first DKG completes.
created_atstringREQUIRED
Row creation time (ISO 8601).
current_keyobject | nullREQUIRED
The vault's current authority key and its DKG state; null before the first key row exists.
statusenumREQUIRED
pending_dkgactiveretiredcompromised
algostringREQUIRED
thresholdintegerREQUIRED
Signatures required to anchor (3 of 5).
total_sharesintegerREQUIRED
Total MPC key shares (5).
pubkey_b58string | nullREQUIRED
Vault authority public key (base58) valid for the row's window.
activated_atstring | nullREQUIRED
When the key became active.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS https://api.roxregister.com/v1/vault \
  -H "Authorization: Bearer $RR_API_KEY"
# The calling key's vault profile, including the current authority key.
Vault keys
Manage the request-signing keys behind the JWS, and read the vault authority key history that proof consumers rely on.
POST/v1/vaults/{id}/request-keysAPI keyscope: adminJWS required
Register a request-signing key
Register a new request-signing public JWK for the vault. The request itself must carry a JWS signed by an EXISTING active key of the vault, so possession of the API key alone can never mint signing keys. The new key is active immediately.
PARAMETERS
id (path)stringREQUIRED
vault_<ULID> (must be the API key-implied vault)
REQUEST BODY · application/json
kidstringREQUIRED
Request-signing key id, the value you send in X-RR-Key-Id.
public_jwkanyREQUIRED
PUBLIC JWK of the new signing key. The private half never leaves you.
algenumREQUIRED
EdDSA (Ed25519, default) or ES256 for stacks without Ed25519.
EdDSAES256
201Key registered and active
RESPONSE FIELDS
idstringREQUIRED
kidstringREQUIRED
Request-signing key id, the value you send in X-RR-Key-Id.
algenumREQUIRED
EdDSA (Ed25519, default) or ES256 for stacks without Ed25519.
EdDSAES256
statusenumREQUIRED
activeretired
activated_atstring | nullREQUIRED
When the key became active.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the admin scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
# public_jwk = the PUBLIC half of the NEW key. The request itself is
# signed by an EXISTING active key of the vault.
BODY='{"kid":"<new-key-id>","alg":"EdDSA","public_jwk":{"kty":"OKP","crv":"Ed25519","x":"<base64url-public-key>"}}'
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/vaults/vault_<ULID>/request-keys\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/vaults/vault_<ULID>/request-keys \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# 201: the new key is active immediately.
DELETE/v1/vaults/{id}/request-keys/{kid}API keyscope: adminJWS requiredapproval-gated
Retire a request-signing key
Retire a request-signing key. Approval-gated: X-RR-Approval-Id must reference an approved request_key_change approval whose payload hash covers {vault_id, kid}. Requests signed with a retired key are rejected from that moment on.
•X-RR-Approval-Id: an approved request_key_change approval covering {vault_id, kid}.
PARAMETERS
id (path)stringREQUIRED
vault_<ULID>
kid (path)stringREQUIRED
Request-signing key id (kid)
200Key retired
RESPONSE FIELDS
kidstringREQUIRED
Request-signing key id, the value you send in X-RR-Key-Id.
statusconstant "retired"REQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
retired_atstringREQUIRED
When the key was retired.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the admin scope.
RR-1101X-RR-Key-Id matches no active request-signing key.
RR-1003X-RR-Timestamp outside the 300s window (unix ms expected).
RR-1004X-RR-Nonce already used within its 10-minute TTL.
RR-1002JWS signature invalid.
RR-2001Vault not active (paused or archived).
RR-4001Approval required: no approved quorum entry referenced.
RR-4002Referenced approval expired (approvals live 72h).
curl
# Placeholders: request-key.pem = your registered Ed25519 signing key,
# ish-prod-2026-01 = its kid. Replace every <angle-bracket> value.
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS (skew limit 300s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)   # empty body
printf 'RRv1\nDELETE\n/v1/vaults/vault_<ULID>/request-keys/<kid-to-retire>\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X DELETE https://api.roxregister.com/v1/vaults/vault_<ULID>/request-keys/<kid-to-retire> \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "X-RR-Approval-Id: apr_<ULID>" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG"
# 200: retired. The approval must cover {vault_id, kid} (RR-4001/RR-4002).
GET/v1/vault/key-historyAPI keyscope: read
Vault authority key history
The vault's authority key history, oldest first, for proof consumers: old anchors verify against the key that was valid at their block time, not just the current key.
200Key history entries
RESPONSE FIELDS
[]array<object>REQUIRED
Top-level array; one entry per item.
pubkey_b58string | nullREQUIRED
Vault authority public key (base58) valid for the row's window.
active_fromstring | nullREQUIRED
Start of the key's validity window.
active_tostring | nullREQUIRED
End of the validity window; null for the current key.
rotation_txstring | nullREQUIRED
RoxChain transaction that anchored the rotation; null for the first key.
statusenumREQUIRED
pending_dkgactiveretiredcompromised
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS https://api.roxregister.com/v1/vault/key-history \
  -H "Authorization: Bearer $RR_API_KEY"
# Oldest first. Old anchors verify against the key valid at their block time.
Billing
Read-only cost reporting: the Rox coin deposit wallet and the gas ledger, with amounts as exact lamports and as decimal ROX strings. Behind the billing.read scope, which must be granted on an API key explicitly; seal or read keys do not gain it implicitly. Plain reads: the API key alone authorizes them.
GET/v1/gasAPI keyscope: billing.read
Gas deposit wallet and balance
The corporate's Rox coin deposit wallet (address, current balance in lamports and as a ROX string) and the RoxChain cluster this deployment anchors to. Read-only; built for cost reporting. Requires the billing.read scope, which must be granted on the API key explicitly: seal or read keys do not gain it implicitly, and calls without it are refused with RR-1005.
200Deposit wallet and cluster
RESPONSE FIELDS
walletobject | nullREQUIRED
The corporate's Rox coin deposit wallet; null until provisioned (automatic).
idstringREQUIRED
addressstringREQUIRED
RoxChain deposit address. Transfer ROX here to top up; balances sync automatically.
statusenumREQUIRED
activepaused
balance_lamportsinteger | nullREQUIRED
Current balance in lamports (1 ROX = 1e9 lamports).
balance_roxstring | nullREQUIRED
The same balance as a decimal ROX string, e.g. "5" or "0.00001".
clusterstringREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the billing.read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS https://api.roxregister.com/v1/gas \
  -H "Authorization: Bearer $RR_API_KEY"
# Requires a key carrying the billing.read scope (granted explicitly).
# balance_lamports and balance_rox are the same value; 1 ROX = 1e9 lamports.
GET/v1/gas/ledgerAPI keyscope: billing.read
Gas ledger
Cursor-paginated gas ledger, newest first: every deduction (anchor fees), refill (deposits) and adjustment, with exact lamports and decimal ROX string amounts plus the anchor id and RoxChain tx signature the entry paid for. Requires the billing.read scope.
PARAMETERS
cursor (query)string
Last entry id of the previous page
limit (query)integer
1-200, default 50
200One page of ledger entries
RESPONSE FIELDS
entriesarray<object>REQUIRED
One page of ledger entries, newest first.
idstringREQUIRED
wallet_idstringREQUIRED
The gas wallet the entry belongs to.
kindenumREQUIRED
deduction (anchor fees), refill (deposits) or adjustment.
deductionrefilladjustmentservice_fee
delta_lamportsintegerREQUIRED
Signed lamports delta; deductions are negative.
delta_roxstringREQUIRED
The same delta as a decimal ROX string, sign included.
balance_after_lamportsinteger | nullREQUIRED
Wallet balance after the entry, in lamports; null when unknown.
balance_after_roxstring | nullREQUIRED
Wallet balance after the entry as a decimal ROX string; null when unknown.
anchor_idstring | nullREQUIRED
Anchor row id, set once the record reaches its anchored outcome.
anchor_tx_signaturestring | nullREQUIRED
RoxChain tx of the anchor this entry paid for; null for non-anchor entries.
notestring | nullREQUIRED
created_atstringREQUIRED
Row creation time (ISO 8601).
next_cursorstring | nullREQUIRED
Pass back as ?cursor= for the next page; null on the last page.
ERRORS
RR-1001Invalid or unknown API key.
RR-1005API key lacks the billing.read scope.
RR-2001Vault not active (paused or archived).
curl
curl -sS "https://api.roxregister.com/v1/gas/ledger?limit=50" \
  -H "Authorization: Bearer $RR_API_KEY"
# Newest first. Follow next_cursor with ?cursor= until it is null.
Verify plane (public)
Deliberately unauthenticated and aggressively rate limited. Responses never contain salts, digests or private metadata, only the record type's whitelisted verify_fields.
GET/verify/{reference}public · no auth
Verify by reference
Public verification by record reference (rr_ref_<ULID>). No auth. Returns the verification result, issuer display names, anchor coordinates, lifecycle and the record type's whitelisted verify_fields. Never returns the salt or the digest.
PARAMETERS
reference (path)stringREQUIRED
rr_ref_<ULID>
200Verification outcome
RESPONSE FIELDS
resultenumREQUIRED
genuine, mismatch, revoked, superseded or not_found.
genuinemismatchrevokedsupersedednot_found
anchor_statusenumREQUIRED
Anchor pipeline state of the underlying record.
pendingbatchedanchoredfailed
record_typestring | nullREQUIRED
A record type key configured for your vault.
issuerobject | nullREQUIRED
Display names of the sealing corporate and vault.
corporate_namestringREQUIRED
vault_namestringREQUIRED
sealed_atstring | nullREQUIRED
When the seal request was accepted (ISO 8601).
anchoredobject | nullREQUIRED
On-chain anchor evidence: tx signature, slot, block time and cluster. One shape on every plane that exposes it; null until the record is anchored.
txstringREQUIRED
slotinteger | nullREQUIRED
block_timestring | nullREQUIRED
clusterstring | nullREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
lifecyclearray<any>REQUIRED
Lifecycle anchors (revocation, supersession); the latest one wins.
document_sealedbooleanREQUIRED
verify_fieldsanyREQUIRED
Public metadata only, whitelisted per record type. Never contains the salt or the digest.
curl
curl -sS https://api.roxregister.com/verify/rr_ref_<ULID>
# Public: no key, no login. Never returns the salt or the digest.
POST/verify/hashpublic · no auth
Verify by digest + salt
Public byte-exact verification: recompute the SHA-256 of the file you hold and send it with the record's salt. With a reference, the leaf is recomputed against that record; without one, candidates are resolved by content digest (first match wins). match flips to false, and result to mismatch, if even one byte changed. Mismatches are counted and alert the vault owner.
REQUEST BODY · application/json
content_digest_hexstringREQUIRED
SHA-256 of the content bytes, lowercase hex.
salt_hexstringREQUIRED
64-char hex, returned once at sealing time. Store it: verification by digest needs it. Never public, never on-chain.
referencestring
Public reference (rr_ref_<ULID>), safe to print on documents and in QR codes.
200Verification outcome with match flag
RESPONSE FIELDS
resultenumREQUIRED
genuine, mismatch, revoked, superseded or not_found.
genuinemismatchrevokedsupersedednot_found
anchor_statusenumREQUIRED
Anchor pipeline state of the underlying record.
pendingbatchedanchoredfailed
record_typestring | nullREQUIRED
A record type key configured for your vault.
issuerobject | nullREQUIRED
Display names of the sealing corporate and vault.
corporate_namestringREQUIRED
vault_namestringREQUIRED
sealed_atstring | nullREQUIRED
When the seal request was accepted (ISO 8601).
anchoredobject | nullREQUIRED
On-chain anchor evidence: tx signature, slot, block time and cluster. One shape on every plane that exposes it; null until the record is anchored.
txstringREQUIRED
slotinteger | nullREQUIRED
block_timestring | nullREQUIRED
clusterstring | nullREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
lifecyclearray<any>REQUIRED
Lifecycle anchors (revocation, supersession); the latest one wins.
document_sealedbooleanREQUIRED
verify_fieldsanyREQUIRED
Public metadata only, whitelisted per record type. Never contains the salt or the digest.
matchbooleanREQUIRED
True only if digest + salt recompute the anchored leaf. One changed byte flips it to false and result to mismatch.
curl
curl -sS -X POST https://api.roxregister.com/verify/hash \
  -H "Content-Type: application/json" \
  -d '{"content_digest_hex":"<sha256-hex-of-your-file>",
       "salt_hex":"<salt-from-sealing-time>",
       "reference":"rr_ref_<ULID>"}'
# match:true only if these exact bytes were sealed under this salt.
POST/verify/filepublic · no auth
Verify by file upload
Public verification by uploading the file itself (multipart: file, salt_hex, reference?). The file is stream-hashed in memory (50MB cap) and never persisted. Same response as verify-by-hash.
200Verification outcome with match flag
RESPONSE FIELDS
resultenumREQUIRED
genuine, mismatch, revoked, superseded or not_found.
genuinemismatchrevokedsupersedednot_found
anchor_statusenumREQUIRED
Anchor pipeline state of the underlying record.
pendingbatchedanchoredfailed
record_typestring | nullREQUIRED
A record type key configured for your vault.
issuerobject | nullREQUIRED
Display names of the sealing corporate and vault.
corporate_namestringREQUIRED
vault_namestringREQUIRED
sealed_atstring | nullREQUIRED
When the seal request was accepted (ISO 8601).
anchoredobject | nullREQUIRED
On-chain anchor evidence: tx signature, slot, block time and cluster. One shape on every plane that exposes it; null until the record is anchored.
txstringREQUIRED
slotinteger | nullREQUIRED
block_timestring | nullREQUIRED
clusterstring | nullREQUIRED
Which RoxChain the value lives on: production or sandbox. Nothing is ever presented as being on a chain it is not on.
statusenumREQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
pendingbatchedanchoredfailedrevokedsuperseded
lifecyclearray<any>REQUIRED
Lifecycle anchors (revocation, supersession); the latest one wins.
document_sealedbooleanREQUIRED
verify_fieldsanyREQUIRED
Public metadata only, whitelisted per record type. Never contains the salt or the digest.
matchbooleanREQUIRED
True only if digest + salt recompute the anchored leaf. One changed byte flips it to false and result to mismatch.
curl
curl -sS -X POST https://api.roxregister.com/verify/file \
  -F "file=@<your-file.pdf>" \
  -F "salt_hex=<salt-from-sealing-time>" \
  -F "reference=rr_ref_<ULID>"
# The file is stream-hashed in memory (50MB cap) and never persisted.
System
Liveness and operational status. Public.
GET/healthzpublic · no auth
Liveness probe
Plain liveness check. Returns 200 with {status: "ok"} while the API process is serving.
200Service is alive
RESPONSE FIELDS
statusconstant "ok"REQUIRED
pending and batched are both pre-anchor states: the record is accepted and awaiting its single terminal outcome (anchored or failed). Treat them identically. revoked and superseded are lifecycle states applied after anchoring.
curl
curl -sS https://api.roxregister.com/healthz
# 200 {"status":"ok"}
GET/v1/statuspublic · no auth
Platform status
Operational snapshot: API health, current anchoring backlog in seconds, RoxChain health as seen by the platform, and whether this deployment is the sandbox. Public, unauthenticated.
200Current platform status
RESPONSE FIELDS
apiconstant "ok"REQUIRED
Constant ok when the API is serving.
anchor_backlog_secondsinteger | nullREQUIRED
Current anchoring backlog in seconds. An operational gauge, not a promise.
chainobjectREQUIRED
RoxChain health as seen by the platform.
slotinteger | nullREQUIRED
healthenumREQUIRED
okdegradeddownunknown
sandboxbooleanREQUIRED
True when this deployment is the sandbox environment.
curl
curl -sS https://api.roxregister.com/v1/status