Knowledge Base

Browse the concepts behind Truestamp. Follow the links between concepts, or search across everything.

JSON:API HTTP Surface

The authenticated JSON:API REST surface at /api/json, its API-key or OAuth bearer auth, the OpenAPI spec plus Swagger UI and ReDoc discovery pages, resource-oriented endpoints, tenant selection, filtering, and pagination conventions.

Overview

Truestamp exposes a JSON:API-compliant REST surface under the base path /api/json. It is an authenticated API: every endpoint requires a credential except the machine readable OpenAPI specification and the two documentation pages that render it. Callers authenticate with an Authorization: Bearer header carrying either a Truestamp API key or an OAuth 2.1 access token. The surface is generated from Truestamp’s data model, so resources such as items, blocks, entropy observations, teams, and webhook endpoints appear as conventional JSON:API resource endpoints with standard filtering, sorting, and pagination. Proof generation and verification ride the same surface as action-style routes. Two hosted documentation pages, Swagger UI and ReDoc, render the live specification so a developer can browse every operation and its request and response shapes.

Base path and authentication

All JSON:API traffic is served under /api/json. Individual resources hang off that prefix, for example /api/json/items for the items collection and /api/json/items/:id for a single item.

Authentication is mandatory. The exceptions are the discovery surfaces: the OpenAPI document at /api/json/open_api and the two documentation pages that render it (/api/json/swaggerui and /api/json/redoc). All three are served without a credential so tooling and developers can discover the API. Every other route rejects an anonymous request with a 401 Unauthorized.

Present a credential in the standard bearer form:

Authorization: Bearer <api-key-or-oauth-access-token>

Two credential types are accepted on the same header:

  • An API key issued to your account.
  • An OAuth 2.1 access token obtained from Truestamp’s authorization server.

For OAuth callers, read operations (GET and HEAD) require the api:read scope and mutating operations (POST, PATCH, DELETE) require api:write. There is one carve-out: a small, explicitly enumerated set of POST routes that only read require api:read too. Ash mounts a generic action as a POST route even when it writes nothing, so the verb alone would over-demand the write scope. The read-only POST routes are the three knowledge-base tools (/api/json/kb/search, /api/json/kb/fetch, /api/json/kb/links), the seven utilities (/api/json/utilities/hash, /verify-hash, /uuidv7-timestamp, /ulid-timestamp, /resolve-id, /jcs-canonicalize, /server-time), and the two proof operations (/api/json/proof/generate, /api/json/proof/verify). Every other POST, PATCH, and DELETE still requires api:write. API-key callers are not scope-limited in this way. A request with a blank or malformed bearer token is rejected with a 401 rather than a server error.

curl -H "Authorization: Bearer YOUR_API_KEY" \
https://www.truestamp.com/api/json/items

Requests are rate limited per authenticated caller, falling back to your network address when a request carries no credential. Exceeding the budget returns 429 Too Many Requests with a JSON:API error object whose code is "rate_limited" and whose meta carries retry_after_ms, the wait in milliseconds, and limit, the budget that was reached. Wait that long and retry.

A refusal from the API pipeline also carries a Retry-After header in whole seconds. Some limits are enforced deeper, on the operation rather than the request, and those refusals carry the same body without the header, so read meta.retry_after_ms and treat the header as a convenience when it is present.

Limits are enforced across the whole fleet with eventual consistency: each server counts what it admits and shares that count with its peers within a fraction of a second, so a burst of many requests sent at the same instant can be admitted a few percent past the limit before the servers agree. The per-caller budget is a token bucket: you may spend the whole budget at once and then continue at the refill rate, and a refusal’s Retry-After is your own short wait for the next token. The per-address guard below is a window aligned to the clock minute, so add a little random jitter to its Retry-After rather than retrying on the exact second; otherwise every refused client retries together at the boundary.

Before any credential is examined, each API surface also limits requests per client address (an IPv4 address or an IPv6 /64) to 1,000 per minute. A flood of missing or bad credentials is therefore refused with the same 429 before it reaches the database, and a request can be refused before it is authenticated. Authenticated traffic is charged against both the per-address and the per-user budget.

Two operations are also limited per call, on every surface and inside an MCP script: proof generation at 300 per minute per account (3,000 fleet-wide) and proof verification at 30 per minute per account, with a fleet-wide ceiling of 120 per minute on verifications that contact the public chains. A refusal from these carries the usual rate_limited body without a Retry-After header.

