1. Health & Status

GET /health

Returns "ok". Use for load balancer health checks.

GET /api/ping
{ "status": "ok", "message": "Server is running" }

2. Blockchain

GET /api/blockchain/latest

Returns the most recently written blockchain block.

GET /api/blockchain/byid/:id

Returns the block at numeric index id.

GET /api/blockchain/bydid/:did

Returns the block whose document DID matches :did.

GET /api/blockchain/recent/10

Returns the last 10 blocks in reverse order.

GET /api/blockchain/validate/:id

Validates the integrity (hash chain) of block at index :id.

Response:

{ "valid": true, "blockId": 42, "message": "..." }
POST /api/blockchain/validate/challenge

Validates a challenge-response proof that a given persona authored a block.

Body:

{
  "personaDID": "did:451:...",
  "blockHash": "abc123...",
  "challengeSignature": "<base64>"
}
POST /api/blockchain/validate/document

Validates that a document block's hash and S3 ETag still match.

Body:

{
  "documentDID": "did:451:...",
  "documentHash": "<sha256 hex>"
}
GET /api/blockchain/search?q=<query>&limit=<n>

Full-text search across the blockchain index via Meilisearch.

Query params:

  • q (required) — search string
  • limit (optional, default 20)

Response:

{
  "hits": [{ "documentID": "...", "relevance": 0.9, "note": "Use /api/blockchain/byid/... to fetch" }],
  "query": "...",
  "processingTimeMs": 3.2,
  "limit": 20,
  "estimatedTotalHits": 5
}

3. Consensus & Checkpoints

GET /api/blockchain/consensus/rules

Returns the current consensus configuration: authorized signers, threshold, etc.

GET /api/blockchain/finalized/latest

Returns the most recently finalized block (one that has met the signature threshold).

POST /api/blockchain/checkpoint/create

Creates a checkpoint summarizing the chain up to a given block.

Body:

{ "upToBlock": 500 }

upToBlock is optional; omit to checkpoint through the latest block.

POST /api/blockchain/checkpoint/compare

Compares two checkpoints to detect chain divergence.

Body:

{
  "ourCheckpoint": { ... },
  "theirCheckpoint": { ... }
}

Response:

{ "diverged": false, "proof": null, "message": "Checkpoints match — no divergence detected" }

4. University / Multi-Signer Onboarding

POST /api/blockchain/university/bootstrap

Creates the genesis node record for a new university participant.

Body:

{
  "universityDID": "did:451:uni-...",
  "universityName": "Example University",
  "publicKey": "<base64 P-256 public key>"
}
POST /api/blockchain/university/authorize

Authorizes an additional university as a block signer. Optionally raises the signature threshold.

Body:

{
  "universityDID": "did:451:uni-...",
  "universityName": "Second University",
  "publicKey": "<base64>",
  "newSignatureThreshold": 2
}

Response:

{ "success": true, "message": "...", "totalSigners": 2, "signatureThreshold": 2 }
POST /api/blockchain/block/:hash/sign

Adds a signature to a block. Once the threshold is met the block is finalized.

Body:

{
  "signature": "<base64>",
  "publicKey": "<base64>",
  "signerDID": "did:451:...",
  "signerName": "Optional Name"
}

Response:

{ "signed": true, "finalized": false, "blockHash": "...", "message": "Block signed, awaiting more signatures" }

5. Documents

POST /api/document/submit

Submits a document for storage. Uploads to all active S3 providers and writes a blockchain block.

HeaderRequiredDescription
Content-TypeyesMIME type of document (e.g. application/pdf)
X-Submitter-DID or X-Uploader-DIDnoDID of the submitter
X-Author-DIDnoDID of the author (defaults to submitter)

Body: Raw document bytes.

Response:

{
  "success": true,
  "documentId": "doc-<uuid>",
  "taskId": "<uuid>",
  "message": "Document processed successfully",
  "size": 204800,
  "filesUploaded": 3,
  "blockchainEntries": 1
}

Use taskId with the Progress Tracking endpoints to stream upload progress.

POST /api/document/:documentId/package/append

Appends an additional asset (image, attachment) to an existing document package.

HeaderRequiredDescription
X-Submitter-DID or X-Uploader-DIDyesDID of the uploader
X-Asset-Path or X-File-NameyesFilename / S3 sub-key for this asset

Body: Raw asset bytes.

Response:

