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¶
- Authentication and tiers
- Capsule lifecycle
- Capsulefiles
- AuditProofs and AuditRecords
- Stateless verification
- Egress profiles
- Webhooks
- Customer account
- Billing
- Health and ops
- Error envelope
- Rate limiting and retries
- Idempotency
- Service-degradation behavior
Vocabulary note. External brand: AuditProof (standard shareable proof) and AuditRecord (the customer's private complete record). API paths still use
/cdpfor 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/businessare 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 thecommandstring.input_datais 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):
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_proofis the shareable projection (no internal-org metadata; safe to send to auditors).cdpis the AuditRecord — the customer's private complete record (includes activity trail, internal IDs).regulatory_contextis a factual reference map; neverCOMPLIANT/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:
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:
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 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_secretis 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 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):
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:
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¶
- Error catalog — every error code, cause, and customer-side fix.
- SDK reference (Python) — every Python SDK method mapped to this API surface.
- SDK reference (TypeScript) — TypeScript mirror.
- Webhooks — full event catalog + HMAC verification.
- AuditProof specification — full AuditProof / AuditRecord schema.
- Verification — verify an AuditProof online, with the SDK, or fully offline.
Forever-Standard discipline. Every field above is permanent. New fields arrive as additive extensions; new endpoints are additive. The
request_id/trace_iddiscipline plus the closed-set failure-reason enum are the structural contract that makes "evidence outlives Nanorix" possible.