Building on the API

A canonical end-to-end incorporation flow against the v1 API, walked through for Cayman Foundations, BVI Limited Companies, and Delaware LLCs

This cookbook walks through the full lifecycle of incorporating a real entity over the v1 API: from product discovery through to handing the customer back a paid, submitted-for-review company they own in their own EntityEngine account.

Three recipes are shown below — Cayman Foundation, BVI Limited Company, and Delaware LLC. The 12-step lifecycle is the same for all three. What differs is the CSP form schema returned by /catalog — which roles you must register, which CSP fields you must collect, which documents per role, and which conditional sections apply. The schema is the contract, not this cookbook. Always read it for the product you're building against.

Prerequisites

  • An EntityEngine API key with scopes entities:read, entities:write, parties:write, documents:write, invoices:write, collaborators:write.
  • The product opted into API availability by your EntityEngine admin.
  • An https URL you can use as success_url / cancel_url for hosted Stripe / Radom checkout.
  • A webhook endpoint subscribed to payment.received, entity.status_changed, entity.feedback_created, invitation.accepted.

All requests below assume base URL https://app.entityengine.io/api/v1 and an Authorization: Bearer sk_live_... header.

The API flow

All API key integrations follow the same sequence. The entity stays in draft throughout — fully editable — until you explicitly submit it. Billing and submission are separate steps you control.

Full API flow — Cayman Foundation example
# 1. Create the entity (no invoice yet, fully editable).
curl -X POST https://app.entityengine.io/api/v1/entities \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jurisdiction_product_id": "aecf91da-50bf-4d7e-9de8-76b28b0a0ccc",
    "name": "Acme Foundation",
    "client_reference": "bank-customer-9001"
  }'

# data.id     → ent_...
# data.status → "draft"

# 2. Configure across as many calls as you need.
#    POST /entities/<ent_id>/parties
#    POST /entities/<ent_id>/csp-data
#    POST /entities/<ent_id>/documents
#    POST /entities/<ent_id>/addons

# 3. (Optional) confirm all requirements are met.
curl https://app.entityengine.io/api/v1/entities/<ent_id>/requirements \
  -H "Authorization: Bearer sk_live_..."

# 4. Issue the invoice + create a hosted Stripe checkout session in one call.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/invoice \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method": "card",
    "success_url": "https://bank.example.com/checkout/done",
    "cancel_url":  "https://bank.example.com/checkout/cancelled",
    "customer_email": "founder@example.com"
  }'

# data.invoice.id                   → inv_...
# data.checkout_session.checkout_url → forward to customer
# entity status stays "draft"

# 5. Wait for payment.received webhook, then submit.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/submit \
  -H "Authorization: Bearer sk_live_..."

# data.status → "submitted_for_review"
# Listen for entity.status_changed to track progress.

Schema-driven flow — read this first

The /catalog response carries a schema object per product. It is the single source of truth for what the entity needs. Treat it as data your client interprets at runtime — do not hardcode field names or role lists.

Sections and scopes

scopeWhat it meansWhere to send the answers
entityEntity-level fields (name, business activities, source of funds, regulatory questions).POST /entities/:id/csp-data
share_structureShare class definition (BVI Limited only — DE LLC and Cayman Foundation do not have this).POST /entities/:id/share-classes
role:director, role:shareholder, role:member, role:supervisor, role:manager, role:uboA natural-person OR corporate-body OR foundation in that role. Section declares minimum, allows_corporate, allows_foundation, plus per-type field lists.POST /entities/:id/parties with party_type: "person" | "corporate" | "foundation"

Field metadata you must respect

  • canonical_field (e.g. entity.name, person.first_name) — cross-product field that maps to a typed column on EE's side. Pass it as the typed field on the relevant POST.
  • csp_field_key (e.g. cayman.gnb_category, bvi.records_custodian_email) — jurisdiction-prefixed CSP-specific field. Send via the CSP data endpoint with the schema id.
  • depends_on: { field, value } — show / require this field (or whole section) only when a condition on another field is met. Supports simple equality or operator: "includes_any" for multi-value matching. Cascades: e.g. Cayman foundation.governance_structure hides the Members section when set to supervisor_only AND requires ≥1 supervisor when set to supervisor_only or members_and_supervisor.
  • provided_by_agent: true — EE auto-populates this field from the CSP's registered office. Show the value from schema.agent_address as confirmation; do not ask the user for it.
  • conditional_min: { field, value | value_in, minimum } — overrides the section's base minimum when the named field equals the given value (or any of the values in value_in).
  • field_type — text, textarea, select, boolean, date, number, currency_amount, address, phone, nationality (array of ISO codes), share_class_ref. Render UI accordingly.
  • documents — per role section, lists the document slots required. Each entry has a name (the slot label) and required bool. Upload using the slot name as document_key.