{
  "success": true,
  "documentId": "doc-<uuid>",
  "assetKey": "privatedocuments/<uuid>/assets/photo.jpg",
  "size": 98304,
  "providers": ["Backblaze", "Wasabi"],
  "providerURLs": ["https://...", "https://..."]
}
PUT /api/document/draft/:draftId/content

Uploads or overwrites the content blob for a draft document.

Body: Raw document bytes (e.g. JSON from a rich-text editor).

Response:

{
  "success": true,
  "draftId": "doc-<uuid>",
  "documentId": "<uuid>",
  "s3Key": "privatedocuments/<uuid>/document.json",
  "size": 4096,
  "providers": ["Backblaze", "Wasabi"]
}
GET /api/document/draft/:draftId/status

Returns whether metadata and/or content blobs exist for a draft.

Response:

{
  "draftId": "doc-<uuid>",
  "documentId": "<uuid>",
  "hasMetadata": true,
  "hasContent": false,
  "metadata": { ... }
}
POST /api/document/:documentId/sign

Records a cryptographic signature against a document's S3 head pointer.

Body:

{
  "signerDID": "did:451:...",
  "signerPublicKey": "<base64>",
  "signature": "<base64>",
  "role": "author"
}

Response:

{
  "success": true,
  "documentId": "...",
  "signerDID": "did:451:...",
  "role": "author",
  "entryID": "sign_<uuid>",
  "message": "Signature recorded and head updated"
}

6. Signatures — Document Workflow

The preferred signing flow uses a challenge to prove key possession before accepting a document signature.

POST /api/documents/:documentId/request-challenge

Generates a signing challenge for an authorized signer.

Body:

{ "signerDID": "did:451:..." }

Response:

{
  "challengeId": "<uuid>",
  "challengeText": "<random string>",
  "expiresAt": "2026-04-06T12:00:00Z",
  "message": "Sign the challengeText … then submit to /api/documents/:id/sign-with-challenge"
}
POST /api/documents/:documentId/sign-with-challenge

Submits both a challenge signature and a document signature. Sends APNS notifications on completion.

Body:

{
  "challengeId": "<uuid>",
  "signerDID": "did:451:...",
  "challengeSignature": "<base64>",
  "documentSignature": "<base64>"
}

Response:

{
  "success": true,
  "documentId": "...",
  "signaturesCollected": 1,
  "signaturesRequired": 2,
  "isComplete": false
}
POST /api/documents/:documentId/sign (legacy)

Accepts a document signature without a challenge. Prefer sign-with-challenge.

Body:

{ "signerDID": "did:451:...", "signature": "<base64>" }
GET /api/documents/:documentId/signatures

Returns current signature status for a document.

Response:

{
  "documentId": "...",
  "documentHash": "<sha256>",
  "collectedSignatures": 1,
  "requiredSignatures": 2,
  "signatures": [
    { "signer": "did:451:...", "status": "verified", "signedAt": "..." }
  ]
}
GET /api/documents/:documentId/head

Returns the head pointer metadata for a document (latest event type, pointers to prior entries).

GET /api/documents/pending-signatures?personaDIDs=<comma-list>&limit=<n>

Returns documents that need signatures from any of the listed personas.

Query params:

  • personaDIDs — comma-separated list of DIDs
  • signerDID — (legacy) single DID
  • limit (default 50)
POST /api/documents/pending-signatures

Same as above but via POST body (useful for large DID lists).

Body:

{ "personaDIDs": ["did:451:...", "did:451:..."], "limit": 50 }
GET /api/documents/pending-signatures/:signerDID

Returns pending-signature count and document list for a single signer.

GET /api/persona/:personaDID/documents/pending-signatures

Single-persona convenience shortcut for the multi-persona endpoint.

7. Personas

Personas are DID-backed identities aligned with the AT Protocol.

POST /api/persona/canonicalize

Step 1 of persona creation. Validates and canonicalizes the proposed persona data, returns a canonicalDID and signing payload.

Body: PersonaCanonicalizeRequest

POST /api/persona/finalize

Step 2 of persona creation. Submits the signed canonical payload to persist the persona to S3 and write a blockchain block.

Body: PersonaFinalizeRequest

GET /api/persona/:personaDID

Returns the PersonaProfile for the given DID.

PUT /api/persona/:personaDID

Performs a verified soft update (name, address, background-validated flag). Requires the private key.

Body:

