VMP API v1

Global rules, auth, scopes, endpoint reference, and interactive testing.

Base URL: https://api.vemorak.com

Global rules

Standards that apply to every endpoint unless explicitly stated.

Permalink
Content type
HTTP
Content-Type: application/json
Time format
Text
RFC3339 / ISO8601 UTC (e.g. 2026-01-09T18:12:00Z)
Identifiers
Text
event_id, batch_id, receipt_id, key_id: UUID (string)
tenant_id: string (1–128, no whitespace)
scope: string (1–128, must contain ':')
scope_prefix: optional string (1–128, no whitespace, must end with ':')

Authentication

Tenant keys for most endpoints, provision token for platform operations, token-only for WebSocket.

Permalink
Tenant API key auth

Used by all endpoints except /healthz and the WebSocket stream.

HTTP
Authorization: Bearer <tenant_api_key>
Key validation outcomes
JSON
401 {"error":"missing Authorization: Bearer <api_key>"}
401 {"error":"invalid api key"}
401 {"error":"api key revoked"}
401 {"error":"api key expired"}
401 {"error":"api key stale", "details": {...}}
403 {"error":"forbidden","details":{"missing_scope":"..."}}
403 {"error":"tenant mismatch"}
403 {"error":"forbidden","details":{"reason":"scope outside key prefix", ...}}
429 {"error":"rate_limited","details":{...}}
Provisioning token auth
HTTP
Authorization: Bearer <VMP_CONSOLE_PROVISION_TOKEN>
JSON
401 {"error":"invalid provision token"}
Protected endpoints
Text
POST /v1/admin/api-keys
GET  /v1/admin/api-keys
POST /v1/admin/api-keys/revoke
POST /v1/admin/pubkeys
PATCH /v1/admin/pubkeys/{pubkey_id}
WebSocket auth (token-only)
HTTP
GET /v1/stream/events?token=<JWT>
Claims
JSON
{
  "tenant_id": "string",
  "scope": "admin.read",
  "exp": 123,
  "iat": 123
}
WhoAmI

Any valid tenant key can call this endpoint.

HTTP
GET /v1/whoami

Scopes

Authoritative scope list and matching rules.

Permalink
Authoritative list
Text
admin
admin.read
events.write
events.delete
proofs.read
receipts.read
ws.mint
Rules
Text
- "admin" satisfies all scopes
- otherwise exact match
- if a key has scope_prefix, any request scope identifier must begin with that prefix

Public endpoints

No authentication required.

Permalink
GET /healthz

Returns a plain text ok response.

Text
ok

Ingest

Append event to the chain for a scope.

Permalink
POST /v1/ingest
HTTP
Authorization: Bearer <tenant_api_key>

Required scope: events.write. Optional idempotency header: x-idempotency-key.

Request
JSON
{
  "tenant_id": "vmp-tenant",
  "scope": "user:123",
  "op": "write",
  "fields": {},
  "meta": {}
}
Response
JSON
{
  "event_id": "uuid",
  "event_hash_hex": "32-byte-hex",
  "prev_hash_hex": "32-byte-hex|null",
  "created_at": "2026-01-09T18:12:00Z"
}

Proofs

Merkle proof for an event once batched.

Permalink
GET /v1/proof/{event_id}

Required scope: proofs.read. If not batched, fields are null.

Response
JSON
{
  "event_id": "uuid",
  "tenant_id": "vmp-tenant",
  "scope": "user:123",
  "batch_id": "uuid|null",
  "leaf_index": 7|null,
  "leaf_hex": "32-byte-hex|null",
  "root_hex": "32-byte-hex|null",
  "path": [
    { "sibling_hex": "32-byte-hex", "sibling_is_left": true }
  ],
  "sig_base64": "base64|null",
  "pubkey_id": "dev-key-1|null",
  "pubkey_base64": "base64|null",
  "pubkey_hex": "hex|null",
  "batch_created_at": "2026-01-09T18:12:00Z|null"
}

Deletion and receipts

Logical deletion plus verifiable receipts.

Permalink
POST /v1/delete

Required scope: events.delete. Appends a new delete event.

JSON
{
  "tenant_id": "vmp-tenant",
  "scope": "user:123",
  "target_event_id": "uuid",
  "meta": {}
}
GET /v1/deletion-receipts/{receipt_id}

Required scope: receipts.read.

GET /v1/verify-deletion/{receipt_id}

Required scope: receipts.read.

Receipt signature message
Text
b"vmp-delete:" || delete_event_id_bytes || delete_event_hash_bytes

Bundles

Self-contained payloads for offline verification.

Permalink
GET /v1/events/{event_id}/bundle

Required scope: proofs.read.

GET /v1/deletion-receipts/{receipt_id}/bundle

Required scopes: receipts.read and proofs.read.

Verifier endpoints

