DOCUMENTATION

API

The Compliance API: one edge function, every verb it serves.

Verbs as the function lists them

PathDescription
(bare path)POST — adjudicate a settlement. verify scope, one credit.
authority/checkPOST — re-derive an authority certificate's digest from its own body. Free, unauthenticated, reads nothing.
receipts/{digest}GET — a receipt this account or its organization recorded, as it was served. read or verify scope, free.
analyze/issuerPOST { issuer } — an issuer's controls, read live from the validated ledger. read or verify scope, free.
analyze/addressPOST { address } — balance, reserve, owner count and credentials, read live. read or verify scope, free.
analyze/transactionPOST { hash } — a transaction's type, parties, result and delivered amount, read live. read or verify scope, free.

One Edge Function, noshashi-verify, serves every verb. Paths are relative to

https://<project>.supabase.co/functions/v1/noshashi-verify

GET on the bare path (no key needed) lists every verb, the domain registry and the published rate limits.

Verbs

VerbMethodScopeCostWhat it answers
(bare path)POSTverify1 creditAdjudicate a settlement: GO / HOLD / NO-GO, with a digested receipt.
receipts/{digest}GETread or verifyfreeA receipt your account or organization recorded, exactly as it was served.
analyze/issuerPOSTread or verifyfreeAn issuer's controls, read live from the validated ledger.
analyze/addressPOSTread or verifyfreeBalance, reserve, spendable XRP, owner count and credentials, read live.
analyze/transactionPOSTread or verifyfreeA transaction's type, parties, final result and delivered amount.
authority/checkPOSTnonefreeWhether an authority certificate's digest is the digest of its own body.

Auth. Every verb except authority/check needs Authorization: Bearer nsh_live_….

Plan. Every verb needs the account's compliance_api entitlement.

Rate limits. Every verb draws on the same per-key, per-tier rate limit. The limits are durable and enforced in Postgres.

Scopes. A verify key can also read. A read key cannot spend credits.

Refused keys. One message covers every refusal, whether the key is unknown, revoked, expired or lacks the scope. This is deliberate, so the endpoint can't be used to test whether a key was ever valid.

Organization keys. A key issued to an organization records its receipts as that organization's. They are visible to its members and sent to its receipt_created webhooks (see WEBHOOKS.md). The same key can read any receipt the organization holds.

GET receipts/{digest}

digest is the 64-character hex digest printed on the receipt. The API matches it case-insensitively.

{
  "receipt": { "verdict": "go", "domain": "DEX-US", "subject": "r…", "checks": [ … ], "digest": "…", "evaluated_at": "…" },
  "digest": "…",
  "recorded_at": "2026-09-24T01:34:12Z",
  "original_request_id": "…",
  "scope": "account" | "organization"
}

The response is 404 receipt_not_found when the digest was not recorded for your account or organization. A receipt that belongs to someone else gets the same answer, so its existence is not disclosed.

POST analyze/issuer — { "issuer": "r…" }

{
  "address": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
  "ledger_index": 107193471,
  "domain": "bitstamp.net",
  "controls": {
    "require_auth": false,
    "global_freeze": false,
    "no_freeze": false,
    "clawback_enabled": false,
    "default_ripple": true,
    "deposit_auth": false,
    "master_key_disabled": true,
    "transfer_fee_bps": 15
  },
  "flags_raw": 10092544,
  "source": "XRPL mainnet, validated ledger, read live from the public rippled servers",
  "read_at": "…"
}

The example above is Bitstamp's account as read at ledger 107,193,471. The test suite checks the same reply against rippled's own account_flags reading of that ledger.

POST analyze/address — { "address": "r…" }

  • reserve_xrp: computed from the reserve figures the server reports at the time of the read. It is not a constant. If those figures cannot be read, reserve_xrp and spendable_xrp are null; the API never falls back to a remembered value.
  • Never-funded address: the response has "funded": false and "account": null, rather than a zero balance.
  • Credentials: each one lists its issuer, its decoded type, whether it has been accepted, and its expiry.

POST analyze/transaction — { "hash": "…" }

  • Validated transactions: only a validated transaction has a final result, so result_final and succeeded are set only when validated is true. Otherwise succeeded is null.
  • delivered_amount: the amount the ledger says was actually delivered. It is null for a transaction that delivers nothing, and "unavailable" where rippled says so (partial payments before 2014).
  • Lookup across nodes: if the first public node does not hold the transaction, the API asks the full-history node before answering 404 transaction_not_found.

Check results: five states

Every check carries passed, and may carry state: PASS, FAIL, REVIEW, INSUFFICIENT_DATA or NOT_APPLICABLE. Without state, the state is implied: PASS when passed, otherwise FAIL for a blocking check and REVIEW for an advisory one.

  • INSUFFICIENT_DATA (passed: false) means the evidence the rule needs could not be read. It is no answer, not a failure.
  • NOT_APPLICABLE (passed: true) means the rule does not apply to this subject, for example supply concentration for an issuer with nothing outstanding.

In a digest a check is [id, passed], or [id, passed, state] when the state is one of those two. Every receipt and certificate issued before the states existed therefore keeps its bytes. authority/check accepts state on each check and binds it the same way; any other value is refused with invalid_checks.

Errors

Every response is JSON and carries a request_id. Every refusal is { "error": "<stable code>", "message": "…" }.

StatusCodeWhen
400invalid_digest, invalid_address, invalid_hash, invalid_json, invalid_subject, invalid_amountThe input is malformed. Addresses must pass their base58 checksum.
401unauthorizedNo usable key with the required scope.
402quota_exhaustedAdjudication only: no credits left.
403feature_not_enabled, entitlement_expiredThe account's plan doesn't include the API, or the entitlement has lapsed.
404receipt_not_found, account_not_found, transaction_not_found, unknown_domain, unknown_verb
405method_not_allowedreceipts/* takes GET; analyze/* takes POST.
413 / 415payload_too_large, unsupported_media_type
429rate_limitedComes with Retry-After.
502ledger_unavailableMainnet could not be read. Nothing was charged.

Not served by the API yet

These run in the app today, against the organization's governed policy, but have no API verb:

  • policy evaluation against an organization's custom policy (policy/evaluate);
  • simulation, which re-decides recorded verdicts under a draft policy (simulation/run);
  • investigations (read or write).

The adjudication verb evaluates the published domain registry, not an organization's custom policy.

Generated from supabase/functions/noshashi-verify/index.ts · docs/api/COMPLIANCE_API.md