{
  "updatedName": "Alice Smith",
  "updatedAddress": "123 Main St",
  "updatedBackgroundValidated": true,
  "privateKeyBase64": "<raw P-256 key>"
}
PATCH /api/persona/:personaDID

Partial update via PersonaUpdateRequest. Ownership is proven via signature in the payload.

DELETE /api/persona/:personaDID

Decommissions a persona. The DID remains on the chain but is marked inactive.

Body (optional):

{ "reason": "Account closed" }
GET /api/persona/resolve/:identifier

Resolves a handle or short ID to a full PersonaProfile.

GET /api/persona/search?q=<query>&filter=<filter>&limit=<n>&offset=<n>

Full-text or filter-based persona search via Meilisearch (personasIndex).

POST /api/persona/:personaDID/reindex

Re-indexes the persona in Meilisearch. Returns 200 OK.

GET /api/persona/:personaDID/validate-document/:documentDID

Checks whether a persona is the declared author or participant of a specific document block.

POST /api/persona/:personaDID/verify-ownership

Verifies that the caller controls the private key for a persona by validating a challenge response.

Body: ChallengeResponsePayload

GET /api/persona/:personaDID/verify

Returns background-check status for a persona.

POST /api/persona/:personaDID/background-check/initiate

Initiates a background-check order via the configured VerifiedCredentials API.

GET /api/persona/:personaDID/background-check/status

Polls background-check order status.

POST /api/persona (deprecated — returns 410 Gone)

Use the two-step canonicalizefinalize flow instead.

8. Device Registration

POST /api/device/register

Registers an APNS device token for a persona (global shortcut).

Body:

{ "did": "did:451:...", "deviceToken": "<apns hex token>" }
POST /api/persona/:personaDID/device/register

Registers a device token scoped to a specific persona (preferred).

Body:

{ "did": "did:451:...", "deviceToken": "<apns hex token>" }

9. Proposals

Proposals represent an invitation to join a document workflow (e.g. as a co-signer).

POST /api/proposals

Creates a proposal from one persona to another.

GET /api/proposals/:proposalID

Returns proposal details.

POST /api/proposals/:proposalID/verify

Verifies the cryptographic proof attached to a proposal.

POST /api/proposals/:proposalID/accept

Accepts a proposal, enrolling the recipient as a participant.

10. Authentication — Sign in With 451

Two-party challenge/response flow. Can be used for identity proof or access attestation.

POST /api/auth/request

Creates an authentication challenge. Returns a deep link usable by the Signator app.

Body:

{
  "relyingPartyDID": "did:451:...",
  "purpose": "login",
  "mode": "identity_proof",
  "callbackURL": "https://yourapp.example/auth/callback"
}

mode values: identity_proof (default) | access_attestation

Response:

{
  "challengeId": "...",
  "challengeText": "...",
  "relyingPartyDID": "did:451:...",
  "purpose": "login",
  "mode": "identity_proof",
  "expiresAt": "2026-04-06T12:05:00Z",
  "deepLinkURL": "signator://auth?challenge=..."
}
GET /api/auth/challenge/:challengeId

Fetches a challenge (used by the Signator app to display details before signing).

POST /api/auth/respond

Submits a signed response to a challenge. If mode is access_attestation a blockchain block is written. Issues a JWT token.

Body:

{
  "challengeId": "...",
  "personaDID": "did:451:...",
  "challengeSignature": "<base64>"
}

Response:

{
  "token": "<jwt>",
  "personaDID": "did:451:...",
  "mode": "identity_proof",
  "blockRef": null,
  "expiresAt": "2026-04-06T13:00:00Z"
}
GET /api/auth/status/:challengeId

Polls for whether a challenge has been responded to and a token issued.

11. Credential Verification

Integration with the VerifiedCredentials background-check API. Configured via VC_ENVIRONMENT, VC_API_USERNAME, VC_API_PASSWORD environment variables.

POST /api/credential/check

Submits a new background-check order for a persona.

Body: CredentialCheckRequest

GET /api/credential/status/:orderRef

Returns status of an existing background-check order.

GET /api/credential/:did

Returns all credential assertions on-chain for a DID.

12. Data Repositories & Instruments

Data repositories allow institutions to ingest instrument readings into the 451 blockchain. All ingest calls write an immutable block.

Repository Management

POST /api/repository/register

Registers a new data repository. Requires an institutional persona with a DNS-validated domain.

Body:

