Skip to main content
A global (user-level) API key can create a business entity and run its KYB onboarding without touching the dashboard: business details, the representatives (beneficial owners and officers), each representative’s identity verification, supporting documents, and the final submission. Every response includes a next_step object that tells you what’s outstanding, so you don’t infer it from status.
This is self-serve onboarding for entities your key owns. The key’s user is the creator and admin, not a representative of the entity. To onboard entities on behalf of your customers as a platform, use Partner Onboarding instead.

You’ll need

  • A global API key (user-scoped, not entity-scoped) with the entity:create scope for writes and accounts:read to read and list onboarding state. Onboarding routes reject entity-scoped keys with 403.
  • No x-entity-id header. The entity is addressed in the URL path.
Every onboarding read returns a next_step. For a US entity mid-onboarding it looks like this (address is a US-only requirement — see documents):
{
  "step": "manage_representatives",
  "phase": "business_info",
  "missing": ["Primary representative"],
  "required_proof_types": ["incorporation", "address"],
  "can_submit": false
}
FieldMeaning
stepThe step the application is on now.
phasebusiness_infoready_to_submitsubmitted.
missingThe items still required to clear the current step.
required_proof_typesKYB documents still owed, as proof_type values. Pass each one when requesting an upload URL.
can_submittrue once everything required has been collected.
The loop: call an endpoint, then GET /entities/{entity_id}, act on next_step.step, repeat until can_submit is true, then submit.
stepDo this
collect_business_details, collect_business_addressesCreate the entity, or update its details.
collect_additional_detailsUpdate business details with the account purpose and digital-currency answers.
manage_representativesAdd representatives, marking one is_primary.
complete_identity_verificationVerify each representative.
upload_documentsUpload the documents named in required_proof_types.
submit_applicationSubmit the application.
Some entities draw an extra due-diligence step (complete_due_diligence) or a follow-up information request after submission (resolve_info_requests). These are handled in the Meow dashboard; the API surfaces them in next_step so you know when to direct the business there.

1. Create the entity

POST /entities creates the entity and returns its public ID. The creating user is granted admin, so the same key can act on the entity immediately. Send an empty body to create a bare entity, or seed any KYB business details up front.
curl -X POST https://api.meow.com/v1/entities \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "business_name": "Catnip Coffee Co",
    "business_date_of_incorporation": "2021-03-01",
    "legal_structure": "ccorp",
    "incorporation_state": "DE",
    "business_address": {
      "address": "9 Whisker Way",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  }'
Response
{
  "id": "b1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "business_name": "Catnip Coffee Co",
  "created_at": "2026-06-26T18:04:11.512Z"
}
Save id. It’s the {entity_id} for every call below, and the x-entity-id header once the entity is onboarded and you call entity-scoped APIs. legal_structure is one of: ccorp, corp, scorp, llc, llp, lp, partnership, soleprop, nonprofit, trust, estate, foreign_entity, scheme.
Pass your own reference_id to make creation idempotent. Retrying a create with a reference_id you’ve already used returns the original entity instead of creating a duplicate, so a network timeout never leaves you with two entities.

2. Update business details

PATCH /entities/{entity_id}/business-details fills in or corrects KYB details before the application is submitted. Send only the fields you want to change; anything you omit keeps its current value. Use it for the business profile the create call didn’t include, and to answer the account-purpose and digital-currency questions (collect_additional_details).
curl -X PATCH https://api.meow.com/v1/entities/$ENTITY_ID/business-details \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "business_phone": "+14155550123",
    "business_taxid": "12-3456789",
    "business_website": "https://catnipcoffee.com",
    "business_description": "Specialty coffee roaster and cafe",
    "industry": "limited_service_restaurants",
    "account_purpose": "business_operations",
    "provides_blockchain_or_digital_currency_services": false,
    "transacts_in_digital_currency": false,
    "develops_governs_supports_blockchain_protocol": false
  }'
The response is the refreshed onboarding status, including the updated next_step. Send physical_address together with business_address; omitting it keeps any physical address already on file, or uses the legal address when none is set.

3. Add representatives

POST /entities/{entity_id}/representatives records a beneficial owner or officer and returns its representative_id. This step is create-only. Verify the representative separately (section 4). Mark exactly one representative as the signer with is_primary: true; setting it demotes any previous primary. A primary representative is required before the application can be submitted.
curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/representatives \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "felix@catnipcoffee.com",
    "first_name": "Felix",
    "last_name": "Whiskers",
    "title_id": "chief executive officer",
    "is_beneficial_owner": true,
    "percent_ownership": "50",
    "is_primary": true
  }'
Response
{
  "representative_id": "a7c3...",
  "email": "felix@catnipcoffee.com",
  "title_id": "chief executive officer",
  "is_beneficial_owner": true,
  "percent_ownership": "50",
  "is_primary": true
}
  • title_id is one of: chief executive officer, chief financial officer, chief operating officer, president, general partner, managing member, finance manager.
  • percent_ownership is required when is_beneficial_owner is true, between 25 and 100. Total beneficial ownership across representatives cannot exceed 100.
  • Each email is unique per entity. Re-posting the same address returns 400.