Request size limits

A request body to /api/json may be at most 528,384 bytes: twice 262,144 plus 4,096. That leaves room for a proof of up to 256 KB (262,144 bytes) even when it is sent escaped inside a JSON string, which can double its size, plus the rest of the document. The limit is checked before the body is parsed.

  • Over the limit: 413. A body over the limit is refused with 413 Content Too Large and a JSON:API error whose code is request_too_large and whose meta.max_bytes is the limit. A declared Content-Length over the limit is refused before any of the body is read. A JSON or form-encoded body sent without one, such as a chunked upload, is read only until it passes the limit. On HTTP/1.x a 413 also closes the connection.
  • No length where one is needed: 411. Any other non-empty body sent without a Content-Length, such as a multipart upload, a body of another content type, or a body on a GET, is refused with 411 Length Required and the code length_required. Send it again with its Content-Length. An empty body, as on an ordinary GET, passes.

A JSON body sent with POST, PATCH or DELETE (application/vnd.api+json or application/json), the normal case for this API, is never asked for its length.

Error bodies

Errors come back as a JSON:API errors array. Each entry carries status, code, a human-readable detail, and, where the error is about a specific input, a source pointer naming it.

An error with a bound value renders that value in both forms. detail reads as prose with the value substituted, and meta carries the bindings separately for a caller that would rather branch on the number than parse the sentence:

{
"errors": [
{
"status": "400",
"code": "invalid_argument",
"source": { "pointer": "/data/attributes/limit" },
"detail": "must be less than or equal to 12",
"meta": { "max": 12 }
}
]
}

Prefer code and meta for anything programmatic. detail is prose and may be reworded; the pair is offered so that reading it is never the only option.

Discovery: OpenAPI, Swagger UI, and ReDoc

The API is self-describing. The full OpenAPI specification is generated from the live data model and served as JSON at /api/json/open_api. Because that document is the canonical source of every operation, path, parameter, and schema, it is the most reliable reference for exactly what the API accepts and returns.

Two human-friendly documentation UIs render that same specification:

  • Swagger UI at /api/json/swaggerui: an interactive explorer that lists every endpoint and lets you inspect request and response schemas.
  • ReDoc at /api/json/redoc: a clean, readable reference view of the same spec.

Both documentation pages and the raw spec are reachable without authenticating, so you can survey the API before wiring up credentials. Actually calling the resource endpoints still requires a bearer token.

Resource endpoints and conventions

The surface follows JSON:API conventions. Each resource is exposed at a base route, and operations map to HTTP verbs:

  • GET /api/json/<resource>: list a collection.
  • GET /api/json/<resource>/:id: fetch a single record by id.
  • POST /api/json/<resource>: create a record.
  • PATCH /api/json/<resource>/:id: update a record.
  • DELETE /api/json/<resource>/:id: delete a record.

Not every resource exposes every verb. Many are read-only, and the write verbs appear only where a matching action is published: for example, the items resource supports listing, fetching by id, creating, and updating under /api/json/items, while webhook endpoints additionally support delete. Some resources also expose action-style endpoints; the proof resource, for instance, accepts POST /api/json/proof/generate to generate a proof and POST /api/json/proof/verify to verify one, and the knowledge base accepts POST /api/json/kb/search, POST /api/json/kb/fetch, and POST /api/json/kb/links to search, read, and traverse the links between concepts (the same audience-gated knowledge-base reads the in-app assistant and connected AI agents use). The OpenAPI spec is the authoritative list of which resources and operations exist.

POST /api/json/proof/generate takes the subject id and an explicit subject type (there is no auto detection), an optional format of json or cbor, and an optional witnesses array choosing which witness details ride in the bundle. A witness is a public record that existed before the submission and that the subject’s fingerprint commits to, so witnesses open the submitted-after edge of the submission window. Omit witnesses to carry every one of them, which is what makes a downloaded bundle stand alone; send [] for a compact bundle that carries none; or name a subset from block, entropy_stellar, entropy_nist, entropy_bitcoin, and signing_key_event. An unrecognized name is rejected as invalid_witness, and asking for a witness the subject never captured simply omits it. See the proof bundle wire format for what each witness carries.