The right pattern: render the schema dynamically, validate against required + depends_on + conditional_min, then POST. The recipes below show the canonical sequence per product.

The 12 canonical steps

  1. Discover available products via the catalog and read the schema.
  2. Create the entity (with any initial addons if known).
  3. Issue the invoice via POST /entities/:id/invoice and forward the hosted payment URL to the customer.
  4. Invite the customer as a collaborator on their new entity.
  5. Define share classes (BVI only) — skip for Cayman Foundation and Delaware LLC.
  6. Register every role declared in the schema (directors, members, shareholders, supervisors, UBOs, etc).
  7. Submit the entity-scope CSP fields (and any role-scope CSP fields per party).
  8. Upload documents for every required slot in schema.documents and per role section.
  9. Poll GET /entities/:id/requirements until it returns complete: true.
  10. Wait for the payment.received webhook.
  11. Submit the entity for review with POST /entities/:id/submit.
  12. Listen for entity.status_changed with new_status: "incorporated" and surface registry documents to the customer.

Recipe 1 — Cayman Foundation

Roles required by the schema

  • director — minimum 1.
  • member — minimum 0; section is hidden unless foundation.governance_structure is members_only or members_and_supervisor.
  • supervisor — minimum 0; required (≥1) when foundation.governance_structure is supervisor_only or members_and_supervisor.
  • ubo — minimum 0; populate per BO register obligations.

Most demos use supervisor_only, which removes the Members section entirely and requires a supervisor.

Step 1 — Discover the Cayman Foundation product
curl https://app.entityengine.io/api/v1/catalog \
  -H "Authorization: Bearer sk_live_..."

# Match jurisdiction.slug === "caymans" AND product.slug === "caymans-foundation-company".
# Capture:
#   item.jurisdiction_product_id   → for POST /entities
#   item.schema.id                 → for POST /csp-data
#   item.schema.sections           → drives the chat / form
#   item.schema.agent_address      → registered office (auto-populated)
Step 2 — Create the draft foundation
curl -X POST https://app.entityengine.io/api/v1/entities \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jurisdiction_product_id": "aecf91da-50bf-4d7e-9de8-76b28b0a0ccc",
    "name": "Acme Foundation",
    "addon_ids": [],
    "client_reference": "bank-customer-9001"
  }'

# data.id     → ent_...
# data.status → "draft"  (no invoice yet, fully editable)
Steps 3 & 4 — Issue invoice and invite customer
# Step 3 — Issue the incorporation invoice and open a hosted Stripe checkout session.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/invoice \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method": "card",
    "success_url": "https://chatbot.example.com/thanks?session={CHECKOUT_SESSION_ID}",
    "cancel_url":  "https://chatbot.example.com/cancelled",
    "customer_email": "founder@example.com"
  }'

# data.invoice.id                    → inv_...
# data.checkout_session.checkout_url → forward to customer

# Step 4 — Invite the customer so they can claim the entity in their own dashboard.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/invitations \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "founder@example.com", "role": "collaborator" }'
Step 6 — Register at least one director (and a supervisor if memberless)
# Director — natural person.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/parties \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "party_type": "person",
    "first_name": "Alice",
    "last_name": "Director",
    "email": "alice@example.com",
    "phone": "+447700900123",
    "date_of_birth": "1985-04-12",
    "place_of_birth": "London, UK",
    "nationalities": ["GB"],
    "address": {
      "street1": "10 Downing Street",
      "city": "London",
      "country": "GB",
      "postcode": "SW1A 2AA"
    },
    "roles": ["director"]
  }'

# If foundation.governance_structure is "supervisor_only" or "members_and_supervisor",
# register at least one supervisor (same payload, roles: ["supervisor"]).

