Sandbox live · API in beta

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

The API creates and moves formations in sandbox only (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.

request header
Authorization: Bearer op_test_a1b2c3d4e5f6...
op_test_*Available now

Sandbox / 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 soon

Live / 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.

Auth error responses

SituationHTTPcode
Header missing / malformed / bad prefix401invalid_api_key
op_live_ with the gate closed (beta)403live_mode_not_available
Key revoked401api_key_revoked
Key mode ≠ presented prefix401invalid_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.

Production base URLhttps://api.offshoreproz.com
Version prefix/v1
MCP endpointhttps://api.offshoreproz.com/mcp
OpenAPI spechttps://api.offshoreproz.com/openapi.json

Success 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" }
    }
  }'
HTTP 201 · status: pending_owner_confirmation · formation_id: frm_66df942fd60f4b5b
The estimate_token has a 30-minute TTL and is bound to the jurisdiction you estimated.
Idempotency-Key makes create safe to retry (24-hour replay window).
The 201 returns next_actions[] — a portal link the human owner uses to confirm.
In sandbox, KYC / payment / signature providers are simulated and always succeed.

REST API reference

System

Public, unauthenticated diagnostics. /health is an internal diagnostic, not a status page or SLA commitment.

GET/healthPublic

Liveness check with a live portal-DB health probe. Use as a post-deploy smoke test.

200 OK
{
  "data": {
    "status": "ok",
    "env": "production",
    "version": "<API_VERSION>",
    "service": "offshoreproz-agent-api",
    "portal_db": { "reachable": true, "portal_sync_enabled": true }
  },
  "request_id": "<trace>"
}
GET/openapi.jsonPublic

Returns 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.

GET/v1/jurisdictionsPublic

Lists jurisdictions. By default returns available + pilot (WY and MI). Pass ?include_coming_soon=true for the rest.

GET/v1/jurisdictions/:codePublic

Full configuration for one jurisdiction. 404 if the code does not exist.

GET/v1/jurisdictions/:code/requirementsPublic

Required fields + legal note, used to build the intake form. 422 if coming_soon.

POST/v1/jurisdictions/:code/estimatePublic

Pure calculation — creates no DB record. Generates the estimate_token (30-min KV TTL) required to create a formation.

201 · response (key fields)
{
  "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).

POST/v1/formationsAPI key

Creates a formation in status pending_owner_confirmation and mints the owner action token. Accepts Idempotency-Key; body capped at 256 KB.

request body
{
  "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" }
}
201 Created (key fields)
{
  "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>"
}
GET/v1/formationsAPI key

Lists your formations. Query: limit (default 20, max 100), cursor (created_at DESC), status filter. Returns has_more + next_cursor.

GET/v1/formations/:idAPI key

Current state + last 10 events + reconstructed next_actions. The action URL points at the reissue endpoint (raw tokens are only returned once, at mint).

GET/v1/formations/:id/eventsAPI key

Full append-only audit timeline. Query: limit (default 100, max 500). Each event carries actor_type, from/to status, trace_id.

POST/v1/formations/:id/retryAPI key

Resets a failed / action_required formation back to pending_owner_confirmation and clears the error. Fires formation.status_changed.

POST/v1/formations/:id/actions/reissueAPI key

Mints a fresh owner action token for the current step, invalidating the previous one. 422 no_pending_action if there is no pending step.

DELETE/v1/formations/:idAPI key

Cancels 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.

GET/v1/actions/:tokenAction / download token

Inspects an action token: purpose, label, expiry, and a non-sensitive formation summary. 422 if expired / consumed / not found.

POST/v1/actions/:token/confirmAction / download token

The 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.

