OffshoreProz
Agent API
Programmatic company formation for AI agents and the people behind them. One API call starts a Wyoming LLC or Marshall Islands DAO LLC; a human owner completes KYC, authorizes payment, and signs.
Overview
Introduction
The OffshoreProz Agent API is the HTTP surface for forming real legal entities programmatically. It is intentionally agent-initiated, human-controlled: an agent can start a formation, but every formation always has an identifiable human (or legal entity) beneficial owner who completes KYC, authorizes the payment, and signs. There is no such thing as “the AI owns itself” — a human is always responsible.
REST + MCP
A full REST surface plus a native Model Context Protocol server for LLM clients.
Consent-gated
Estimate → confirm → create. Cost and process are confirmed before anything happens.
Webhooks
HMAC-signed events for every status change, modeled on the Stripe scheme.
Beta — what works today
op_test_ keys). Live mode (op_live_) is gated and coming soon. The KYC, payment, and signature providers are simulated in sandbox and always succeed. Wyoming is the first end-to-end jurisdiction; Marshall Islands is a launch target; Nevada, BVI, Panama, and UAE are coming soon.Security
Authentication
The API uses three distinct credential families — never mix them. Developers authenticate with an API key; human owners advance a formation with a single-use action token; downloads use a short-lived download token.
API keys — Bearer auth
Pass your key in the Authorization header. Only the SHA-256 hash of a key is ever stored; the raw value is shown once at creation and never persisted.
Authorization: Bearer op_test_a1b2c3d4e5f6...op_test_*Available nowSandbox / test mode. Never triggers a real filing or charge. Providers are simulated and always succeed. Fixed 200 req/min regardless of tier.
op_live_*Coming soonLive / production mode. Real providers behind a sandbox/live switch. Currently gated — any op_live_ key returns 403 live_mode_not_available before any DB lookup.
Key safety
op_live_ keys like passwords — they authorize real charges and filings once live. Rotate by revoking (revoked_at is set; keys are never hard-deleted, preserving audit). The same key governs both REST and MCP.Auth error responses
| Situation | HTTP | code |
|---|---|---|
| Header missing / malformed / bad prefix | 401 | invalid_api_key |
| op_live_ with the gate closed (beta) | 403 | live_mode_not_available |
| Key revoked | 401 | api_key_revoked |
| Key mode ≠ presented prefix | 401 | invalid_api_key |
Reference
Base URL & versioning
All REST endpoints live under a single base URL with the /v1 version prefix. The MCP server is served from the same host at /mcp.
https://api.offshoreproz.com/v1https://api.offshoreproz.com/mcphttps://api.offshoreproz.com/openapi.jsonSuccess envelope
{ "data": { ... }, "request_id": "<trace>" }Correlation header (every response)
X-Request-Id: <trace_id>The MCP server lives at api.offshoreproz.com/mcp — never mcp.offshoreproz.com. The OpenAPI spec is currently a stub that covers a subset of endpoints; this page is the reference until it is complete.
Get going
Quick start
Create a formation in two calls: a public estimate that returns the estimate_token required by create, then the create call itself. Use an op_test_ key — everything runs in sandbox.
# 1. Estimate (public) — returns the estimate_token required to create.
curl -X POST https://api.offshoreproz.com/v1/jurisdictions/WY/estimate \
-H "Content-Type: application/json" \
-d '{ "obtain_ein": true, "members_count": 1 }'
# 2. Create the formation (sandbox — op_test_).
curl -X POST https://api.offshoreproz.com/v1/formations \
-H "Authorization: Bearer op_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-unique-key-001" \
-d '{
"jurisdiction": "WY",
"company_name": "NeuralVentures LLC",
"estimate_token": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"beneficial_owner": {
"full_name": "Heitor Rocha",
"email": "[email protected]",
"address": { "street": "1 Market St", "city": "Cheyenne", "country": "US" }
}
}'REST API reference
System
Public, unauthenticated diagnostics. /health is an internal diagnostic, not a status page or SLA commitment.
/healthPublicLiveness check with a live portal-DB health probe. Use as a post-deploy smoke test.
{
"data": {
"status": "ok",
"env": "production",
"version": "<API_VERSION>",
"service": "offshoreproz-agent-api",
"portal_db": { "reachable": true, "portal_sync_enabled": true }
},
"request_id": "<trace>"
}/openapi.jsonPublicReturns the OpenAPI 3.1 spec (currently a stub). Cache-Control: public, max-age=300.
Jurisdictions
Public endpoints (no API key). Pricing is the canonical source for the SDK. Codes are WY · MI · NV · BVI · PA · UAE.
/v1/jurisdictionsPublicLists jurisdictions. By default returns available + pilot (WY and MI). Pass ?include_coming_soon=true for the rest.
/v1/jurisdictions/:codePublicFull configuration for one jurisdiction. 404 if the code does not exist.
/v1/jurisdictions/:code/requirementsPublicRequired fields + legal note, used to build the intake form. 422 if coming_soon.
/v1/jurisdictions/:code/estimatePublicPure calculation — creates no DB record. Generates the estimate_token (30-min KV TTL) required to create a formation.
{
"data": {
"estimate_token": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"jurisdiction_code": "WY",
"total_usd": 499,
"currency": "USD",
"breakdown": [ ... ],
"annual_maintenance_usd": 185,
"eta_days": "1-2",
"expires_at": "<iso + 30m>"
},
"request_id": "<trace>"
}Formations
All endpoints require an API key. Ownership is always scoped to your key — a key only sees and changes its own formations (otherwise 404, never 403, so existence is never leaked).
/v1/formationsAPI keyCreates a formation in status pending_owner_confirmation and mints the owner action token. Accepts Idempotency-Key; body capped at 256 KB.
{
"jurisdiction": "WY",
"company_name": "NeuralVentures LLC",
"company_purpose": "any lawful business purpose",
"obtain_ein": true,
"management_structure": "member_managed",
"estimate_token": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"beneficial_owner": {
"full_name": "Heitor Rocha",
"email": "[email protected]",
"phone": "+1 555 000 0000",
"address": { "street": "1 Market St", "city": "Cheyenne", "country": "US" },
"ownership_percentage": 100,
"id_document_type": "passport"
},
"agent_context": { "agent_name": "ResearchBot", "platform": "claude" }
}{
"data": {
"formation_id": "frm_66df942fd60f4b5b",
"status": "pending_owner_confirmation",
"mode": "test",
"jurisdiction": "WY",
"company_name": "NeuralVentures LLC",
"next_actions": [
{ "type": "owner_confirmation",
"url": "https://docs.offshoreproz.com/portal/actions/act_..." }
],
"sandbox": true,
"portal_sync_status": "not_attempted",
"legal_disclaimer": "This is not legal, tax, or financial advice."
},
"request_id": "<trace>"
}/v1/formationsAPI keyLists your formations. Query: limit (default 20, max 100), cursor (created_at DESC), status filter. Returns has_more + next_cursor.
/v1/formations/:idAPI keyCurrent state + last 10 events + reconstructed next_actions. The action URL points at the reissue endpoint (raw tokens are only returned once, at mint).
/v1/formations/:id/eventsAPI keyFull append-only audit timeline. Query: limit (default 100, max 500). Each event carries actor_type, from/to status, trace_id.
/v1/formations/:id/retryAPI keyResets a failed / action_required formation back to pending_owner_confirmation and clears the error. Fires formation.status_changed.
/v1/formations/:id/actions/reissueAPI keyMints a fresh owner action token for the current step, invalidating the previous one. 422 no_pending_action if there is no pending step.
/v1/formations/:idAPI keyCancels a formation while in draft or pending_owner_confirmation. Sets status=cancelled and fires formation.cancelled.
Owner actions
Public, but authenticated by a single-use action token (act_*) — no API key. The high-entropy token is the bearer credential. These power the owner-facing page at docs.offshoreproz.com/portal/actions/{token}. Only a non-sensitive summary is returned — never PII.
/v1/actions/:tokenAction / download tokenInspects an action token: purpose, label, expiry, and a non-sensitive formation summary. 422 if expired / consumed / not found.
/v1/actions/:token/confirmAction / download tokenThe owner advances one step. Validates and atomically consumes the token (preventing double-confirm races), walks the state machine, and mints the next step's token.
{
"data": {
"formation_id": "frm_66df942fd60f4b5b",
"status": "kyc_pending",
"step_completed": "owner_confirmation",
"confirmed": true,
"next_action": {
"type": "human_kyc",
"url": "https://docs.offshoreproz.com/portal/actions/act_...",
"sandbox_note": "Sandbox: KYC is simulated and auto-approves."
}
},
"request_id": "<trace>"
}Documents
Objects are private; downloads are streamed by the Worker via a short-lived download token (dl_*, ~5-min KV TTL). Upload limit is 10 MB.
/v1/formations/:id/documentsAPI keyUploads a document (base64). document_type ∈ articles_of_organization, operating_agreement, ein_confirmation, registered_agent_acceptance, certificate_of_formation, invoice, kyc_document, other.
/v1/formations/:id/documentsAPI keyLists documents for a formation (newest first). Returns metadata only — no R2 keys.
/v1/documents/:idAPI keyDocument metadata + a freshly minted download_url with a ~5-minute download token.
/v1/documents/:id/download?token=Action / download tokenStreams the file bytes. The dl_ token in the query is the credential (no API key). Content-Disposition: attachment.
Webhooks API
Manage delivery endpoints. Each API key may have up to 10 active webhooks. See for the event catalog and signature scheme.
/v1/webhooksAPI keyRegisters an endpoint. url must be HTTPS (≤500 chars); events[] defaults to ['*']. Returns the signing_secret (whsec_...) exactly once — store it now, it is not recoverable.
{
"data": {
"id": "wh_a1b2c3d4e5f6a7b8c9",
"url": "https://example.com/hooks/offshoreproz",
"events": ["formation.*"],
"active": true,
"signing_secret": "whsec_9f8e7d6c..." // shown only once
},
"request_id": "<trace>"
}/v1/webhooksAPI keyLists all webhooks for the key, including inactive ones.
/v1/webhooks/:idAPI keyOne webhook. The signing secret is not recoverable here.
/v1/webhooks/:idAPI keySoft-deletes the endpoint (active = 0).
/v1/webhooks/:id/deliveriesAPI keyDelivery attempts (default 50, max 100): status, response_status, attempt_number, next_retry_at, delivered_at.
Admin & beta
Admin routes model the team-assisted filing hand-off and require the ADMIN_API_TOKEN (an env var, not a customer API key). The public beta waitlist is the interim path while live keys are not yet self-serve.
/v1/admin/formations/:id/filing/startAdmin tokenfiling_ready → filing_in_progress. 422 filing_not_ready if it cannot transition.
/v1/admin/formations/:id/filing/completeAdmin tokenWalks filing_in_progress → registration_complete → … → complete (filing simulated). Sets completed_at; returns filing_reference.
/v1/beta/waitlistPublicPublic. Join the beta waitlist (idempotent on email). Body: email (required), name?, company?, use_case?, platform?.
/v1/beta/waitlistAdmin tokenAdmin. Returns up to 200 waitlist entries + count.
Concepts
Formation lifecycle
A formation moves through a strict state machine — every mutation passes through canTransition(). There are 19 states, three of them terminal. Each transition is recorded in an append-only audit log with an identifiable actor.
All 19 states
draftpending_owner_confirmationportal_syncedkyc_pendingkyc_reviewkyc_approvedkyc_failedpayment_pendingpayment_authorizedsignature_pendingfiling_readyfiling_in_progressregistration_completeein_pendingdocuments_readycompleteaction_requiredfailedcancelledActors
api_key (agent), owner (human), webhook (provider), admin (ops), or system.kyc_approved — beta note
MCP server
The MCP server is the native entry point for AI agents and builders. It speaks the Model Context Protocol (JSON-RPC 2.0) that clients like Claude, Cursor, and VS Code already know how to discover and invoke. It is a thin dispatch layer over the same REST router — so behavior never diverges.
JSON-RPC endpoint
POST https://api.offshoreproz.com/mcpDiscovery hint
GET https://api.offshoreproz.com/mcpThe 6 tools
offshoreproz_list_jurisdictionspublicLists jurisdictions with pricing and ETA.
When: Call this first — discover what's available (WY and MI appear by default).
offshoreproz_get_jurisdiction_requirementspublicReturns the required fields for a jurisdiction.
When: Before collecting intake — to build the form the human will confirm.
offshoreproz_estimate_costAPI keyEstimates the all-in cost and returns the estimate_token required to create.
When: After the human picks a jurisdiction; dispatches to POST /v1/jurisdictions/{code}/estimate.
offshoreproz_create_formationAPI keyStarts the formation. Requires a recent estimate_token AND user_confirmed_cost_and_process=true.
When: Only after presenting cost, timeline, KYC, payment, signature, and the not-legal-advice notice.
offshoreproz_get_formation_statusAPI keyCurrent status plus the next action.
When: To poll progress; dispatches to GET /v1/formations/{formation_id}.
offshoreproz_list_documentsAPI keyLists the documents available for a formation.
When: Once documents are ready; dispatches to GET /v1/formations/{id}/documents.
The consent gate
create_formation is defended by two independent barriers. First, the MCP-specific flag user_confirmed_cost_and_process=true — the agent may only set it after showing the human the cost, timeline, KYC, payment, signature, and the not-legal-advice notice. Second, a recent estimate_token (30-min TTL) that the REST handler re-validates against KV, with a jurisdiction match. An LLM cannot impulsively create a formation without first estimating and getting explicit human confirmation.Calling a tool (JSON-RPC)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "offshoreproz_estimate_cost",
"arguments": { "jurisdiction": "WY", "obtain_ein": true }
}
}Authenticated tools read the same Authorization: Bearer op_... header (or X-API-Key fallback). Tool errors return a successful result with isError: true in the content, so the LLM can react — distinct from JSON-RPC protocol errors.
Webhooks
Subscribe to formation events. Every delivery is signed with HMAC-SHA256 in the Stripe scheme, so you can reuse Stripe-style verification libraries and mental model.
Event catalog
| Event | Fires when |
|---|---|
formation.created | Formation created via POST /v1/formations. |
formation.status_changed | Status transition without a more specific event; retry; lifecycle step confirmation. |
formation.payment_received | Stripe payment confirmed (fired by inbound /webhooks/stripe — beta). |
formation.signed | Document signed in DocSeal (fired by inbound /webhooks/signature — beta). |
formation.filed | Filing submitted to the state (WY) / registered agent (MI). |
formation.portal_synced | Project mirrored into PORTAL_DB successfully (live mode). |
formation.portal_sync_failed | Portal sync failed. |
formation.cancelled | Formation cancelled via DELETE /v1/formations/:id. |
formation.complete | Formation completed — documents ready. |
formation.error | Unrecoverable lifecycle error. |
Subscribe with events[] using exact names, formation.*, or * (up to 20 patterns). The envelope mirrors Stripe: id · type · created · livemode · data, and data.formation_id is always present — dedupe on id (the same evt_* is kept across retries).
Signature header
X-OffshoreProz-Signature: t=<unix_ts_seconds>,v1=<hmac_hex>The HMAC input is "<ts>.<raw_json_body>" (timestamp, dot, the raw body), keyed with the bytes of your whsec_ secret (hex-decode it first). Always verify against the raw body you received — re-serializing the JSON breaks the signature.
const crypto = require("crypto");
function verifyOffshoreProzWebhook(rawBody, header, whsec) {
// header: "t=1718719329,v1=abc123..."
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("="))
);
const key = Buffer.from(whsec.replace(/^whsec_/, ""), "hex");
const expected = crypto
.createHmac("sha256", key)
.update(`${parts.t}.${rawBody}`) // timestamp.raw_body
.digest("hex");
const ok =
expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
// Anti-replay: reject if |now - t| > 300s
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return ok && fresh;
}Retry policy
0ms, 30s, and 5min, then dead-letter. The same payload.id is reused across retries — dedupe on it. The signing secret whsec_* is returned exactly once at registration and is not recoverable; rotate by registering a new endpoint.Jurisdictions
Wyoming and Marshall Islands are both launch targets; Nevada, BVI, Panama, and UAE are coming soon (discoverable for display, not creatable via the API). Pricing is stored in cents server-side; the values below are the all-in totals charged to the client.
Wyoming LLC
Code: WY · status: available
- $100 state filing + $125 registered agent + $50 EIN + service
- EIN service when obtain_ein=true (ein_pending stage)
- Standard KYC (Stripe Identity, one human owner)
- Internal ETA 1–2 business days (not an SLA)
Marshall Islands DAO LLC
Code: MI · status: pilot
- Standard MIDAO package; final pricing confirmed before formation
- On-chain governance fields: governance_model, smart_contract_address, blockchain_network
- Enhanced KYC/KYB for any UBO with 25%+ governance
- No EIN step; internal ETA 7–30 days (not an SLA)
| Code | Entity | Status | All-in |
|---|---|---|---|
| WY | Wyoming LLC | available | ~$499 |
| MI | Marshall Islands DAO LLC | pilot | ~$9,500 |
| NV | Nevis LLC | coming_soon | ~$650 |
| BVI | BVI Business Company | coming_soon | ~$1,400 |
| PA | Panama SA / SRL | coming_soon | ~$900 |
| UAE | UAE Free Zone LLC (RAKEZ) | coming_soon | ~$1,500 |
Wyoming — required fields
- company_name (must include LLC / L.L.C. / Limited Liability Company)
- company_purpose · obtain_ein
- beneficial_owner.full_name · email · address
- beneficial_owner.id_document_type (passport / drivers_license / national_id)
- phone is optional
Marshall Islands — required fields
- company_name (must include "DAO LLC")
- governance_model (on_chain / hybrid / traditional)
- beneficial_owner.full_name · email · address
- beneficial_owner.ownership_percentage (25%+ triggers enhanced KYC)
- smart_contract_address, blockchain_network optional
Even for a DAO LLC, at least one identifiable human UBO must pass KYC — never “AI owns itself”. ETAs are internal estimates, not SLAs; filing is team-assisted at launch.
Idempotency & rate limits
Make creates safe to retry, and understand the per-minute throughput tiers.
Idempotency
Send an Idempotency-Key header on POST /v1/formations (≤255 chars, characters [a-zA-Z0-9_-.]). A repeated (key, body) replays the cached response with X-Idempotency-Replayed: true and no new insert. The cache lives for 24 hours.
Idempotency-Key: my-unique-key-001Rate limits
A sliding 1-minute window per API key. Sandbox keys are fixed at 200 req/min regardless of tier. Successful responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; a 429 adds Retry-After.
| Mode · tier | Limit (req/min) | Notes |
|---|---|---|
| test · free / pro / enterprise | 200 | Sandbox override (any tier) |
| live · free | 50 | Live mode (beta) |
| live · pro | 1000 | Live mode (beta) |
| live · enterprise | 999999 | Effectively unlimited, still tracked |
| unauthenticated | 50 | Edge-protected public routes |
Errors
Every error uses the same shape and a stable, machine-readable code. Always include the request_id when contacting support.
{
"error": "Human-readable message.",
"code": "estimate_token_invalid",
"request_id": "<trace>",
"docs": "https://docs.offshoreproz.com/api/errors/estimate-token-invalid"
}| HTTP | code | When |
|---|---|---|
| 400 | validation_error | Zod / body validation failed, body over 256 KB, or invalid Idempotency-Key. |
| 401 | invalid_api_key | Auth missing / malformed / bad prefix / mode mismatch. |
| 401 | api_key_revoked | Key has been revoked (revoked_at set). |
| 402 | payment_failed | Reserved for real payment providers. |
| 403 | live_mode_not_available | An op_live_ key was presented while the live gate is closed (beta). |
| 403 | forbidden | Operation not permitted. |
| 404 | not_found | Resource missing, or not owned by your key (ownership is never leaked as 403). |
| 405 | method_not_allowed | Method not supported on the route. |
| 409 | idempotency_key_conflict | Defined for same-key / different-body conflicts on create. |
| 422 | jurisdiction_not_available | Jurisdiction is coming_soon or does not exist. |
| 422 | estimate_token_invalid | estimate_token expired or absent from KV (30-min TTL). |
| 422 | estimate_token_jurisdiction_mismatch | estimate_token was issued for a different jurisdiction. |
| 422 | formation_not_retryable | retry called on a formation that is not failed / action_required. |
| 422 | formation_not_cancellable | delete called on a formation past draft / pending_owner_confirmation. |
| 422 | no_pending_action | reissue called with no pending owner step. |
| 422 | action_token_expired | Action token past its ~14-day TTL. |
| 422 | action_token_consumed | Action token already used (single-use). |
| 422 | step_out_of_order | Owner step attempted out of sequence. |
| 422 | illegal_transition | State-machine rejected the transition. |
| 422 | document_too_large | Upload exceeds 10 MB (also invalid_base64 / empty_document). |
| 422 | webhook_limit_reached | More than 10 active webhooks for the key. |
| 429 | rate_limit_exceeded | Rate limit hit; includes Retry-After. |
| 500 | internal_error | Unhandled exception. |
| 503 | service_unavailable | Service temporarily unavailable. |
Start building in sandbox
Use an op_test_ key to run the full lifecycle without creating a real entity. Live keys are coming soon.