Server endpoints used by offline verifiers.

Permalink
POST /v1/verify/bundle

Currently public. Validates recompute, Merkle path, and signature.

POST /v1/verify/deletion-bundle

Currently public. Validates receipt signature and the delete-event bundle.

Admin surface

Tenant-authenticated admin endpoints.

Permalink
Admin endpoints
Text
/v1/admin/events
/v1/admin/events/{event_id}
/v1/admin/ws-token
/v1/admin/batches
/v1/admin/stats
/v1/admin/deletion-receipts
/v1/admin/worker/health
/v1/admin/api-keys/{key_id}/audit-events
/v1/admin/batches/{batch_id}/leaves
/v1/admin/batches/{batch_id}/tree
/v1/admin/dashboard

WebSocket stream

Token-authenticated stream of events.

Permalink
Text
GET /v1/stream/events?token=<jwt>
JSON
{ "kind": "hello", "data": { "tenant_id": "vmp-tenant", "scope": null } }
{ "kind": "heartbeat", "data": { "ts": "..." } }

Provisioning

Console to VMP platform operations (provision token).

Permalink
Provision token endpoints
Text
POST /v1/admin/api-keys
GET  /v1/admin/api-keys
POST /v1/admin/api-keys/revoke

Pubkeys

Pubkey registry and verifier support.

Permalink
Text
GET   /v1/pubkeys/{pubkey_id}
POST  /v1/admin/pubkeys
PATCH /v1/admin/pubkeys/{pubkey_id}
Text
active: can sign and verify
retired: verify only
revoked: neither sign nor verify

Error format

Consistent JSON error objects for clients.

Permalink
JSON
{ "error": "message" }
JSON
{ "error": "forbidden", "details": { "missing_scope": "..." } }

Endpoints index

Tabular view for API consumers and OpenAPI mapping.

Permalink
Endpoints index

OpenAPI-friendly view of method, path, auth, and required scope.

MethodPathAuthScope
GET/healthznonenone
POST/v1/ingesttenant keyevents.write
GET/v1/proof/{event_id}tenant keyproofs.read
POST/v1/deletetenant keyevents.delete
GET/v1/deletion-receipts/{receipt_id}tenant keyreceipts.read
GET/v1/verify-deletion/{receipt_id}tenant keyreceipts.read
GET/v1/events/{event_id}/bundletenant keyproofs.read
GET/v1/deletion-receipts/{receipt_id}/bundletenant keyreceipts.read + proofs.read
POST/v1/verify/bundlenone (public)none
POST/v1/verify/deletion-bundlenone (public)none
GET/v1/admin/eventstenant keyadmin.read
GET/v1/admin/events/{event_id}tenant keyadmin.read
GET/v1/admin/batchestenant keyadmin.read
GET/v1/admin/batches/{batch_id}/leavestenant keyadmin.read
GET/v1/admin/batches/{batch_id}/treetenant keyadmin.read
GET/v1/admin/statstenant keyadmin.read
GET/v1/admin/deletion-receiptstenant keyadmin.read
GET/v1/admin/worker/healthtenant keyadmin.read
GET/v1/admin/dashboardtenant keyadmin.read
POST/v1/admin/ws-tokentenant keyws.mint
GET/v1/stream/events?token=<jwt>ws tokenadmin.read (in token)
GET/v1/whoamitenant keyany valid key
POST/v1/admin/api-keysprovision tokenn/a
GET/v1/admin/api-keysprovision tokenn/a
POST/v1/admin/api-keys/revokeprovision tokenn/a
GET/v1/pubkeys/{pubkey_id}tenant keyproofs.read
POST/v1/admin/pubkeysprovision tokenn/a
PATCH/v1/admin/pubkeys/{pubkey_id}provision tokenn/a

Try it

Interactive calls from your browser.

Permalink
Try it

Runs in your browser. Paste a tenant API key and call /v1/whoami or /v1/ingest.

Tenant API key

This key stays in your browser. Do not paste production secrets on untrusted devices.

Response
JSON
No response yet.

Protocol notes

Details worth making explicit in public docs.

Permalink
Canonical JSON and commitments
Text
fields -> fields_canon
meta -> meta_canon
commit_fields with tenant salt
c_fields stored as bytes (hex exposed)
Event hash chaining
Text
event_hash = link(prev_hash, tenant_id, scope, op, c_fields, meta_canon)
Merkle batching
Text
worker batches events -> batch_id + leaf_index
proofs exist only after batching
Bundle schema versioning
Text
schema_version must be 1..=BUNDLE_SCHEMA_VERSION
Rate limiting
Text
per-key RPM (enforce_key_rpm)
per-IP burst for provisioning endpoints (enforce_ip_burst)
VEMORAK
Verifiable memory for AI systems
© 2026 Vemorak. All rights reserved.