200 · response (key fields)
{
  "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.

POST/v1/formations/:id/documentsAPI key

Uploads a document (base64). document_type ∈ articles_of_organization, operating_agreement, ein_confirmation, registered_agent_acceptance, certificate_of_formation, invoice, kyc_document, other.

GET/v1/formations/:id/documentsAPI key

Lists documents for a formation (newest first). Returns metadata only — no R2 keys.

GET/v1/documents/:idAPI key

Document metadata + a freshly minted download_url with a ~5-minute download token.

GET/v1/documents/:id/download?token=Action / download token

Streams 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.

POST/v1/webhooksAPI key

Registers 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.

201 Created (key fields)
{
  "data": {
    "id": "wh_a1b2c3d4e5f6a7b8c9",
    "url": "https://example.com/hooks/offshoreproz",
    "events": ["formation.*"],
    "active": true,
    "signing_secret": "whsec_9f8e7d6c..."  // shown only once
  },
  "request_id": "<trace>"
}
GET/v1/webhooksAPI key

Lists all webhooks for the key, including inactive ones.

GET/v1/webhooks/:idAPI key

One webhook. The signing secret is not recoverable here.

DELETE/v1/webhooks/:idAPI key

Soft-deletes the endpoint (active = 0).

GET/v1/webhooks/:id/deliveriesAPI key

Delivery 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.

POST/v1/admin/formations/:id/filing/startAdmin token

filing_ready → filing_in_progress. 422 filing_not_ready if it cannot transition.

POST/v1/admin/formations/:id/filing/completeAdmin token

Walks filing_in_progress → registration_complete → … → complete (filing simulated). Sets completed_at; returns filing_reference.

POST/v1/beta/waitlistPublic

Public. Join the beta waitlist (idempotent on email). Body: email (required), name?, company?, use_case?, platform?.

GET/v1/beta/waitlistAdmin token

Admin. 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.

Happy path (Wyoming, with EIN)
draftpending_owner_confirmationkyc_pendingkyc_approvedpayment_pendingpayment_authorizedsignature_pendingfiling_readyfiling_in_progressregistration_completeein_pendingdocuments_readycomplete

All 19 states

draft
Formation created (intake received), awaiting owner confirmation.
initial
pending_owner_confirmation
Awaiting the human / UBO to confirm cost and process.
human gate
portal_synced
Project mirrored into the operations portal (live mode).
integration
kyc_pending
KYC started — owner completes verification (Stripe Identity).
KYC
kyc_review
Verification submitted, under manual / provider review.
KYC
kyc_approved
KYC approved — gate to payment.
KYC· beta
kyc_failed
KYC rejected or expired.
KYC
payment_pending
Awaiting the owner to authorize and pay (Stripe).
payment
payment_authorized
Payment authorized / captured.
payment
signature_pending
Awaiting the owner's signature (DocSeal).
signature
filing_ready
Everything ready; queued for team-assisted filing.
filing
filing_in_progress
Team executing the filing with the registered agent / state.
filing
registration_complete
Entity registered in the jurisdiction.
filing
ein_pending
Awaiting federal EIN (Wyoming only, when obtain_ein=true).
post-registration
documents_ready
Final documents generated and available.
post-registration
complete
Formation completed successfully.
terminal· terminal
action_required
Something needs human action; retry hub.
recovery
failed
Unrecoverable system error.
terminal· terminal
cancelled
Cancelled by the owner or by an unmet gate.
terminal· terminal

Actors

Transitions record an actor: api_key (agent), owner (human), webhook (provider), admin (ops), or system.

kyc_approved — beta note

The provider-driven steps (KYC, payment, signature) are simulated in sandbox today; the real Stripe Identity / Stripe / DocSeal providers and the inbound webhooks that drive them are coming with live mode.

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/mcp

Discovery hint

GET https://api.offshoreproz.com/mcp

The 6 tools

01
offshoreproz_list_jurisdictionspublic

Lists jurisdictions with pricing and ETA.

When: Call this first — discover what's available (WY and MI appear by default).

02
offshoreproz_get_jurisdiction_requirementspublic

Returns the required fields for a jurisdiction.

When: Before collecting intake — to build the form the human will confirm.

03
offshoreproz_estimate_costAPI key

Estimates 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.

04
offshoreproz_create_formationAPI key

Starts 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.

05
offshoreproz_get_formation_statusAPI key

Current status plus the next action.

When: To poll progress; dispatches to GET /v1/formations/{formation_id}.

06
offshoreproz_list_documentsAPI key

Lists 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)

POST /mcp
{
  "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

EventFires when
formation.createdFormation created via POST /v1/formations.
formation.status_changedStatus transition without a more specific event; retry; lifecycle step confirmation.
formation.payment_receivedStripe payment confirmed (fired by inbound /webhooks/stripe — beta).
formation.signedDocument signed in DocSeal (fired by inbound /webhooks/signature — beta).
formation.filedFiling submitted to the state (WY) / registered agent (MI).
formation.portal_syncedProject mirrored into PORTAL_DB successfully (live mode).
formation.portal_sync_failedPortal sync failed.
formation.cancelledFormation cancelled via DELETE /v1/formations/:id.
formation.completeFormation completed — documents ready.
formation.errorUnrecoverable 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.

verify.js
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

The intended contract is 3 attempts at 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

Launch
~$499all-in · $185/yr maintenance
  • $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

Target
~$9,500all-in · $2,000/yr maintenance
  • 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)
CodeEntityStatusAll-in
WYWyoming LLCavailable~$499
MIMarshall Islands DAO LLCpilot~$9,500
NVNevis LLCcoming_soon~$650
BVIBVI Business Companycoming_soon~$1,400
PAPanama SA / SRLcoming_soon~$900
UAEUAE 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.

request header
Idempotency-Key: my-unique-key-001

Rate 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 · tierLimit (req/min)Notes
test · free / pro / enterprise200Sandbox override (any tier)
live · free50Live mode (beta)
live · pro1000Live mode (beta)
live · enterprise999999Effectively unlimited, still tracked
unauthenticated50Edge-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 envelope
{
  "error": "Human-readable message.",
  "code": "estimate_token_invalid",
  "request_id": "<trace>",
  "docs": "https://docs.offshoreproz.com/api/errors/estimate-token-invalid"
}
HTTPcodeWhen
400validation_errorZod / body validation failed, body over 256 KB, or invalid Idempotency-Key.
401invalid_api_keyAuth missing / malformed / bad prefix / mode mismatch.
401api_key_revokedKey has been revoked (revoked_at set).
402payment_failedReserved for real payment providers.
403live_mode_not_availableAn op_live_ key was presented while the live gate is closed (beta).
403forbiddenOperation not permitted.
404not_foundResource missing, or not owned by your key (ownership is never leaked as 403).
405method_not_allowedMethod not supported on the route.
409idempotency_key_conflictDefined for same-key / different-body conflicts on create.
422jurisdiction_not_availableJurisdiction is coming_soon or does not exist.
422estimate_token_invalidestimate_token expired or absent from KV (30-min TTL).
422estimate_token_jurisdiction_mismatchestimate_token was issued for a different jurisdiction.
422formation_not_retryableretry called on a formation that is not failed / action_required.
422formation_not_cancellabledelete called on a formation past draft / pending_owner_confirmation.
422no_pending_actionreissue called with no pending owner step.
422action_token_expiredAction token past its ~14-day TTL.
422action_token_consumedAction token already used (single-use).
422step_out_of_orderOwner step attempted out of sequence.
422illegal_transitionState-machine rejected the transition.
422document_too_largeUpload exceeds 10 MB (also invalid_base64 / empty_document).
422webhook_limit_reachedMore than 10 active webhooks for the key.
429rate_limit_exceededRate limit hit; includes Retry-After.
500internal_errorUnhandled exception.
503service_unavailableService 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.