Corporate Management
Overview
Corporate management lets businesses onboard as entities on the Bakkt platform. This guide covers creating a corporate, linking users to it with roles, updating corporate details, and unlinking users.
KYB (Know Your Business) verification — which the corporate must clear before it can transact — is covered in KYC & KYB Verification.
Before you begin
- Sandbox base URL:
https://sandbox.api.bakkt.com- Production base URL:
https://api.bakkt.com- Headers on every call:
Authorization: <YOUR_API_KEY>. Some endpoints additionally requirebakkt-session-id(a user session) — see each endpoint below. See Authentication for how to obtain a session.
Corporate status lifecycle
A corporate progresses through KYB verification before it can transact. The happy path:
CREATED → KYB_NEEDED → PENDING_KYB_DATA → KYB_PENDING → ACTIVEKYB_PENDING can also resolve to a failure state:
SOFT_KYB_FAILED— recoverable; the admin can resubmit, looping back toKYB_PENDING.HARD_KYB_FAILED— terminal; contact support.
A corporate can also be moved to SUSPENDED from any state by a manual compliance action.
| Status | Meaning | Can transact? | Next action |
|---|---|---|---|
CREATED | Corporate entity has just been created. | No | Generate the KYB form URL. |
KYB_NEEDED | Corporate exists but no KYB form has been generated yet. | No | GET /corporate/{corporate_uuid}/kyb/applicant/url |
PENDING_KYB_DATA | KYB form generated; corporate hasn't submitted yet. | No | Send the form URL to the corporate admin. |
KYB_PENDING | KYB submitted; under compliance review. | No | Wait for webhook or poll GET /corporate/{corporate_uuid}. |
SOFT_KYB_FAILED | Recoverable failure (e.g. missing document). | No | Re-issue the form URL and have the admin resubmit. |
HARD_KYB_FAILED | Terminal failure. | No | Contact support. |
ACTIVE | KYB approved. | Yes | — |
SUSPENDED | Suspended manually by Bakkt compliance. | No | Contact support. |
Note on sandbox: the sandbox
PATCH /corporate/{corporate_uuid}/kyb/verificationendpoint also accepts a legacyREJECTEDvalue. Treat it as a sandbox shortcut forHARD_KYB_FAILED— the production GET response uses the soft/hard failure statuses above.
Create a corporate
Create a new corporate entity. This call does not require a session — it's typically made server-side using your API key.
Endpoint: POST /corporate — returns 201 Created with { corporate_uuid, status }.
Required fields
legal_name— official registered business nametype— business structure (see business types below)contact_details— primary contact (name,email,phone)registered_address— legal registered addresstarget_address— Ethereum address used for digital assets operations (must be a valid 40-hex-character0xaddress)
Request example
const res = await fetch('https://sandbox.api.bakkt.com/corporate', {
method: 'POST',
headers: {
'Authorization': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
legal_name: 'Acme Corporation Ltd',
type: 'LIMITED_LIABILITY',
registration_number: '12345678',
contact_details: {
name: 'John Doe',
email: '[email protected]',
phone: '+447871556837'
},
registered_address: {
address_line_1: '123 Business Street',
post_code: 'SW1A 1AA',
city: 'London',
country: 'GB'
},
target_address: '0xFaFe15f71861609464a4ACada29a92c5bC01637a',
// The three accepted_* fields below are only required if hash validation is
// enabled for your merchant. Omit them otherwise. See /docs/terms-and-conditions-hashes.
accepted_terms_and_condition_hash: '<base64-hash>',
accepted_privacy_policy_hash: '<base64-hash>',
accepted_terms_of_service_hash: '<base64-hash>'
})
});
const { corporate_uuid, status } = await res.json();
console.log('Corporate UUID:', corporate_uuid);
console.log('Status:', status); // 'CREATED'Three legal-acceptance hashes are required. Each hash is the base64 of the document version the user agreed to. The current canonical URLs for the documents are listed in the API reference.
Business types
| Value | Description |
|---|---|
LIMITED_LIABILITY | Limited Liability Company |
SOLE_TRADER | Sole Proprietorship |
PARTNERSHIP | Partnership |
PUBLIC_LIMITED_COMPANY | Public Limited Company |
JOINT_STOCK_COMPANY | Joint Stock Company |
CHARITY | Charitable Organisation |
Country-specific fields
Some jurisdictions require additional fields:
| Country | Field | Notes |
|---|---|---|
Nigeria (NG) | bvn | Bank Verification Number — required for corporates registered in Nigeria. |
Optional fields
registration_number— official corporate registration number.trading_address— day-to-day operations address, if different fromregistered_address.target_solana_address— Solana account address (must be active and rent-exempt). See Digital assets addresses below.
Digital assets addresses
Both the Ethereum (target_address) and Solana (target_solana_address) target addresses can be classified for compliance:
target_address_type/target_solana_address_type: eitherHOSTED(custodial — held by a VASP on the user's behalf) orSELF_HOSTED(the corporate controls the keys).target_address_vasp/target_solana_address_vasp: when the type isHOSTED, optionally identify the VASP (e.g.'Coinbase').
Setting these helps Bakkt apply the right Travel Rule treatment. See the API reference for the full schema.
Update a corporate
Update corporate details after creation. Requires a session (bakkt-session-id) for an admin user linked to the corporate.
Endpoint: PATCH /corporate/{corporate_uuid} — returns 204 No Content.
await fetch(`https://sandbox.api.bakkt.com/corporate/${corporateUuid}`, {
method: 'PATCH',
headers: {
'Authorization': 'YOUR_API_KEY',
'bakkt-session-id': sessionId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
trading_address: {
address_line_1: '456 Trading Street',
post_code: 'SW1A 2BB',
city: 'London',
country: 'GB'
},
target_solana_address: '3KWxkxHLnxTqg1PMXUljpeFffzYxS7dziqWvJgQNg8dq'
})
});You can patch any of the fields accepted by POST /corporate (legal name, type, contact details, addresses, target addresses, country-specific fields, hashes). Omit fields you don't want to change.
Get corporate details
Retrieve the corporate profile and current KYB status.
Endpoint: GET /corporate/{corporate_uuid} — returns the full corporate object.
This endpoint accepts either bakkt-session-id (admin user's session) or user-uuid (server-to-server, where the user is linked to the corporate) as the auth header.
const res = await fetch(
`https://sandbox.api.bakkt.com/corporate/${corporateUuid}`,
{
headers: {
'Authorization': 'YOUR_API_KEY',
'bakkt-session-id': sessionId
// or: 'user-uuid': userUuid
}
}
);
const corporate = await res.json();Example response:
{
"legal_name": "Acme Corporation Ltd",
"type": "LIMITED_LIABILITY",
"registration_number": "12345678",
"contact_details": {
"name": "John Doe",
"email": "[email protected]",
"phone": "+447871556837"
},
"registered_address": { "...": "..." },
"trading_address": { "...": "..." },
"target_address": "0xFaFe15f71861609464a4ACada29a92c5bC01637a",
"target_solana_address": "3KWxkxHLnxTqg1PMXUljpeFffzYxS7dziqWvJgQNg8dq",
"status": "ACTIVE"
}The response may also include a
customFeeConfigobject if Bakkt has configured custom fees for your corporate. This is set up out-of-band by Bakkt support.
Add a user to a corporate
Link an individual user to a corporate entity with a specific role. This call does not require a session — it's typically made server-side once you've created both the user and the corporate.
Endpoint: POST /corporate/{corporate_uuid}/user — returns 204 No Content.
await fetch(
`https://sandbox.api.bakkt.com/corporate/${corporateUuid}/user`,
{
method: 'POST',
headers: {
'Authorization': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_uuid: '5594401c-0072-4df2-be9c-d491c0754c21',
role: 'ADMINISTRATOR'
})
}
);Roles
| Role | Description |
|---|---|
ADMINISTRATOR | Full access to corporate operations. |
ADMINISTRATOR is currently the only supported role. Check the API reference for the authoritative list.
Unlink a user from a corporate
Remove a user's link to a corporate. This requires a session for a user with the ADMINISTRATOR role on that corporate.
Endpoint: DELETE /corporate/{corporate_uuid}/user/{user_uuid} — returns 204 No Content.
await fetch(
`https://sandbox.api.bakkt.com/corporate/${corporateUuid}/user/${userUuid}`,
{
method: 'DELETE',
headers: {
'Authorization': 'YOUR_API_KEY',
'bakkt-session-id': adminSessionId
}
}
);Admin only. Calls from a non-admin session for the corporate are rejected with
401 Unauthorized.
Complete corporate onboarding workflow
const BASE_URL = 'https://sandbox.api.bakkt.com';
const headers = {
'Authorization': API_KEY,
'Content-Type': 'application/json'
};
const corpRes = await fetch(`${BASE_URL}/corporate`, {
method: 'POST',
headers,
body: JSON.stringify({
legal_name: 'Acme Corporation Ltd',
type: 'LIMITED_LIABILITY',
contact_details: {
name: 'John Doe',
email: '[email protected]',
phone: '+447871556837'
},
registered_address: {
address_line_1: '123 Business Street',
post_code: 'SW1A 1AA',
city: 'London',
country: 'GB'
},
target_address: '0xFaFe15f71861609464a4ACada29a92c5bC01637a',
// The three accepted_* fields below are only required if hash validation is
// enabled for your merchant. Omit them otherwise. See /docs/terms-and-conditions-hashes.
accepted_terms_and_condition_hash: '<base64-hash>',
accepted_privacy_policy_hash: '<base64-hash>',
accepted_terms_of_service_hash: '<base64-hash>'
})
});
const { corporate_uuid } = await corpRes.json();
const adminRes = await fetch(`${BASE_URL}/user`, {
method: 'POST',
headers,
body: JSON.stringify({
first_name: 'John',
last_name: 'Doe',
email: '[email protected]',
country: 'GB',
target_address: '0xFaFe15f71861609464a4ACada29a92c5bC01637a',
// The three accepted_* fields below are only required if hash validation is
// enabled for your merchant. Omit them otherwise. See /docs/terms-and-conditions-hashes.
accepted_terms_and_conditions_hash: '<base64-hash>',
accepted_privacy_policy_hash: '<base64-hash>',
accepted_terms_of_service_hash: '<base64-hash>'
})
});
const { user_uuid } = await adminRes.json();
await fetch(`${BASE_URL}/corporate/${corporate_uuid}/user`, {
method: 'POST',
headers,
body: JSON.stringify({ user_uuid, role: 'ADMINISTRATOR' })
});
const urlRes = await fetch(
`${BASE_URL}/corporate/${corporate_uuid}/kyb/applicant/url`,
{
headers: {
'Authorization': API_KEY,
'user-uuid': user_uuid
}
}
);
const { url: kybUrl } = await urlRes.json();
console.log('Send this URL to the admin:', kybUrl);
// The admin completes the Sumsub-hosted form externally.
// Bakkt fires a webhook when the corporate transitions to ACTIVE
// (or to SOFT_KYB_FAILED / HARD_KYB_FAILED). See /docs/webhooks.In production, prefer Webhooks over polling — they fire as soon as the corporate status changes.
Multi-corporate users
A single user can be linked to multiple corporates. The user object exposes linked_corporates_uuid:
const res = await fetch('https://sandbox.api.bakkt.com/user', {
headers: {
'Authorization': 'YOUR_API_KEY',
'bakkt-session-id': sessionId
}
});
const user = await res.json();
console.log('Linked corporates:', user.linked_corporates_uuid);
// [ "corp-1-uuid", "corp-2-uuid", "corp-3-uuid" ]To act on a specific corporate, pass its corporate_uuid in the URL of the relevant endpoint — there's no "active corporate" concept on the user session itself.
Best practices
Setup
- Create the admin user before creating the corporate so you can link them immediately. The corporate cannot start KYB without at least one admin.
- Communicate KYB requirements to the admin upfront — the Sumsub form will ask for company documents, beneficial-ownership info, and business activity details.
- Set both
registered_addressandtrading_addressif they differ. KYB verification uses both. - If hash validation is enabled for your merchant (it's opt-in), submit the three legal-acceptance hashes (
accepted_terms_and_condition_hash,accepted_privacy_policy_hash,accepted_terms_of_service_hash) on creation. The corporate flow hashes the same three documents as the user flow — only the field name for the T&C hash differs (singular..._condition_hashfor corporates, plural..._conditions_hashfor users). See Terms and Conditions Hashes for the source documents, the procedure, and how to confirm whether the feature is enabled for your merchant. If hash validation isn't enabled, omit these fields.
User and role management
- Today only
ADMINISTRATORexists, so be deliberate about who you link — every linked user has full access. - Audit
linked_corporates_uuidon user objects periodically to catch stale links. - Use
DELETE /corporate/{corporate_uuid}/user/{user_uuid}from an admin session to revoke access.
Address management
- Registered address: legal address on the company's incorporation documents.
- Trading address: day-to-day operations address (optional; can be the same as registered).
- Keep both addresses in sync with what the corporate submits in the KYB form to avoid
WRONG_ADDRESSrejections — see KYC & KYB → Rejection reasons.
Digital assets addresses
- Use
target_address_type/target_solana_address_type(HOSTEDorSELF_HOSTED) andtarget_address_vasp/target_solana_address_vaspto classify the destination correctly. This is required for Travel Rule compliance on outbound transfers.
Next steps
- KYC & KYB Verification — full KYB flow and status semantics.
- User Management — create and manage the individual users you'll link to corporates.
- Authentication — how to obtain
bakkt-session-idfor admin actions. - Webhooks — receive corporate status updates in real time.
- API Reference — Corporate Management
Updated 1 day ago
