API Reference

Errors

Every error the ACTA API returns uses one JSON envelope with a stable, machine-readable code. Branch on error, never on message.

Error envelope

json
{
  "error": "machine_readable_code",
  "message": "Human readable description",
  "details": { "optional": "context" },
  "request_id": "..."
}
  • error (always): stable code to branch on.
  • message (usually): human-readable description; wording may change between releases.
  • details (sometimes): structured context, e.g. { "path", "method" } on 404 not_found.
  • request_id (always): correlation id; include it in support requests. 5xx responses also carry trace_id.
  • retry_after (on 429): seconds to wait, mirrored in the Retry-After header.

Unknown paths return 404 not_found; unhandled exceptions return 500 internal_error without leaking internals.

HTTP status codes

StatusMeaning
200 / 201Success (201 for submits and key creation)
400Invalid parameters or malformed request
401Missing, invalid, or expired API key
403Role or ownership not allowed, or forbidden origin
404Resource or route not found
409Conflict (already exists / already revoked / stale state)
410Gone (revoked vault)
413Payload or field too large
429Rate limit exceeded
500Internal error (carries trace_id)
503Dependency unavailable (e.g. rate limiter, contract not initialized)

Authentication & authorization

CodeStatusMeaning & what to try
401 (missing/invalid key)401No X-ACTA-Key header, unknown key, or expired key. Try: create a key (see ) and send it on every /contracts/* request.
forbidden_origin403POST /public/api-keys called from a non-allowlisted Origin. Try: create keys from the dApp.
network_mismatch400metadata.network does not match the base URL's network. Try: align the body with the host you call.
Ownership violation403On issue, batch-issue, list-vc-ids, get-vc, and push, the owner / fromOwner must equal the wallet bound to your API key (admin keys exempt). Try: use the key created for that wallet.

Validation errors

Returned as 400 with a field-specific code:

CodeMeaning
owner_required / owner_invalidMissing or malformed vault owner (G...)
issuer_required / issuer_invalidMissing or malformed issuer address
vcId_requiredMissing credential id (max 64 chars)
vcData_requiredMissing credential payload (max 10,000 chars)
userSalt_invalidSalt must be 32 bytes hex (64 hex chars)
vaultContract_invalidMust be a valid C... contract id
limit_too_largePagination limit above 200
batch_empty / batch_too_largeBatch must contain 1 to 5 credentials
vcs[i].vcId_too_long / vcs[i].vcData_too_longA batch entry exceeds the field caps
payload_too_largeRequest body over the limit (413)

Issuer DID errors

The full family (issuerDid_required, issuerDid_invalid, issuerDid_unresolvable, issuerDid_controller_mismatch, issuerDid_network_mismatch, issuerDid_deactivated, issuerDid_registry_unavailable) is documented with remedies in , and the did:stellar background lives in the .

Rate limiting

Requests are limited per API key over a sliding 60-second window, with separate read and write buckets by role (see for the table).

CodeStatusNotes
rate_limit_exceeded429Read bucket exhausted; wait Retry-After seconds
write_rate_limit_exceeded429Write bucket exhausted
rate_limit_unavailable503The rate limiter backend is down; retry later

Watch the X-RateLimit-* and X-WriteRateLimit-* headers to pace clients proactively.

Prepare/Submit errors

CodeStatusMeaning & what to try
signed_xdr_invalid400The submitted signedXdr cannot be parsed. Try: sign the exact xdr returned by prepare, with the returned network passphrase.
simulation_error400Soroban simulation failed while preparing; the message includes the on-chain reason (often a contract error, see below).
tx_submit_error500Submission to the network failed. Try: retry with the same Idempotency-Key.

Contract errors over HTTP

When a Soroban contract rejects the operation, the API maps Error(Contract, #N) to a stable code and a fitting HTTP status:

CodeStatusOn-chain cause
vault_already_exists409Vault already deployed at that owner + salt
vault_revoked410Vault was revoked; writes blocked
vault_not_initialized404No vault for this owner yet
vc_not_found404No credential with that vcId
vc_already_exists409vcId already used in this vault
vc_already_revoked409Credential is already revoked
issuer_not_authorized403Issuer is blocked (denied) for this vault
invalid_vault_contract400vaultContract does not match the called vault
vault_full409Vault reached its max active credentials
input_too_long413A field exceeds its on-chain cap
batch_too_large / batch_empty400Batch size outside 1 to 5
issuer_list_too_long400Denied-issuer list is full (1,000)
fee_out_of_bounds400Batch fee total overflowed
contract_not_initialized503Contract state missing; check the network
no_pending_admin404Admin accept without a pending nomination

For the underlying on-chain semantics (and USDC/XLM fee failures like missing trustlines), see .

Idempotent retries

Contract write routes accept an Idempotency-Key header (up to 200 chars). The first response for a key is cached for 24 hours; retries replay it with the Idempotency-Replayed: true header. Use it to make submit retries safe after timeouts or tx_submit_error.