Skip to content

API Reference

Audience: Engineers integrating against the Nanorix HTTP API directly (or building third-party SDKs). Stability contract: the API surface follows a Forever-Standard discipline — fields are additive; existing fields and identifiers are forever-stable; breaking renames do not ship. Substrate framing: Nanorix attests to the boundary; the customer composes downstream attestations; the auditor verifies independently. The API surface below is the boundary contract.

Base URL: https://api.nanorix.io

All requests and responses are JSON unless explicitly noted (file upload endpoints accept raw bytes). Authentication is Authorization: Bearer nrx_live_<32hex>. Every response carries the X-Nanorix-Request-Id header for support correlation; on errors the same value appears at error.request_id in the body.


Table of contents

  1. Authentication and tiers
  2. Capsule lifecycle
  3. Capsulefiles
  4. AuditProofs and AuditRecords
  5. Stateless verification
  6. Egress profiles
  7. Webhooks
  8. Customer account
  9. Billing
  10. Health and ops
  11. Error envelope
  12. Rate limiting and retries
  13. Idempotency
  14. Service-degradation behavior

Vocabulary note. External brand: AuditProof (standard shareable proof) and AuditRecord (the customer's private complete record). API paths still use /cdp for forever-stable structural compatibility. When the docs say "AuditProof" the JSON path inside the request still says /cdp/.... This is by design.


Authentication and tiers

Headers

Authorization: Bearer nrx_live_<32hex>
Content-Type: application/json
Idempotency-Key: <uuid>            # optional, recommended for POST mutations
X-Nanorix-Trace-Id: <uuid>          # optional, propagated through logs

The API key is issued once at signup and cannot be retrieved later (Nanorix stores only a SHA-256 hash). Test-mode keys (nrx_test_...) operate against the same surface but do not generate billable AuditProofs.

Tier surface (external naming)

Tier Wire identifier AuditProofs / month
Free free 100 included
Developer starter per plan
Team business per plan
Enterprise enterprise per plan

Tier naming: the wire values starter / business are forever-stable identifiers; public surfaces, marketing, and docs use Developer / Team. Both are accepted in tier-relevant API requests.

Free tier offers evaluation-on-ramp limits. Developer / Team / Enterprise pricing is contact-based; see billing.

Rate limits

Per-customer rate limit is rate_limit_rpm (returned by GET /v1/customers/me). Exceeding it returns 429 rate_limited with a Retry-After header in seconds. Public endpoints (signup, verify, contact, keys) are rate-limited per source IP.

Surface Limit
POST /v1/signup 10 / min / IP
POST /v1/verify 60 / min / IP
POST /v1/contact 5 / min / IP
GET /v1/capsules/:id/cdp/verify 30 / min / IP
Authenticated endpoints per-tier rate_limit_rpm

Capsule lifecycle

POST /v1/capsules

Create an ephemeral capsule. Authenticated.

Request:

{
  "ttl": 300,
  "data_classification": "PHI",
  "capsulefile_id": "cf_018f...",
  "egress_profile_id": "egp_018f...",
  "metadata": {"matter_id": "ACME-001"}
}
Field Type Required Description
ttl integer no TTL in seconds (default: 300). Capped by tier max_ttl_seconds.
data_classification string no general (default), PHI, PII, financial, credentials. Customer-declared; recorded verbatim in capsule_context.
capsulefile_id string no Pin a specific Capsulefile (cf_<id>).
egress_profile_id string no Named egress profile (Team+).
metadata object no Free-form {string:string}, ≤4KB total, customer-defined keys (the nanorix:* prefix is reserved).

Response (201):

{
  "id": "cap_018f5b8e-...",
  "status": "active",
  "ttl_seconds": 300,
  "created_at": "2026-...Z",
  "expires_at": "2026-...Z",
  "capsulefile_id": "cf_018f...",
  "data_classification": "PHI"
}

Errors: 400 invalid_parameters, 401 invalid_api_key, 402 quota_exceeded, 403 tier_required (egress profiles are Team+), 429 rate_limited.

GET /v1/capsules/:id

Get capsule status. Authenticated; must own the capsule.

Response (200):

{
  "id": "cap_018f5b8e-...",
  "status": "active",
  "ttl_seconds": 300,
  "created_at": "2026-...Z",
  "expires_at": "2026-...Z",
  "execution_count": 2,
  "bytes_processed": 1024,
  "metadata": {"matter_id": "ACME-001"}
}

status enum: active, executing, destroyed, expired, expired_unclean.

POST /v1/capsules/:id/exec

Execute a command inside the capsule. Authenticated.

Request:

{
  "command": "python main.py",
  "input_data": "{\"record_id\":\"P-1234\"}",
  "input_filename": "input.json",
  "timeout": 30
}
Field Type Required Description
command string yes Shell command (max 10 KB).
input_data string no Written to capsule filesystem before exec (max 512 KB).
input_filename string no Path under tmpfs (default: /data/input/data).
timeout integer no Seconds before SIGTERM. Defaults to capsule TTL.

Response (200):

{
  "capsule_id": "cap_018f5b8e-...",
  "stdout": "extraction complete",
  "stderr": "",
  "exit_code": 0,
  "duration_ms": 1240,
  "executed_at": "2026-...Z",
  "timed_out": false
}

Security note: sensitive bytes go in input_data, not in the command string. input_data is written to a tmpfs file inside the sealed sandbox; command-line arguments would be visible at /proc/PID/cmdline.

Errors: 400 invalid_parameters, 401 invalid_api_key, 404 resource_not_found, 410 capsule_expired, 413 payload_too_large, 503 service_unavailable (see degradation).

POST /v1/capsules/:id/upload

Upload a file to the capsule's input area. Authenticated. Body is raw bytes; filename is the ?filename= query param.

curl -X POST https://api.nanorix.io/v1/capsules/cap_.../upload?filename=input.pdf \
  -H "Authorization: Bearer nrx_live_..." \
  --data-binary @input.pdf

Response (200):

{"capsule_id":"cap_...","filename":"input.pdf","size_bytes":48132,"sha512":"sha512:..."}

Errors: 413 payload_too_large (tier-bounded), 404 resource_not_found, 410 capsule_expired.

GET /v1/capsules/:id/output

List output files written by the capsule. Authenticated.

Response (200):

{
  "capsule_id": "cap_...",
  "files": [
    {"filename":"extraction.json","size_bytes":2048,"sha512":"sha512:..."}
  ]
}

GET /v1/capsules/:id/output/:filename

Download an output file (binary body).

Response (200): raw file body. Content-Type reflects the file extension; X-Nanorix-File-Sha512 header carries the integrity digest.

DELETE /v1/capsules/:id

Destroy the capsule. Returns the AuditRecord (schema v2.0) with the AuditProof projection embedded. Authenticated.

Response (200):

{
  "capsule_id": "cap_...",
  "status": "destroyed",
  "destroyed_at": "2026-...Z",
  "total_executions": 2,
  "total_bytes_processed": 1024,
  "cdp": { /* AuditRecord — see the AuditProof specification */ },
  "audit_proof": { /* projection of cdp; the shareable AuditProof */ },
  "regulatory_context": {
    "notice": "This mapping identifies regulatory provisions potentially related to the destruction evidence. It is not a compliance certification.",
    "framework_version": "2026-02",
    "mappings": [...]
  },
  "capsule_context": {"jurisdiction":"US","data_classification":"PHI"}
}

The full schema is in the AuditProof specification. Field-level discipline:

  • audit_proof is the shareable projection (no internal-org metadata; safe to send to auditors).
  • cdp is the AuditRecord — the customer's private complete record (includes activity trail, internal IDs).
  • regulatory_context is a factual reference map; never COMPLIANT/PASSED/MEETS.

Capsulefiles

A Capsulefile is the customer's sealed environment definition. The customer is the sole declarer of its contents; Nanorix injects nothing beyond the published base-image spec.

POST /v1/capsulefiles

Create a draft Capsulefile. Authenticated.

Request:

{
  "name": "med-ai-extraction",
  "runtime": "python3.12",
  "entrypoint": "python main.py",
  "data_classification": "PHI",
  "egress_declaration": ["api.anthropic.com"],
  "default_ttl": 1800,
  "memory_mb": 2048,
  "env_vars": {"ANTHROPIC_MODEL": "claude-3-5-sonnet-latest"},
  "packages": {"python": ["anthropic==0.39.0"]}
}

Response (201):

{"id":"cf_018f...","status":"draft","name":"med-ai-extraction","content_hash":null,"created_at":"2026-...Z"}

POST /v1/capsulefiles/:id/files

Upload a source file to the Capsulefile (raw body, ?filename= query param). Repeatable.

curl -X POST 'https://api.nanorix.io/v1/capsulefiles/cf_.../files?filename=main.py' \
  -H "Authorization: Bearer nrx_live_..." \
  --data-binary @main.py

Response (200): {"capsulefile_id":"cf_...","filename":"main.py","size_bytes":1234,"sha512":"sha512:..."}

POST /v1/capsulefiles/:id/finalize

Finalize the Capsulefile. Locks file set, computes the content_hash, transitions draft → finalized. After finalize the Capsulefile is immutable.

Response (200):

{
  "id": "cf_...",
  "status": "finalized",
  "content_hash": "sha512:...",
  "size_bytes": 48132,
  "file_count": 6,
  "finalized_at": "2026-...Z"
}

GET /v1/capsulefiles

List your Capsulefiles. Includes is_system: true Capsulefiles (Nanorix-published reference shapes — read-only, not deletable).

Response (200):

{
  "items": [
    {"id":"cf_...","name":"med-ai-extraction","status":"finalized","is_system":false,"content_hash":"sha512:..."}
  ],
  "next_cursor": null
}

GET /v1/capsulefiles/:id

Response (200): full Capsulefile detail including file manifest:

{
  "id":"cf_...",
  "name":"med-ai-extraction",
  "status":"finalized",
  "runtime":"python3.12",
  "entrypoint":"python main.py",
  "egress_declaration":["api.anthropic.com"],
  "default_ttl":1800,
  "memory_mb":2048,
  "env_vars":{"ANTHROPIC_MODEL":"claude-3-5-sonnet-latest"},
  "files":[
    {"filename":"main.py","size_bytes":1234,"sha512":"sha512:..."}
  ],
  "content_hash":"sha512:..."
}

DELETE /v1/capsulefiles/:id

Delete a Capsulefile and its stored files. Existing AuditProofs that referenced it remain valid — each AuditProof carries its own bound content_hash.

Response (204): no body.

Errors: 409 resource_conflict if active capsules still reference this Capsulefile.


AuditProofs and AuditRecords

GET /v1/capsules/:id/cdp

Retrieve the full AuditRecord for a capsule. Authenticated.

Response (200): the full AuditRecord (schema v2.0) as documented in the AuditProof specification.

GET /v1/capsules/:id/cdp/verify

Retrieve the shareable AuditProof projection. Public endpoint (no auth) — rate limited to 30 / min / IP.

Response (200): the AuditProof projection — same Ed25519 signature as the AuditRecord, but stripped of activity-trail and internal metadata fields. Safe to share with auditors.

GET /v1/proofs/:capsule_id

Retrieve the stored AuditRecord for a destroyed capsule. Authenticated; must own the capsule.

Response (200):

{
  "proof_id":"prf_...",
  "capsule_id":"cap_...",
  "cdp":{...},
  "component_proofs":{},
  "verified":true,
  "created_at":"2026-...Z"
}

Errors: 401 invalid_api_key, 404 resource_not_found (capsule not yet destroyed, or you don't own it).

GET /v1/keys/:id

Retrieve the Ed25519 public key used to sign a specific AuditProof. Public endpoint, no auth.

Response (200):

{
  "key_id":"nrx-verify-2026-...-cap_018f",
  "algorithm":"Ed25519",
  "public_key":"WN+jK9k7AUxfU9KXkqEXBU6j6ensSx2qqGJdnnr4lYI=",
  "created_at":"2026-...Z",
  "signing_authority_id":"us-kms-nanorix-v1",
  "signing_key_version":7
}

The public_key is raw base64 (no base64: prefix) for direct decode by verifiers.


Stateless verification

POST /v1/verify

Verify an AuditProof JSON document. Public endpoint, no auth, 60 / min / IP.

Request:

{"cdp":{"cdp_version":"1.0","chain":[...],"final_hash":"sha512:...","attestation":{...}}}

Response (200):

{
  "valid": true,
  "checks": {
    "cdp_version_valid": true,
    "schema_valid": true,
    "chain_integrity": true,
    "final_hash_valid": true,
    "canonical_hash_valid": true,
    "signing_key_resolved": true,
    "signature_valid": true,
    "authority_status": "active",
    "algorithm": "Ed25519"
  },
  "stage_reached": 8,
  "verified_at": "2026-...Z"
}

On failure:

{
  "valid": false,
  "stage_reached": 6,
  "failure_reason": {"type":"SignatureMismatch","reason":"DoesNotVerify"},
  "verified_at": "2026-...Z"
}

failure_reason follows a closed-set failure-reason enum — the same enum returned by the offline verifiers, byte-equivalent across implementations.

Nothing is stored. The AuditProof is verified and discarded.


Egress profiles

Egress profiles let you define named domain/IP allowlists for outbound traffic from your capsules. Available on Team tier and above.

POST /v1/egress/profiles

Create a profile. Authenticated. Team+.

Request:

{"name":"med-ai-egress","description":"Allowlist for clinical extraction"}

Response (201): {"id":"egp_...","name":"med-ai-egress","description":"...","created_at":"..."}

GET /v1/egress/profiles

List profiles. Response (200): {"items":[...],"next_cursor":null}

GET /v1/egress/profiles/:id

Get profile + rules. Response (200):

{
  "id":"egp_...","name":"med-ai-egress","rules":[
    {"id":"egr_...","kind":"domain","value":"api.anthropic.com","port":443}
  ]
}

PUT /v1/egress/profiles/:id

Update profile name/description.

DELETE /v1/egress/profiles/:id

Delete profile. Errors: 409 resource_conflict if active capsules reference it.

POST /v1/egress/profiles/:id/rules

Add a rule. Team+.

Request:

{"kind":"domain","value":"api.anthropic.com","port":443}

kind enum: domain, ip_v4, ip_v6, cidr_v4, cidr_v6.

GET /v1/egress/profiles/:id/rules

List rules in a profile.

DELETE /v1/egress/profiles/:id/rules/:rid

Remove a rule.

Backwards-compatible flat endpoints

Nanorix retains pre-profile-feature endpoints for forever-stability:

  • GET /v1/egress — list all rules flat.
  • POST /v1/egress — add rule to default profile (Team+).
  • DELETE /v1/egress/:id — remove rule by id.

Webhooks

See the full webhooks integration guide. API surface:

POST /v1/webhooks

Register an endpoint.

Request: {"url":"https://...","events":["cdp.generated","capsule.failed"],"description":"..."}

Response (201): {"id":"whse_...","url":"...","events":[...],"signing_secret":"whsec_...","created_at":"..."}

The signing_secret is shown once. Save it. HMAC-SHA256 verification recipes are in integrations/webhooks.md.

GET /v1/webhooks

List endpoints.

GET /v1/webhooks/:id

Get one endpoint.

PUT /v1/webhooks/:id

Update url / events / description / active status.

DELETE /v1/webhooks/:id

Delete an endpoint.

POST /v1/webhooks/:id/test

Send a test event. Response: delivery record.

GET /v1/webhooks/:id/deliveries

List recent deliveries (success + failed). Returns attempt_number, response_status, response_body, duration_ms, retry schedule.

Failure handling

A delivery is retried up to 5 times with exponential backoff (1m, 5m, 30m, 2h, 10h). After the 5th failure the delivery moves to a dead-letter queue. Admin endpoints for replaying DLQ items are scoped to Enterprise tier; see integrations/webhooks.md for the operational surface.


Customer account

POST /v1/signup

Self-service signup. Public, 10 / min / IP.

Request: {"email":"you@company.com","jurisdiction":"US"}

jurisdiction enum: US, EU, UK, CA, AU, IN, OTHER. Customer-declared, recorded verbatim, never inferred.

Response (201):

{
  "api_key":"nrx_live_...",
  "customer_id":"...",
  "email":"...","tier":"free","jurisdiction":"US",
  "limits":{"capsules_per_month":100,"max_ttl_seconds":300,"concurrent_capsules":3,"rate_limit_rpm":20},
  "warning":"Store this API key securely — it will not be shown again."
}

GET /v1/customers/me

Get your account, usage, limits.

Response (200):

{
  "customer_id":"...","email":"...","tier":"free","jurisdiction":"US",
  "limits":{"capsules_per_month":100,"max_ttl_seconds":300,"concurrent_capsules":3,"rate_limit_rpm":20},
  "usage":{
    "capsules_used_this_month":5,
    "cdps_this_cycle":5,
    "cdp_limit":100,
    "billing_cycle_start":"2026-...Z"
  },
  "created_at":"2026-...Z"
}

cdp_limit is 100 for Free tier and null (unlimited) for Developer / Team / Enterprise.

POST /v1/contact

Submit a contact form. Public, 5 / min / IP.

Request: {"email":"...","name":"...","company":"...","message":"...","source":"pricing_page"}

Response (200): {"status":"received"}


Billing

POST /v1/billing/checkout

Upgrade tier through Stripe Checkout. Authenticated.

Request:

{"tier":"developer"}

tier accepts both external (developer, team) and internal (starter, business) names per the tier-naming split. Enterprise contracts go through support@nanorix.io rather than self-service Checkout.

Response (200):

{"checkout_url":"https://checkout.stripe.com/..."}

The Checkout URL expires in 24 hours. After successful payment, your account tier updates within ~10 seconds via the Stripe webhook.

POST /v1/billing/webhook

Stripe webhook receiver. HMAC-verified. Not customer-callable; documented for transparency.


Health and ops

GET /v1/health

Response (200): {"status":"healthy","version":"1.0.0","timestamp":"2026-08-07T00:00:00Z"}

GET /v1/health/ready

Readiness probe. Returns 200 only when DB connectivity is healthy; 503 otherwise.


Error envelope

Every error response uses the same envelope:

{
  "error": {
    "type": "<category>",
    "code": "<machine_code>",
    "message": "<human>",
    "doc_url": "https://docs.nanorix.io/errors/<code>",
    "request_id": "req_018f5b8e-...",
    "retry_after": 30
  }
}

request_id is also returned in the X-Nanorix-Request-Id response header (every request — success or error). Quote it when filing support tickets.

For 422 errors, the envelope carries an additional reason (typed enum) and an error-specific payload field (input_hash for input_provenance_rejected; secret_id for secrets_injection_rejected). See the error catalog.

Closed-set error codes

HTTP Code Category
400 invalid_parameters invalid_request_error
401 invalid_api_key authentication_error
402 quota_exceeded quota_error
403 mfa_required mfa_required
403 mfa_enrollment_required mfa_enrollment_required
403 tier_required forbidden
404 resource_not_found not_found
409 resource_conflict conflict_error
410 capsule_expired capsule_error
413 payload_too_large invalid_request_error
422 input_provenance_rejected input_provenance_error
422 secrets_injection_rejected secrets_injection_error
429 rate_limited rate_limit_error
500 internal_error api_error
503 service_unavailable service_unavailable

Each code is documented in the error catalog with cause + customer-side fix.


Rate limiting and retries

429 responses include a Retry-After header (seconds) and a retry_after field in the body. Both Nanorix SDKs implement exponential backoff with jitter automatically (default: 4 retries with full-jitter backoff capped at 30s). For raw HTTP integrations the retry recipe is:

attempt n: wait min(retry_after, 2^n + random(0,1)) seconds
abort after 4 retries

Client-side timeouts: recommend ≥30s for capsule create, ≥120s for capsule exec, ≥60s for AuditProof verification; the actual wall-clock cap is the per-capsule TTL.


Idempotency

POST mutations accept an Idempotency-Key: <uuid> header. Replays within 24 hours return the original response (status, body, headers) without re-running the side effect. Recommended for:

  • POST /v1/capsules (capsule create)
  • POST /v1/capsulefiles (Capsulefile create)
  • POST /v1/capsulefiles/:id/finalize (Capsulefile finalize)
  • POST /v1/billing/checkout

Stripe-internal Idempotency-Keys are auto-generated for our Stripe calls so customer-side retries cannot trigger duplicate charges.


Service-degradation behavior

When dependent infrastructure is degraded (KMS unavailable, signing-key rotation in flight, sealed-secret injector offline) capsule-affecting endpoints return 503 service_unavailable:

{
  "error":{
    "type":"service_unavailable",
    "code":"service_unavailable",
    "message":"capsule create temporarily unavailable",
    "request_id":"req_...",
    "retry_after":15,
    "degradation_mode":"signing_key_unavailable"
  }
}

The Retry-After header is set; SDKs auto-retry. Signing-completion races and KMS endpoint version-policy renegotiation are handled fire-and-buffer with two-phase destroy + retry-signing, so customer-visible 503 is the boundary state, not the steady state.

The degradation_mode enum is closed-set:

  • signing_key_unavailable — signing service degraded; capsule destroy buffered, AuditProof emission delayed.
  • kms_endpoint_negotiating — KMS endpoint version policy in renegotiation.
  • sealed_secret_injector_offline — secret injection unavailable; capsule create rejected.
  • dlq_replay_in_progress — webhook DLQ replay throttling new deliveries.

Admin (Enterprise tier) endpoints for inspecting current degradation state and DLQ replay are scoped under /v1/admin/*.


Cross-references

Forever-Standard discipline. Every field above is permanent. New fields arrive as additive extensions; new endpoints are additive. The request_id / trace_id discipline plus the closed-set failure-reason enum are the structural contract that makes "evidence outlives Nanorix" possible.