{
  "ownerDID": "did:451:...",
  "shortId": "university.edu/admin",
  "name": "Weather Station Network",
  "description": "Campus weather instruments",
  "publicKey": "<base64 P-256>",
  "signature": "<base64 — sign(ownerDID:name)>",
  "s3Endpoints": ["s3://my-bucket"],
  "cacheServerURL": "https://cache.university.edu"
}

Response:

{
  "repositoryId": "repo_<uuid>",
  "apiKey": "<raw key — shown once>",
  "blockIndex": 42,
  "blockHash": "...",
  "createdAt": "..."
}
GET /api/repository/:repositoryId

Returns repository registration details and the corresponding blockchain block reference.

POST /api/repository/:repositoryId/rotate-key

Rotates the repository API key.
Auth: X-Owner-DID + X-Signature (sign ownerDID:repositoryId:rotate)

Body:

{
  "ownerDID": "did:451:...",
  "shortId": "university.edu/admin",
  "publicKey": "<base64>",
  "signature": "<base64>"
}

Response:

{ "repositoryId": "...", "apiKey": "<new raw key>", "rotatedAt": "..." }

Ingest — Streaming Pattern

Use open/close for continuous instruments (weather sensors, body cameras).

POST /api/repository/ingest/open

Opens a data stream session.

Headers:

  • X-Repository-API-Key: raw API key
  • X-Repository-ID: repository ID

Body:

{
  "repositoryId": "repo_<uuid>",
  "instrumentType": "weather_station",
  "metadata": { "location": "Building A rooftop" }
}

Response:

{ "streamId": "stream_<uuid>", "openedAt": "..." }
POST /api/repository/ingest/close

Closes a stream session and writes a blockchain block.

Headers: Same as open.

Body:

{
  "repositoryId": "repo_<uuid>",
  "streamId": "stream_<uuid>",
  "s3Bucket": "my-bucket",
  "s3Key": "readings/2026-04-06/weather.json",
  "s3ETag": "\"abc123\"",
  "s3ProviderURL": "https://s3.us-east-1.amazonaws.com",
  "dataHash": "<sha256 hex>",
  "dataSizeBytes": 4096,
  "metadata": { "temperatureUnit": "celsius" }
}

Response:

{ "streamId": "...", "blockIndex": 43, "blockHash": "...", "recordedAt": "..." }

Ingest — Single-Shot Pattern

POST /api/repository/ingest/record

Single-call ingest for instruments that don't hold a session open.

Headers: Same as streaming.

Body: Same fields as ingest/close plus optional capturedAt timestamp. No streamId required (generated server-side).

Instrument Registration (Conformance Spec)

POST /api/repository/instrument/register

Registers a conformant instrument. The instrument's P-256 key pair must be generated on the instrument; only the public key is sent here. The private key never leaves the instrument.

Body:

{
  "repositoryId": "repo_<uuid>",
  "ownerDID": "did:451:...",
  "shortId": "university.edu/admin",
  "ownerPublicKey": "<base64>",
  "ownerSignature": "<base64 — sign(repositoryId:instrumentPublicKey)>",
  "instrumentPublicKey": "<base64 P-256 — generated on instrument>",
  "instrumentType": "weather_station",
  "name": "Rooftop Sensor 1"
}

Response:

{
  "instrumentDID": "did:451:instrument:<uuid>",
  "repositoryId": "...",
  "specVersion": "0.1",
  "signingInputFormat": "spec:repositoryId:instrumentDID:s3Bucket:s3Key:s3ETag:dataHash:openedAt:closedAt",
  "registeredAt": "..."
}
GET /api/repository/:repositoryId/instruments

Lists all registered instruments for a repository.

POST /api/repository/ingest/submit

Conformant instrument path. The instrument signs the block locally before submitting. S451 validates and commits.

Header: X-Instrument-DID: did:451:instrument:<uuid>

Body (ConformantIngestSubmission):

{
  "payload": {
    "spec": "0.1",
    "instrumentDID": "did:451:instrument:...",
    "repositoryId": "repo_<uuid>",
    "s3Bucket": "my-bucket",
    "s3Key": "readings/2026-04-06/data.json",
    "s3ETag": "\"abc123\"",
    "s3ProviderURL": "https://...",
    "dataHash": "<sha256>",
    "dataSizeBytes": 2048,
    "openedAt": "2026-04-06T10:00:00Z",
    "closedAt": "2026-04-06T10:01:00Z",
    "instrumentType": "weather_station",
    "metadata": {}
  },
  "publicKey": "<base64 — must match registered key>",
  "signature": "<base64 — ECDSA P-256 over canonical input>"
}

