API Overview
The XNDR Network REST API returns indexed ALX Blocks, source-scoped observations, and relationship data as JSON.
The REST API is available under /api/v1 at the production and local base URLs:
| Environment | Value |
|---|---|
| Production | https://xndr.network/api/v1 |
| Local development | http://127.0.0.1:4173/api/v1 |
Available Interfaces
The REST API is read-only JSON under /api/v1. Every route is GET. Other methods return 405 method_not_allowed with an Allow: GET header. GET /api/v1/openapi.yaml serves an OpenAPI 3.1 description of every route, parameter, and response schema (XNDR Index Read API, version 1.0.0). The document declares /api/v1 as a relative server path, so a generated client needs the deployment origin. The REST API sends no CORS headers, so a browser on another origin cannot call the REST API.
Routes
Paths are relative to the base URL. Every hash parameter is a full 0x-prefixed, 64-character hexadecimal Block or content hash. The API assigns no numeric Block id.
| Route | Purpose | Key Parameters |
|---|---|---|
GET /scopes | Current named scopes and source membership | None |
GET /openapi.yaml | OpenAPI 3.1 description of the XNDR Network REST API | None |
GET /status | Source-scoped counts and current revision | scope, scopeVersion, atRevision |
GET /search?q={hash} | Exact Block or content identity lookup | q (required), scope parameters, limit, cursor |
GET /blocks/{blockHash} | Block and observation context | Scope parameters |
GET /content/{contentHash} | Blocks sharing an exact content identity | Scope parameters, limit, cursor |
GET /blocks/{blockHash}/parents | Declared parents and scoped availability | Scope parameters, limit, cursor |
GET /blocks/{blockHash}/children | Source-scoped indexed children | Scope parameters, limit, cursor |
GET /blocks/{blockHash}/verification | Independent verification facets | Scope parameters |
GET /blocks/{blockHash}/neighborhood | Bounded parent and child neighborhood | direction, depth, maxNodes, contentType, status, source, scope parameters, limit, cursor |
GET /scopes returns { data } only; every other JSON route returns the Response Envelope.
Query Capabilities
The shell commands use BASE for the base URL and BLOCK_HASH for the Block under review.
BASE="https://xndr.network/api/v1"
BLOCK_HASH="0x..."
Exact Search
GET /search?q={hash} resolves one exact identity. A Block hash fills data.block with the matching Block view; a content hash fills data.contentMatches with one page of indexed Blocks sharing the content hash, and data.query echoes the normalized hash. Follow page.nextCursor for further matches. Partial hashes, metadata terms, and Block numbers are rejected with 400 invalid_request.
curl "$BASE/search?q=$BLOCK_HASH"
Block Context
GET /blocks/{blockHash} returns the Block view.
| Field | Meaning |
|---|---|
block | Canonical ALX content: blockHash, contentHash, parentHashes, content, and protocolVersion |
indexedChildren | Optional inline child hashes; use the paginated children route to retrieve child hashes when this field is absent |
indexedChildCount | How many Blocks in the selected scope declare the path Block as a parent |
observedSources | The sources that reported the path Block, each with an id and a type |
firstObservedAt | When the selected scope first observed the Block, not a creation timestamp |
lastObservedAt | The most recent observation within the selected scope |
verification | The facets inline, identical to the dedicated verification route |
A Block that is not indexed in the selected scope returns 404 block_not_found. The 404 block_not_found response is limited to the selected scope and is not proof that the Block does not exist.
curl "$BASE/blocks/$BLOCK_HASH"
Content Matches
GET /content/{contentHash} returns one page of indexed Blocks in the selected scope whose contentHash equals the path value. Follow page.nextCursor for further matches.
Parent Lookup
GET /blocks/{blockHash}/parents returns a page of the Block's declared parentHashes as { blockHash, available } items. Across all pages, the hashes are exactly those the Block declares; available reports whether the selected scope has indexed each declared parent.
Children Lookup
GET /blocks/{blockHash}/children lists Blocks observed in the selected scope that declare the path Block as a parent, as { blockHash, available } items. Children are reverse-index observations, not protocol facts, so an absent child is unobserved, never disproven.
Verification Facets
GET /blocks/{blockHash}/verification returns the validation record for the Block in the selected scope: blockHash, the verification facets, missingParents, checkedAt, and validatorVersion.
| Facet | Value |
|---|---|
identity | valid, invalid, or not-checked |
schema | valid, invalid, or not-checked |
lineage | valid, invalid, or not-checked |
availability | available, unavailable, or unknown |
missingParents lists declared parents the scope has not indexed, and lineage becomes not-checked when declared parents fall outside the scope. not-checked is an XNDR Network index value, not an ALX Protocol canonical Graph status.
A 404 block_not_found from GET /blocks/{blockHash}/verification means no validation record exists yet; fall back to the inline verification facets on the Block view.
Bounded Neighborhood
GET /blocks/{blockHash}/neighborhood expands parents, children, or both from a root Block within explicit budgets. The neighborhood is an application view, not an ALX Protocol Graph verification or Attribution Trace.
| Parameter | Values | Default |
|---|---|---|
direction | parents, children, both | both |
depth | Integer from 0 through 8 | 1 |
maxNodes | Integer from 1 through 10000 | 1000 |
contentType | Keep nodes whose application-declared content.type matches | Unfiltered |
status | valid, invalid, incomplete, not_checked | Unfiltered |
source | Keep nodes observed by the supplied source id within the scope | Unfiltered |
The status filter spells the final listed value not_checked, with an underscore, while the verification facets report not-checked. The REST API serves not_checked on the neighborhood status parameter and not-checked on the verification facets.
The response's data member returns the root, applied traversal parameters, content types, nodes, and edges. Each edge pairs a child Block with the parent hash that child declares. The root Block is always returned, even when a filter excludes the root Block. maxNodes bounds traversal and limit bounds the returned page; data.budgetTruncated reports when traversal stopped at maxNodes.
See the ALX Block Model for relationship semantics.
Scope Parameters
Every query route accepts three snapshot parameters. A named scope is a versioned set of sources; the index revision advances once for every accepted observation and every quarantined candidate.
| Parameter | Format | Behavior |
|---|---|---|
scope | 1 through 128 letters, digits, dots, colons, underscores, or hyphens, starting with a letter or digit; default default | Selects the named scope. An unknown id returns 404 scope_not_found. |
scopeVersion | Positive integer string | Pins one immutable scope version. Publishing a new version leaves existing versions queryable. |
atRevision | Non-negative integer string | Pins the index revision. A revision XNDR Network cannot serve returns 409 revision_unavailable. |
GET /scopes lists the current scopes, each with id, name, version, sources, completeness, latestRevision, and indexedThrough. Source types are local, retrieval, partner, and network.
GET /status returns counters for the selected snapshot.
| Counter | Meaning |
|---|---|
blockCount | Indexed Blocks |
edgeCount | Resolved parent-child edges |
observationCount | Source observations recorded |
validationCount | Validation records written |
quarantineCount | Inputs held back, and excluded from Block and graph queries |
Authentication
Authentication is optional and selects the rate tier. Send an issued key in the X-XNDR-API-Key header, or as Authorization: Bearer followed by a space and the key.
A key is 16 through 512 characters. A malformed, unknown, or disabled key returns 401 invalid_api_key; the request is rejected, not downgraded to the anonymous tier.
An XNDR Network operator issues keys. A key is displayed once at creation. The API provides no self-serve key endpoint and no signup route. Request a key from support@alxlabs.io.
Rate Limits
| Tier | Budget | Counted By |
|---|---|---|
| Anonymous | 60 requests per minute | Client IP address |
| Keyed | 600 requests per minute | Issued API key |
Counting uses a fixed 60-second window. Exceeding the budget returns 429 rate_limited with a Retry-After header in seconds; wait at least the Retry-After interval before retrying. JSON responses carry Cache-Control: no-store, so cache on your side deliberately and key cached entries by scope and revision.
Pagination and Snapshots
List routes accept limit (1 through 1000, default 100) and cursor. Each response reports page.limit, page.nextCursor, and page.truncated; a null nextCursor means no further page follows.
Cursors are signed and bound to:
- The query kind.
- The normalized query.
- The selected scope.
- The result offset.
- The exact index revision.
A cursor is rejected with 400 invalid_request after the index revision changes or when reused for another query, so pin atRevision to the scope.indexRevision of the first page before continuing.
curl "$BASE/blocks/$BLOCK_HASH/children?atRevision=$REVISION&limit=5"
Repeat the request with cursor set to the returned page.nextCursor and the same atRevision until nextCursor is null. A further page sets page.truncated to true. Separately, a bounded-neighborhood traversal that reaches maxNodes sets data.budgetTruncated to true and scope.completeness to truncated, even when page.truncated is false.
Response Envelope
Every query route returns data, scope, and page. data holds the route result, scope reports the snapshot that produced the route result, and page reports limit, nextCursor, and truncated.
| Field | Meaning |
|---|---|
id | The scope the request was served from |
name | Display name of the served scope |
version | The immutable scope version |
sources | The configured sources in the scope, each with an id and a type |
indexRevision | The revision the response was served from |
indexedThrough | Latest observation timestamp in the served scope and revision, or null when the scope has no observations; not a measure of source catch-up or Block creation time |
completeness | Completeness reported for the served scope |
| Completeness | Meaning |
|---|---|
complete-within-scope | Every configured source is caught up to the source's advertised boundary and the query stayed within budget |
partial | At least one source has succeeded, but not every source is caught up |
truncated | Pagination or a traversal budget cut the result short; inspect page.truncated, page.nextCursor, and, for neighborhoods, data.budgetTruncated |
unknown | The service has not derived completeness for the scope |
complete-within-scope describes the configured source high-water marks and the query budget. complete-within-scope never claims coverage of every ALX Block. Responses may gain additive fields within /api/v1; removals, renamed meanings, and incompatible type changes require a new major API prefix.
Error Handling
Errors are JSON with one error object carrying a code and a message: { "error": { "code", "message" } }.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter, hash, or cursor failed validation |
| 401 | invalid_api_key | The supplied key is malformed, unknown, or disabled |
| 404 | block_not_found | The Block is not indexed in the selected scope |
| 404 | scope_not_found | The scope id is not configured |
| 404 | route_not_found | The path is not an API route |
| 405 | method_not_allowed | The method is not GET |
| 409 | revision_unavailable | The requested atRevision cannot be served |
| 414 | request_target_too_long | The request target exceeds 8192 bytes |
| 429 | rate_limited | The per-minute budget is exhausted; honor Retry-After |
| 500 | internal_error | The request could not be completed; no server internals are exposed |
Error codes are stable within /api/v1. Messages may be refined without changing the code, so branch on error.code and the HTTP status, never on message text.
Build for Source-Scoped Data
Scope, parent availability, unobserved children, pagination, and completeness are defined in the route sections above. Review the Indexing Model before building a workflow that depends on relationship completeness or availability. Integrate with XNDR Network applies those rules to search, lineage, and verification.