# A director or supervisor can be a corporate body or foundation instead of a
# person — use POST /entities/<ent_id>/parties with party_type: "corporate" or "foundation".
Step 7 — Submit entity-scope CSP fields
# Use the schema.id from the catalog and post the answers as field_key/value pairs.
# Field keys are the csp_field_key (or canonical_field) values from schema.sections[].fields[].
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/csp-data \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "csp_form_schema_id": "e92ace31-51a6-4587-bf65-49d4ec5a7362",
    "fields": [
      { "field_key": "cayman.alternate_name",          "value": "Acme Stewardship Foundation" },
      { "field_key": "foundation.purpose",             "value": "Stewardship of an open-source treasury." },
      { "field_key": "foundation.governance_structure", "value": "supervisor_only" },
      { "field_key": "cayman.tax_exemption_certificate", "value": false },
      { "field_key": "cayman.corporate_seal",          "value": false },
      { "field_key": "cayman.share_certificates",      "value": false },
      { "field_key": "cayman.cima_licensing",          "value": false },
      { "field_key": "cayman.gnb_category",            "value": "Holding Company (securities)" },
      { "field_key": "cayman.business_activities",     "value": "Holds treasury assets for the Foo DAO." },
      { "field_key": "cayman.business_locations",      "value": "Cayman Islands, United Kingdom" },
      { "field_key": "cayman.multiple_structure_layers", "value": false },
      { "field_key": "cayman.source_of_funds",         "value": "Initial protocol grant." },
      { "field_key": "cayman.source_of_wealth",        "value": "Donations/Inheritance" },
      { "field_key": "cayman.is_domestic_company",     "value": false },
      { "field_key": "cayman.is_investment_fund",      "value": false },
      { "field_key": "cayman.is_tax_resident_outside", "value": false },
      { "field_key": "cayman.bo_out_of_scope",         "value": false },
      { "field_key": "cayman.carries_relevant_activities", "value": false }
    ]
  }'
Step 8 — Upload documents per the schema's documents block
# Each role section in the schema has a documents.{person|entity|foundation} list.
# For a director who is a person, the schema requires:
#   - "Certified passport copy"
#   - "Proof of address"
#
# Upload each one against that party using the slot name as document_key.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/documents \
  -H "Authorization: Bearer sk_live_..." \
  -F "scope=party" \
  -F "party_id=<alice_party_id>" \
  -F "document_key=Certified passport copy" \
  -F "file=@/local/alice-passport.pdf"

# Entity-scope documents (e.g. addon-related) use scope=entity and no party_id.
Steps 9–12 — Verify, wait, submit, monitor
# Step 9 — Poll until requirements are clear.
curl https://app.entityengine.io/api/v1/entities/<ent_id>/requirements \
  -H "Authorization: Bearer sk_live_..."

# Step 10 — Wait for the payment.received webhook for the entity's invoice.

# Step 11 — Submit for CSP review. Returns 4xx with structured reasons if anything is off.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/submit \
  -H "Authorization: Bearer sk_live_..."

# Step 12 — Listen for entity.status_changed with new_status: "incorporated";
#            then GET /entities/<ent_id>/documents to surface the registry pack.

Recipe 2 — BVI Limited Company

Roles required by the schema

  • director — minimum 1. May be a person or a corporate body.
  • shareholder — minimum 1. Carries relationship fields (share_class + shares_held).
  • ubo — minimum 1.

Plus a share_structure section — you must create at least one share class before issuing shareholdings.

Step 2 (delta) — Create the BVI company
curl -X POST https://app.entityengine.io/api/v1/entities \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jurisdiction_product_id": "be9a0966-794a-410f-95c4-a244d298b221",
    "name": "Acme Holdings",
    "client_reference": "bank-customer-9001"
  }'

# Then at step 3, issue the invoice with Radom (crypto) checkout:
# POST /entities/<ent_id>/invoice with payment_method: "crypto"
Step 5 — Define a share class (BVI requires this before any shareholding)
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/share-classes \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ordinary",
    "currency": "USD",
    "par_value": 1,
    "voting_rights": true,
    "dividend_rights": true,
    "authorized_shares": 50000
  }'

# Capture share_class_id for the shareholding step below.
Step 6 — Register a director and a shareholder, then issue shares
# Director.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/parties \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "party_type": "person",
    "first_name": "Alice",
    "last_name": "Director",
    "email": "alice@example.com",
    "phone": "+447700900123",
    "date_of_birth": "1985-04-12",
    "place_of_birth": "London, UK",
    "nationalities": ["GB"],
    "address": {
      "street1": "10 Downing Street",
      "city": "London",
      "country": "GB",
      "postcode": "SW1A 2AA"
    },
    "roles": ["director","shareholder","ubo"]
  }'

# Issue 100 shares to Alice (use the share_class_id captured above).
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/shareholdings \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "party_id": "<alice_party_id>",
    "share_class_id": "<share_class_id>",
    "shares_held": 100
  }'