Canonical signing input format:

spec:repositoryId:instrumentDID:s3Bucket:s3Key:s3ETag:dataHash:openedAt:closedAt

Fields are joined with : in that exact order and signed as UTF-8 bytes.

Response:

{
  "instrumentDID": "did:451:instrument:...",
  "repositoryId": "...",
  "blockIndex": 44,
  "blockHash": "...",
  "verifiedInput": "0.1:repo_...:did:451:instrument:...:...",
  "recordedAt": "..."
}

13. Progress Tracking (SSE)

Long-running operations (document submit, S3 uploads) report progress via Server-Sent Events.

GET /api/progress/:taskId/stream

Opens an SSE stream for the given task. The client receives ProgressStep events followed by a CompletionEvent or ErrorEvent.

GET /api/progress/:taskId/status

Snapshot of current progress without streaming.

Response:

{
  "taskId": "...",
  "steps": [
    { "step": "extracting_metadata", "message": "Analyzing document", "progress": 0.1, "documentId": "..." }
  ],
  "completion": null,
  "error": null,
  "isComplete": false
}
POST /api/progress/test

Creates a synthetic test task and streams fabricated steps. Useful for client SSE integration testing.

Body (optional): { "duration": 5 } (seconds, default 5)

Response: { "taskId": "..." }

14. Signed URLs

POST /api/signed-url

Generates a pre-signed S3 URL for a file.

Body:

{
  "fileName": "privatedocuments/<uuid>/document.pdf",
  "operation": "GET",
  "expiration": 3600
}

Response:

{ "signedURL": "https://..." }

15. Timestamp

POST /api/timestamp/submit

Submits a timestamp proof request, writing a timestampRecord block to the chain.

Body: TimestampRequestFromWallet

16. GPS Location

POST /api/gpslocation/process

Submits a GPS location claim, writing a gpsLocationRecord block to the chain.

Body: GPSLocationClaimRequest

17. Notifications

POST /api/notification/send

Sends a notification and records it to the notification blockchain.

Body: NotificationBlockPayload

18. Search & Debug

GET /api/debug/meilisearch/health

Checks whether the Meilisearch service is reachable.

GET /api/debug/meilisearch/verify/:indexName/:documentId

Verifies that a specific document is searchable in a given index. Accepts optional ?originalDID= query parameter.

GET /api/debug/meilisearch/task/:taskUid

Returns the status of a Meilisearch background task by its numeric UID.

GET /api/debug/search/indices

Lists all configured Meilisearch indices and their settings.

GET /api/debug/search/index/:indexName?limit=<n>

Returns up to limit document IDs from a specific index. Does not return full documents — fetch those from S3 using the ID.

GET /api/debug/search/document/:documentID

Searches all indices for a document ID and reports which indices contain it.

Authentication Model

Most endpoints authenticate requests via signature verification rather than bearer tokens:

MechanismUsed by
P-256 ECDSA signature over a known messageRepository registration, key rotation, instrument registration
Challenge/response (identity challenge service)auth/respond, sign-with-challenge, persona verify-ownership
API key header (X-Repository-API-Key)All data ingest endpoints
JWT (issued by auth/respond)Consumer applications polling auth/status

S3 Path Conventions

EntityS3 Key Pattern
Private document contentprivatedocuments/<uuid>/document.json
Document metadataprivatedocuments/<uuid>/metadata.json
Document head pointerprivatedocuments/<uuid>/head.json
Document signature eventprivatedocuments/<uuid>/signatures/<entryID>.json
Blockchain blockblockchain/entry_<index>.json
Blockchain ledgerblockchain/ledger.json
Repository recordrepositories/<repositoryId>/record.json
API key recordrepositories/<repositoryId>/apikeys/<keyId>.json
Instrument recordrepositories/<repositoryId>/instruments/<instrumentId>.json
Stream sessionrepositories/<repositoryId>/streams/<streamId>.json

Meilisearch Indices

IndexContent
blockchainIndexBlockchain blocks (all types), repository registrations, ingest records
personasIndexPublic persona profiles
privatePersonasIndexPrivate persona profiles
fullTextIndexFull text of public documents
privateFullTextIndexFull text of private documents
metadataIndexDocument metadata
signatureIndexSignature records (filterable on status, documentId, signerId)