Read this once before you write any code. Everything in the integration journey assumes the conventions below.
Base URLs
| Environment | Base URL |
|---|
| Sandbox | https://sandbox.api.bluumfinance.com/v1 |
| Production | https://api.bluumfinance.com/v1 |
All endpoints are under the /v1 prefix. You receive separate credentials for each environment.
Authentication
Every request uses HTTP Basic authentication. Your API Key is the username, your API Secret is the password.
- Concatenate them with a colon:
API_KEY:API_SECRET
- Base64-encode the result
- Send it as
Authorization: Basic <encoded>
curl "https://sandbox.api.bluumfinance.com/v1/assets" \
-H "Authorization: Basic $(echo -n 'YOUR_API_KEY:YOUR_API_SECRET' | base64)"
The API Secret is shown only once when created. Store it in a secret manager immediately. If lost, generate a new key pair. Never commit credentials or log them in plaintext.
Get credentials in Create a sandbox account.
Object IDs
IDs are opaque, prefixed strings — not raw UUIDs. The prefix tells you the resource type: inv_ investor, fs_ funding source, dep_ deposit, wd_ withdrawal, ord_ order, pos_ position, doc_ document, wh_ webhook, apk_ API key. Pass IDs back exactly as returned; raw UUIDs are rejected on the external surface.
Response shape
Every resource carries a small envelope followed by domain fields:
{
"id": "inv_01j9x8m2k7qpzwv3t5r6y8n0ab",
"object": "investor",
"created": 1718455800,
"livemode": false,
"metadata": {},
"...": "domain fields"
}
List endpoints accept limit and offset query parameters and return a list envelope:
{
"object": "list",
"url": "/v1/investors/inv_.../orders",
"has_more": false,
"data": [ /* resources */ ]
}
Page with offset. limit defaults per endpoint (commonly 50, max 200).
Idempotency
Retrying a request must not move money twice. Send an Idempotency-Key header on state-changing requests — required on deposits and withdrawals, recommended on orders.
curl -X POST "$BASE_URL/investors/$INVESTOR_ID/deposits" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: dep-$(uuidgen)" \
-d '{ "amount": "5000.00", "currency": "USD", "method": "ach", "funding_source_id": "fs_01j9x8m2k7qpzwv3t5r6y8n0ef" }'
| Scenario | Result |
|---|
| First request with a key | Processed; response stored |
| Retry, same key + body | Original response returned — not reprocessed |
| Retry, same key + different body | Error — the key is bound to the first request |
Generate a fresh key per distinct operation (e.g. dep-<uuid>). Keys expire after 24 hours.
Rate limits
Rate limiting is a stated platform policy. Build for it now so you don’t have to retrofit later.
| Environment | Limit |
|---|
| Sandbox | 10 requests/second per key pair |
| Production | 25 requests/second per key pair |
On 429, back off and retry with jitter; honor the Retry-After header when present.
Errors
Errors return a consistent envelope with a structured BLUM-{HTTP_STATUS}-{SEQUENCE} code:
{
"error": {
"type": "invalid_request_error",
"code": "BLUM-400-002",
"message": "Required field 'symbol' is missing.",
"param": "symbol"
}
}
| Status | Retryable | Meaning |
|---|
400 / 422 | No | Validation or business-rule failure — fix the request |
401 / 403 | No | Auth or permission problem — check credentials and product entitlements |
404 | No | Resource doesn’t exist or isn’t yours |
409 | No | Conflict (e.g. duplicate) |
429 / 5xx | Yes | Back off with jitter and retry |
Add-on products (Market Data, Cash Management, Wealth) return 403 with a PRODUCT_NOT_ENABLED code when not enabled for your tenant. See the full catalog in Error Codes.
Versioning
The API is versioned in the path (/v1). Backwards-compatible changes (new endpoints, new optional fields, new enum values) ship without a version bump. Breaking changes ship under a new prefix and are announced in the Changelog.
Request tracing
Every response includes an X-Request-Id header. Log it — include it in support requests to let Bluum trace a specific call.
Next: Create a sandbox account.