> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bluumfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the API Works

> The cross-cutting basics every request depends on — auth, environments, rate limits, idempotency, pagination, errors, and versioning.

Read this once before you write any code. Everything in the [integration journey](/get-started/journey/overview) 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.

1. Concatenate them with a colon: `API_KEY:API_SECRET`
2. Base64-encode the result
3. Send it as `Authorization: Basic <encoded>`

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://sandbox.api.bluumfinance.com/v1/assets" \
    -H "Authorization: Basic $(echo -n 'YOUR_API_KEY:YOUR_API_SECRET' | base64)"
  ```

  ```javascript Node.js theme={null}
  const credentials = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64');
  const res = await fetch('https://sandbox.api.bluumfinance.com/v1/assets', {
    headers: { Authorization: `Basic ${credentials}` },
  });
  ```

  ```python Python theme={null}
  import requests, base64
  credentials = base64.b64encode(f'{api_key}:{api_secret}'.encode()).decode()
  res = requests.get(
      'https://sandbox.api.bluumfinance.com/v1/assets',
      headers={'Authorization': f'Basic {credentials}'},
  )
  ```
</CodeGroup>

<Warning>
  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.
</Warning>

Get credentials in [Create a sandbox account](/get-started/sandbox).

## 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:

```json theme={null}
{
  "id": "inv_01j9x8m2k7qpzwv3t5r6y8n0ab",
  "object": "investor",
  "created": 1718455800,
  "livemode": false,
  "metadata": {},
  "...": "domain fields"
}
```

## Pagination

List endpoints accept `limit` and `offset` query parameters and return a list envelope:

```json theme={null}
{
  "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.

```bash theme={null}
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

<Note>
  Rate limiting is a stated platform policy. Build for it now so you don't have to retrofit later.
</Note>

| 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:

```json theme={null}
{
  "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](/resources/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](/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](/get-started/sandbox).