4. Verify each representative

KYC is per representative, with two ways to do it. Pick one per person.
Submit the representative’s identity data and Meow runs verification on it.
curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/representatives/$REP_ID/kyc \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Felix",
    "last_name": "Whiskers",
    "date_of_birth": "1990-01-01",
    "id_number": "123-45-6789",
    "id_type": "us_ssn",
    "phone_number": "+14155550123",
    "address": {
      "address": "1 Catnip Court",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  }'
Response
{ "representative_id": "a7c3...", "kyc_status": "pending" }
For id_type: us_ssn send nine digits with or without dashes (123-45-6789 or 123456789); for us_ssn_last_4 send the last four.
Verification completes asynchronously. Track it via the entity’s next_step (the complete_identity_verification step clears once a representative is verified) or the identity_verification.* webhooks. The entity’s own kyc_status reflects the primary user, not an added representative.

5. Upload supporting documents

next_step.required_proof_types lists exactly which documents the entity still owes. For a US entity that’s typically ["incorporation", "address"]. Each one uses a three-step flow: get a pre-signed URL, PUT the bytes, then confirm. Repeat the flow for each proof type — for example, the incorporation certificate first, then the proof of physical address:
1

Request an upload URL

curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/upload-url \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "proof_type": "incorporation", "file_name": "certificate.pdf", "content_type": "application/pdf" }'
Response
{
  "upload_url": "https://s3.amazonaws.com/...&X-Amz-Signature=...",
  "object_key": "kyb/b1b2.../certificate.pdf",
  "max_bytes": 10485760,
  "expires_in_seconds": 900
}
2

PUT the file to upload_url

curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @certificate.pdf
Use the same Content-Type you requested, keep the file under max_bytes, and upload before the URL expires.
3

Confirm the upload

curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/confirm \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "object_key": "kyb/b1b2.../certificate.pdf", "proof_type": "incorporation" }'
Response
{ "confirmed": true, "file_name": "certificate.pdf" }
4

Repeat for the proof of physical address

curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/upload-url \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "proof_type": "address", "file_name": "utility-bill.pdf", "content_type": "application/pdf" }'
curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @utility-bill.pdf
curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/confirm \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "object_key": "kyb/b1b2.../utility-bill.pdf", "proof_type": "address" }'
Pass each value from required_proof_types as the proof_type. For US entities, required_proof_types includes both incorporation and address as standard requirements. address is proof of physical address at the entity level — a utility bill, bank statement, or lease/rental agreement evidencing the entity’s relationship to its physical address. The address requirement applies to US entities only; foreign entities are not asked for it. Other common values include articles_of_organization, bylaws, operating_agreement, ein, bank_statement, and beneficial_owner_id.
Address documents uploaded for a representative do not satisfy the entity-level address requirement — the document must evidence the entity’s physical address. next_step.can_submit stays false until every proof type in required_proof_types has been uploaded.

6. Submit the application

Once next_step.can_submit is true, submit the application with POST /entities/{entity_id}/submit. Choose the checking account product to open, and attest that you are authorized to submit on the business’s behalf. If anything is still outstanding, this returns 400 with the remaining requirements; check next_step.can_submit first.
curl -X POST https://api.meow.com/v1/entities/$ENTITY_ID/submit \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "banking_account_product_type": "grasshopper", "attestation": true }'
Response
{
  "entity_id": "b1b2c3d4-...",
  "status": "under_review",
  "next_step": { "step": "monitor_review", "phase": "submitted", "missing": [], "required_proof_types": [], "can_submit": false }
}
  • banking_account_product_type is grasshopper or crb.
  • attestation must be true.
Once submitted, the application moves to review and can no longer be edited. Meow moves status to under_review, then approved or rejected. Watch for the outcome with webhooks (below) or by polling the status endpoint.

Sandbox: simulate application approval

In sandbox and development environments there is no human compliance review, so a submitted application would otherwise sit in under_review forever. Simulate the approval with POST /simulations/entities/{entity_id}/application_approval. It approves the application as if compliance review had passed and kicks off checking-account onboarding.
curl -X POST https://api.sandbox.meow.com/v1/simulations/entities/$ENTITY_ID/application_approval \
  -H "x-api-key: $MEOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "banking_account_product_type": "grasshopper" }'
  • The body is optional. banking_account_product_type defaults to grasshopper; crb is also supported.
  • Use a global (user) API key with the simulations:write scope; the key’s user must be an admin of the entity.
  • The application must already be submitted (section 6).
After calling it, the application’s status moves to submitted immediately and to approved once the checking account activates. Poll GET /entities/{entity_id} or subscribe to the application.* webhook events to observe the transition.
Simulation endpoints are only available in sandbox and development environments. In production they return 404 Not Found.

7. Track progress to submission

