AUDRIEDOCS
ReferenceRecords

Authenticate a document as your organisation

POST/v1/records

Authenticates a document as the calling organisation, signing its hash with the organisation's own post-quantum key and anchoring the result to Hedera. This is the only operation on this API that creates anything.

Send a hash. Audrie never receives the document.

A test-prefixed credential cannot reach this operation. There is no sandbox ledger: this endpoint signs with your organisation's production key and anchors a permanent, public Hedera message that cannot be withdrawn, so a key labelled test is never granted records:write at all and is refused here with 403. Mint a live credential when you are ready to create records that are real.

What this is, and what it is not

The signature attributes to an organisation, not to a person. No individual is accountable for it: there is no identity-verified signer and no WebAuthn ceremony behind an API call. A verifier sees that difference — these records answer verified_org rather than verified, carry signerType: org on the match, and at the organisation tier carry your organisation's registered name in signerOrg.

Use it where an organisation is the right signer: bulk authentication of documents your organisation issues. Where a named person must be accountable, have that person authenticate in the portal instead.

Idempotency

Retrying is safe, and you do not need an idempotency key: the document hash is the key.

Your organisation can hold one active record per document hash. A repeat call for a hash you already hold returns that record with 200 and creates nothing; a first call returns 201. The body is identical either way, because after a timeout you cannot know which call you made — branch on the status only if you care.

A record whose anchor ultimately failed stops counting, so a later call for that hash creates a new record rather than returning the failed one forever.

Anchoring is asynchronous, and it can fail

On a 201, anchorState is pending or accepted, and reaches anchored at Hedera finality — typically 3–5 seconds. On a 200 retry it is whatever the existing record has already reached, which may be anchored. Branch over all three values, not two. Poll POST /v1/verifications with the same hash rather than this operation.

Read the verdict, not the clock, and expect three outcomes. Until the record reaches the ledger, verification answers no_record — it reports only records that have been accepted for anchoring, so a record you have just created is briefly invisible there. Then:

The verdict becomesMeaningWhat to do
pending_anchorOn the ledger, awaiting consensus.Wait retryAfterSeconds and poll again.
verified_orgAnchored. Done.Nothing.
still no_record after ~3 minutesThe anchor failed before the message was submitted. The record is abandoned and stops being active.Call POST /v1/records again for this hash. It will create a new record rather than returning the abandoned one.
stuck at pending_anchor for many minutesThe message reached the ledger but consensus has not been reconciled. This one does not self-resolve by retrying, and re-authenticating will not help — the record is still active, so a repeat call returns it unchanged.Report it with the recordId.

An anchored record cannot be withdrawn. A Hedera message is permanent. Revocation marks a record as no longer in good standing rather than removing it — see POST /v1/records/{recordId}/revoke.

Errors

A 400 means originalDocHash is not sha256: followed by 64 lowercase hex characters, filename is longer than 500 characters or contains a control or invisible formatting character, or the body is not a JSON object. error.details names the field. Uppercase hex is refused rather than normalised: the hash is used verbatim inside the signature and the ledger message, so two spellings would be two separate permanent records for one document.

The 402 and 403 each have more than one cause. Branch on error.code, and read the response descriptions below — they say which causes exist and which of them mean "wait" rather than "fix something".

AuthorizationBearer <token>

Authorization: Bearer aud_live_…

Optional on POST /v1/verifications, which answers without any credential. Required to list or read verifications back.

Send exactly one Authorization header. A credential that is present and does not work is refused rather than downgraded to the anonymous tier.

Issue a key in the portal under Settings → Organisation → API credentials; it is shown once, at creation.

Two scopes exist. verifications:read covers every verification operation and is on every key. records:write covers POST /v1/records and is granted only on a live credential whose organisation holds the organisation-issued authentication add-on at the moment the key is minted — a test key never receives it, because there is no sandbox ledger and the records it would create would be permanent — scopes are fixed then and never widen afterwards, because a credential's recorded scopes are what an auditor reads to know what that key could do on a given date. Buying the add-on later means minting a new key; the old one keeps verifying.

Scopes, rotation, revocation, the live and test prefixes and what to do about a leaked key: https://audrie.io/docs/authentication

In: header

Request Body

application/json

What to authenticate. It names no organisation and no signer: both come from the API credential you present.

There is no orgId or credentialId field, and there never will be. Sending one is not an error — unknown properties are ignored — but it has no effect whatsoever. Tenancy is taken from the credential and from nothing else, so a key can only ever write records for its own organisation.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/records" \  -H "Content-Type: application/json" \  -d '{    "originalDocHash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"  }'
{  "recordId": "9a1b7c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",  "originalDocHash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",  "authenticatedAt": "2026-09-14T03:21:44Z",  "anchorState": "anchored",  "verifyUrl": "https://app.audrie.io/verify/9a1b7c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",  "anchor": {    "topicId": "0.0.4915623",    "transactionId": "0.0.1234@1746223981.000000000",    "hashscanUrl": "https://hashscan.io/mainnet/transaction/0.0.1234@1746223981.000000000"  }}

Read one verification back GET

Returns one verification as it was recorded. Scoped to the calling key's organisation and environment. `matches` is always `[]` here. Matches are derived from the document hash rather than stored, so this operation answers "what did we tell you, and when". To get current matches, `POST` the hash again, which is the operation that computes them. ## Errors A `404` means the id does not exist, belongs to another organisation, or belongs to another environment. These are not distinguished, and a malformed id answers the same way. Existence is not disclosed.

Revoke a record your organisation authenticated POST

Marks a record your organisation authenticated as no longer in good standing, and anchors that decision to Hedera. **Revocation is permanent.** It cannot be undone, by you or by us. The record is not removed — a Hedera message cannot be withdrawn — and verification continues to report it, with a `revoked` verdict and the reason you gave. ## The credential must carry `records:revoke` This scope is granted only when you ask for it as you mint a credential. A credential that can authenticate documents cannot revoke them unless you requested both, and a credential minted without it never gains the scope — mint a new one. Ask for it on the credentials you intend to revoke with, and not on the ones your batch jobs carry. A credential holding this scope can revoke **any** record your organisation authenticated, including records created by a different credential. That is what makes it the right tool after a credential is lost: mint a new credential with `records:revoke` and clean up with it. **A credential granted `records:revoke` also carries `records:write`.** There is no revoke-only credential, so a key you mint to clean up after an incident can also authenticate documents as your organisation — protect it as a signing key, and revoke it when the cleanup is done. A `test`-prefixed credential is never granted this scope. ## What you can revoke Records your **organisation** authenticated through this API. Documents a person authenticated in the Audrie app are not revocable here — only that signer can revoke those, from the app. Revoking is not a way to correct a mistake in the document itself. A corrected document is a new document with a new hash: authenticate the new one, and revoke the old one as `SUPERSEDED`. ## Errors A `403` has two causes, and they read differently. A credential without `records:revoke` is told so, and the message names the scope. Otherwise the record is not one your organisation can revoke — and that answer deliberately does not distinguish a record belonging to another organisation from one a person authenticated in the Audrie app. A `409` has two causes and does not distinguish them. **Do not retry blindly**: if the record is already revoked, retrying never succeeds, because revocation is permanent. If its anchor has not yet reached consensus, retry once `POST /v1/verifications` reports `verified_org`. That call is also how you tell the two apart — an already-revoked record reports `revoked`.