Step 7 (delta) — Submit BVI CSP fields
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/csp-data \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "csp_form_schema_id": "71624bc1-7e42-4c85-907b-96cecb315cc0",
    "fields": [
      { "field_key": "bvi.purpose_category",            "value": "Holding of Assets" },
      { "field_key": "bvi.purpose_details",             "value": "Holds intellectual property used by Acme group entities." },
      { "field_key": "bvi.source_of_funds",             "value": "Founder capital." },
      { "field_key": "bvi.estimated_annual_turnover",   "value": 250000 },
      { "field_key": "bvi.company_maintain_bank_accounts", "value": true },
      { "field_key": "bvi.bank_account_countries",      "value": ["GB","SG"] },
      { "field_key": "bvi.records_custodian_boolean",   "value": true }
    ]
  }'

# UBO-scope CSP fields (occupation, source_of_funds, etc) are submitted per
# party with POST /entities/<ent_id>/party-csp-data.

Recipe 3 — Delaware LLC

Roles required by the schema

  • member — minimum 1.
  • manager — minimum 1, but only when delaware.management_structure = "Manager-Managed". Skip entirely for member-managed LLCs.

No share classes, no shareholders, no UBO section. Member onboarding is intentionally minimal — Delaware LLCs only require first name + last name per member at formation.

Step 2 (delta) — Create the Delaware LLC
curl -X POST https://app.entityengine.io/api/v1/entities \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jurisdiction_product_id": "e4e98e70-3ab4-4aaf-af48-ae6138d8c8b8",
    "name": "Acme Labs",
    "client_reference": "bank-customer-9001"
  }'

# Then at step 3, issue the invoice with Stripe checkout:
# POST /entities/<ent_id>/invoice with payment_method: "card"
Step 6 — Register at least one member (and a manager if manager-managed)
# Member — only first_name + last_name are mandatory per the schema.
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/parties \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "party_type": "person",
    "first_name": "Alice",
    "last_name":  "Member",
    "roles":      ["member"]
  }'

# Skip the next call if delaware.management_structure === "Member-Managed".
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/parties \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "party_type": "person",
    "first_name": "Bob",
    "last_name":  "Manager",
    "roles":      ["manager"]
  }'
Step 7 (delta) — Submit Delaware CSP fields
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/csp-data \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "csp_form_schema_id": "24646997-2ecd-44a6-99ba-7ffd66acddc2",
    "fields": [
      { "field_key": "entity.business_activity",      "value": "Software product development." },
      { "field_key": "delaware.management_structure", "value": "Member-Managed" }
    ]
  }'

Delaware LLC requires no per-person KYC documents at formation. Steps 8–12 are the same as the Cayman recipe — poll requirements, wait for payment.received, submit, listen for entity.status_changed with new_status: "incorporated".

Post-incorporation — selling more addons

Once entity.status_changed fires with new_status: "incorporated", additional addons follow a different billing path automatically. Same endpoint, the response tells you what happened.

Buy a nominee director addon after incorporation
curl -X POST https://app.entityengine.io/api/v1/entities/<ent_id>/addons \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "addon_slug": "nominee-director",
    "payment_provider": "stripe",
    "success_url": "https://chatbot.example.com/thanks",
    "cancel_url":  "https://chatbot.example.com/cancelled",
    "customer_email": "founder@example.com"
  }'

# Response carries invoice.action:
#   "appended" → bolted onto the (still unpaid) setup invoice; old session was regenerated.
#   "created"  → fresh api_topup invoice raised; new checkout session URL returned.
# If GET /entities/<ent_id>/addons shows requires_survey: true for that addon,
# call /addons/<slug>/requirements then /submit before the addon goes active.

Recommended webhook subscriptions

EventWhy subscribe
payment.session_createdRefresh the checkout link in your UI when it changes.
payment.receivedUnblock the “ready to submit” step in your UX.
payment.session_expiredHosted checkout session expired before payment — regenerate via POST /invoices/{id}/checkout-session and re-prompt.
invitation.acceptedShow the customer they now own the entity.
addon.invoicedTrack post-incorporation top-ups.
entity.status_changedFired on every status transition. Check new_status: submitted_for_review to mark as handed off, returned_for_changes to prompt for corrections, incorporated to surface registry docs.

Sandbox vs live

Sandbox API keys do not have a payment gateway attached. To test the lifecycle without real charges, omit payment_provider (the invoice will fall back to bank-transfer mode) and simulate payment with POST /sandbox/invoices/{invoiceId}/pay. Once you're confident, swap to a live key and add the payment_provider field to your entity-creation call.