Transient refusals are worth telling apart from terminal ones, because only the transient ones are worth retrying. subject_not_ready and no_external_commitments are transient: the subject or its block has not reached a verifiable stage yet, and the next commit gets it there. signing_unavailable is transient too: Truestamp could not sign the proof just now, so the proof is temporarily unavailable. So are witness_unavailable, where Truestamp could not find a witness the subject’s fingerprint commits to (meta.witness names it) and issues no proof rather than one without that witness, and lookup_failed, where Truestamp could not read a record the proof is built from. In all three cases nothing about the subject is at fault, and a later request can succeed. subject_not_recomputable is terminal: the item’s stored claims or metadata no longer reproduce the hash committed at submission, so no proof over them could be verified by anyone, and meta.drifted names which half is at fault. If generation fails its own internal verification for any other reason, the detail and meta.failed_steps name the verification steps that failed rather than reporting only that something did.

One route to know early: GET /api/json/users/me (the currentUser operation) returns the authenticated caller’s own user record. It takes no parameters and is the canonical first call to confirm that a new API key or OAuth token works:

curl -H "Authorization: Bearer YOUR_API_KEY" \
https://www.truestamp.com/api/json/users/me

A 200 with your own user id in data.id confirms the credential; a 401 means the credential was missing, expired, or malformed.

Two sibling user routes exist on the same resource: GET /api/json/users (listUsers) and GET /api/json/users/:id (getUser). Both are visibility-filtered to the caller: the list returns you plus the members of teams you share, a get by id resolves only for yourself or a shared-team member, and sensitive fields are restricted field-by-field regardless of who is asking. There is no way to enumerate users outside your own teams.

Responses can embed related records. A request may ask for related resources to be included alongside the primary data (for example, an item can include its team, its creator, and the block it was committed to), following the JSON:API include convention.

Tenant selection for multi-tenant resources

Some resources are scoped to a team. For those, the request selects which team it operates within, either with a tenant request header or a tenant query parameter carrying the team id:

curl -H "Authorization: Bearer YOUR_API_KEY" \
-H "tenant: <team-id>" \
https://www.truestamp.com/api/json/items
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://www.truestamp.com/api/json/items?tenant=<team-id>"

When no tenant is supplied, the request falls back to your default team preference if one is set and you still have a write-capable role on it, and otherwise to your personal team. A tenant you are not a member of is rejected with 403 Forbidden.

Utility endpoints

Seven stateless utility operations live under POST /api/json/utilities: hash, verify-hash, uuidv7-timestamp, ulid-timestamp, resolve-id, jcs-canonicalize, and server-time. They compute hashes and canonical JSON, extract the time embedded in an identifier, classify opaque ids, and read the server’s clock; they create nothing, and their responses use a result envelope instead of a JSON:API resource document. Each has a GraphQL query twin. See the utility endpoints reference for the request and response shape, OAuth scope requirements, and the visibility gating on resolve-id.

Filtering, sorting, and pagination

Collection endpoints support the standard JSON:API query conventions for narrowing and ordering results, along with pagination for large result sets.

Pagination is controlled with the page family of query parameters:

  • page[limit] sets the page size and must be a positive integer.
  • page[offset] skips a number of records and must be a non-negative integer.
  • page[after] and page[before] carry an opaque cursor.

Collections that page by cursor, the items collection among them, return next and prev links that already carry the right page[after] or page[before] cursor. Follow those links rather than constructing cursors yourself. The collections that grow without bound - items, beacons, blocks, entropy observations, commitments and epochs - return a bounded page of 25 by default even when you send no page parameters, so a first request never hands back an entire table.

Each resource defines its own default page size, and every paginated collection has a maximum: 250 unless the resource declares its own. There is no unbounded page. An oversized page[limit] is clamped to that maximum rather than rejected, so a large value returns a full page instead of a 400. A clamped page is still a complete page: it carries the clamped size in meta.page.limit and, when more records remain, a next link that continues from where it ended. Paging by next therefore reaches every record no matter what page[limit] was asked for. Supplying a non-integer, zero, or negative page[limit], or a non-integer or negative page[offset], is rejected with a 400 Bad Request and a JSON:API error body.

The JSON:API surface is one of several ways to integrate with Truestamp. A GraphQL endpoint exposes the same underlying data model with the same bearer authentication, and outgoing webhooks push delivery notifications to your own endpoints. Those are covered by their own concepts; this concept is the reference for the REST surface only.

Citations

  1. JSON:API v1.1 Specification. Defines the resource object structure, include, sorting, and the page pagination conventions this surface follows.
  2. OpenAPI Specification. The format of the machine-readable spec served at /api/json/open_api and rendered by the Swagger UI and ReDoc pages.