API User Onboarding Workflow
How to onboard an API user to Blockstream Enterprise Custody, capture the returned device credentials, and initialize the SDK.
Complete Setup and Installation before following this guide.
This guide uses:
@blockstream/cryptic@blockstream/cryptic/utils@blockstream/ecs-registryuuid
You also need:
- a user that can create API users
- access to the
/usersendpoint - an authenticated
Blockstreamsession that can create users - a
Broadcasterfor that session, or one of the lower-level request helpers from Making Requests
If your deployment requires mTLS for /request, follow the same onboarding flow here and switch the broadcaster transport to the Node-side mTLS pattern described in Making Requests.
What this flow does
API-user onboarding is the programmatic path for machine-to-machine integrations. In this flow you:
- generate the client's key material locally
- create the user on the server
- capture the returned
device_uuid,server_ecdsa_pubkey, andserver_rsa_pubkey - initialize
Blockstreamwith those values
For the core crypto model and installation details, use Setup and Installation. This page stays focused on the onboarding sequence itself.
Step 1: Generate client key pairs
Use the built-in helper unless you need a custom key-management flow:
import { generateUserKeyPairs } from '@blockstream/cryptic/utils'
const userKeys = await generateUserKeyPairs()Step 2: Create the API user
Create the user with the generated public keys. In most applications, this means using a Broadcaster built from the already-authenticated admin or service session that is provisioning the new API user:
import { v4 as uuidv4 } from 'uuid'
import { Broadcaster } from '@blockstream/cryptic/broadcaster'
const adminBroadcaster = new Broadcaster(ECS_BASE_URL, adminClient)
const createResponse = await adminBroadcaster.processBroadcast({
action: 'add',
resource: '/users',
details: {
uid: uuidv4(),
email: 'api_user@example.com',
first_name: 'API',
last_name: 'User',
ecdsa_pubkey: userKeys.ecdsa.publicKeyHex,
rsa_pubkey: userKeys.rsa.publicKeyPem,
type: 'api',
},
})On success, the server returns the values you need to initialize the SDK:
const { uid, device_uuid, server_ecdsa_pubkey, server_rsa_pubkey } = createResponse.detailsWhy these returned fields matter
device_uuidis the server-generated device identity for this API userserver_ecdsa_pubkeyis the public key used to verify server signaturesserver_rsa_pubkeyis the public key used to encrypt request material for the server
Persist device_uuid with the user's private keys. They are a matched credential set.
Step 3: Initialize the SDK
import { Buffer } from 'buffer'
import { Blockstream } from '@blockstream/cryptic'
import { Broadcaster } from '@blockstream/cryptic/broadcaster'
const userClient = new Blockstream(
userKeys.rsa.privateKeyPem,
Buffer.from(userKeys.ecdsa.privateKeyHex, 'hex'),
device_uuid,
server_rsa_pubkey,
Buffer.from(server_ecdsa_pubkey, 'hex'),
)
const userBroadcaster = new Broadcaster(ECS_BASE_URL, userClient)Step 4: Verify the user can make authenticated requests
const response = await userBroadcaster.processBroadcast({
action: 'get',
resource: `/users/${uid}`,
})If this succeeds, the onboarding flow is complete.
If your environment requires mTLS, keep this verification step the same at the ECS message level and only replace the broadcaster transport layer with the mTLS flow from Making Requests.
Complete example
import { Buffer } from 'buffer'
import { Blockstream } from '@blockstream/cryptic'
import { Broadcaster } from '@blockstream/cryptic/broadcaster'
import { generateUserKeyPairs } from '@blockstream/cryptic/utils'
import { v4 as uuidv4 } from 'uuid'
async function onboardApiUser(
adminClient: Blockstream,
email: string,
firstName: string,
lastName: string,
) {
const userKeys = await generateUserKeyPairs()
const adminBroadcaster = new Broadcaster(ECS_BASE_URL, adminClient)
const createResponse = await adminBroadcaster.processBroadcast({
action: 'add',
resource: '/users',
details: {
uid: uuidv4(),
email,
first_name: firstName,
last_name: lastName,
ecdsa_pubkey: userKeys.ecdsa.publicKeyHex,
rsa_pubkey: userKeys.rsa.publicKeyPem,
type: 'api',
},
})
if (createResponse.status !== 'success') {
throw new Error(`User creation failed: ${createResponse.message}`)
}
const { uid, device_uuid, server_ecdsa_pubkey, server_rsa_pubkey } = createResponse.details
const client = new Blockstream(
userKeys.rsa.privateKeyPem,
Buffer.from(userKeys.ecdsa.privateKeyHex, 'hex'),
device_uuid,
server_rsa_pubkey,
Buffer.from(server_ecdsa_pubkey, 'hex'),
)
const broadcaster = new Broadcaster(ECS_BASE_URL, client)
await broadcaster.processBroadcast({
action: 'get',
resource: `/users/${uid}`,
})
return {
uid,
deviceUuid: device_uuid,
serverEcdsaPublicKey: server_ecdsa_pubkey,
serverRsaPublicKeyPem: server_rsa_pubkey,
keys: userKeys,
}
}Troubleshooting
| Issue | Likely cause | What to check |
|---|---|---|
unauthorized response | Wrong device or keys | Confirm the stored device_uuid matches the key pair |
| Signature verification fails | Wrong server ECDSA key format | Confirm server_ecdsa_pubkey is passed as hex bytes |
| Decryption fails | Wrong RSA private key | Confirm the stored PEM matches the created user |
Missing device_uuid | User creation did not complete successfully | Inspect the create-user response status and message |
Related guides
Onboarding a Regular User
How a regular user authorizes a device from an invitation and stores the permanent session credentials returned by the server.
Policy Configuration
How to define ALLOW, DENY, and REVIEW behavior in the Custody Engine, and how to model policy logic clearly for operational workflows.