GET /entities/{entity_id} returns the lifecycle status, uploaded documents, and next_step.
curl https://api.meow.com/v1/entities/$ENTITY_ID -H "x-api-key: $MEOW_API_KEY"
Response
{
  "entity_id": "b1b2c3d4-...",
  "onboarding_type": "api",
  "status": "pending_user_action",
  "kyc_status": "pending",
  "business_name": "Catnip Coffee Co",
  "created_at": "2026-06-26T18:04:11.512Z",
  "updated_at": "2026-06-26T18:22:54.001Z",
  "documents": [
    { "proof_type": "incorporation", "file_name": "certificate.pdf" },
    { "proof_type": "address", "file_name": "utility-bill.pdf" }
  ],
  "next_step": { "step": "submit_application", "phase": "ready_to_submit", "missing": [], "required_proof_types": [], "can_submit": true }
}
statusMeaning
pending_document_uploadNo documents uploaded yet.
pending_user_actionStill collecting required data.
submittedSubmitted; queued for review.
under_reviewMeow is reviewing the application.
approvedKYB approved (and an account is open).
rejectedKYB rejected.

8. List your entities

GET /entities returns every entity your key’s user owns, each tagged with its onboarding_type, with opaque-offset pagination.
curl "https://api.meow.com/v1/entities?limit=20" -H "x-api-key: $MEOW_API_KEY"
Response
{
  "entities": [
    { "entity_id": "b1b2c3d4-...", "onboarding_type": "api", "status": "under_review", "business_name": "Catnip Coffee Co", "created_at": "...", "updated_at": "..." }
  ],
  "page": { "nextOffset": "20" }
}
When page is non-null, pass page.nextOffset as the offset query parameter for the next page. When page is null, you’ve reached the end.

Track updates with webhooks

Subscribe to identity_verification.* for KYC outcomes and application.* for KYB lifecycle changes, then update your records from the delivered payloads. See the webhooks guide for signing, retries, and out-of-order handling.

End to end

# 1. Create the entity with its business details.
ENTITY_ID=$(curl -s -X POST https://api.meow.com/v1/entities \
  -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
  -d '{"business_name":"Catnip Coffee Co","business_date_of_incorporation":"2021-03-01","legal_structure":"ccorp","incorporation_state":"DE","business_address":{"address":"9 Whisker Way","city":"San Francisco","state":"CA","zip":"94105","country":"US"}}' \
  | jq -r .id)

# 2. Fill in the remaining business profile.
curl -s -X PATCH https://api.meow.com/v1/entities/$ENTITY_ID/business-details \
  -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
  -d '{"business_phone":"+14155550123","business_taxid":"12-3456789","business_website":"https://catnipcoffee.com","business_description":"Specialty coffee roaster and cafe","industry":"limited_service_restaurants","account_purpose":"business_operations","provides_blockchain_or_digital_currency_services":false,"transacts_in_digital_currency":false,"develops_governs_supports_blockchain_protocol":false}'

# 3. Add the primary representative.
REP_ID=$(curl -s -X POST https://api.meow.com/v1/entities/$ENTITY_ID/representatives \
  -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
  -d '{"email":"felix@catnipcoffee.com","title_id":"chief executive officer","is_beneficial_owner":true,"percent_ownership":"50","is_primary":true}' \
  | jq -r .representative_id)

# 4. Verify them: submit their data, or mint a self-serve link instead.
curl -s -X POST https://api.meow.com/v1/entities/$ENTITY_ID/representatives/$REP_ID/kyc \
  -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
  -d '{"first_name":"Felix","last_name":"Whiskers","date_of_birth":"1990-01-01","id_number":"123456789","id_type":"us_ssn","address":{"address":"1 Catnip Court","city":"San Francisco","state":"CA","zip":"94105","country":"US"}}'

# 5. Upload each document in next_step.required_proof_types — for a US entity,
#    "incorporation" and "address" (proof of physical address).
for DOC in "incorporation certificate.pdf" "address utility-bill.pdf"; do
  set -- $DOC
  UPLOAD=$(curl -s -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/upload-url \
    -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
    -d "{\"proof_type\":\"$1\",\"file_name\":\"$2\",\"content_type\":\"application/pdf\"}")
  curl -s -X PUT "$(echo $UPLOAD | jq -r .upload_url)" \
    -H "Content-Type: application/pdf" --data-binary @$2
  curl -s -X POST https://api.meow.com/v1/entities/$ENTITY_ID/documents/confirm \
    -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
    -d "{\"object_key\":\"$(echo $UPLOAD | jq -r .object_key)\",\"proof_type\":\"$1\"}"
done

# 6. Poll until ready: KYC clears asynchronously and every required document
#    must be uploaded before can_submit turns true.
until [ "$(curl -s https://api.meow.com/v1/entities/$ENTITY_ID \
  -H "x-api-key: $MEOW_API_KEY" | jq -r .next_step.can_submit)" = "true" ]; do
  sleep 5
done

# 7. Submit for review.
curl -s -X POST https://api.meow.com/v1/entities/$ENTITY_ID/submit \
  -H "x-api-key: $MEOW_API_KEY" -H "Content-Type: application/json" \
  -d '{"banking_account_product_type":"grasshopper","attestation":true}' | jq .status

See also

Create an entity

Full request contract for POST /entities.

Update business details

Fill in or correct KYB details before submission.

Add a representative

Beneficial owners and officers, with the primary signer flag.

Submit the application

Choose the account product and attest, then send for review.