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

# Build your own CRM on Hyperprop

> Mirror your traders, accounts, purchases, payouts, invoices and team into your own system, keep it in sync in real time, and act on it, with your organization API key.

Everything your team does with your organization's data in the dashboard is
also available through the Platform API. This guide shows how to build a CRM
or back office on top of it: copy your data once, keep it current from events,
and write changes back safely.

You need an organization API key. Every request below sends it in the
`X-API-Key` header to `https://api.hyperprop.com/platform`.

## 1. Choose your keys

Use two keys with different jobs. An organization can have two active keys
at a time, which is also how you [rotate without downtime](https://docs.hyperprop.com/authentication.md#rotating-a-key-without-downtime).

| Key | Scope | Use it for |
| - | - | - |
| Sync key | `read_only` | Backfills, scheduled reconciliation, dashboards, the event stream |
| Action key | `full` | Writes: creating accounts, lockouts, payouts, team changes, billing details |

A [read-only key](https://docs.hyperprop.com/authentication.md#read-only-keys) can call every `GET`
endpoint and open the trading stream; anything that changes data returns
`403 READ_ONLY_KEY`. Each endpoint in the API reference lists which keys can
call it under **Access**. Add an [IP allowlist](https://docs.hyperprop.com/authentication.md#ip-allowlist)
to both keys if your servers have fixed addresses.

> [!NOTE]
>
> API keys act for the whole organization: a full-access key can do everything
> an admin can, and a read-only key can view everything. Keep role-based access
> inside your CRM, and use [team roles](https://docs.hyperprop.com/guides/team-and-roles.md) for people who
> use the dashboard.

## 2. Map Hyperprop to your data model

| Your CRM | Hyperprop | Read with | Key fields |
| - | - | - | - |
| Customer | Trader | `GET /organization/traders` | `userId`, `email`, `externalUserId`, `metadata` |
| Product subscription | Trading account | `GET /organization/trading-accounts` | `id`, `accountNumber`, `type`, `status`, `userId`, `metadata` |
| Order | Purchase | `GET /organization/purchases` | `id`, `status`, `pricePaid`, `currency`, `metadata` |
| Payout | Payout | `GET /organization/accounts/{accountId}/payouts` | `id`, `amount`, `status` |
| Product | Trading plan and rule | `GET /organization/trading-plans`, `GET /organization/trading-rules` | `id`, `name` |
| Staff user | Team member | `GET /organization/team` | `id`, `email`, `roleId`, `roleName` |
| Vendor bill | Invoice | `GET /organization/billing/invoices` | `id`, `number`, `status`, `amountRemainingCents` |

Store Hyperprop IDs next to your own. To find records by your IDs instead, set
them on Hyperprop's side:

* `customerId` on a trading account, then filter with `?customerId=`.
* The trader's external ID (`traderExternalId` when you create an account),
  then filter traders with `?externalUserId=`.
* Free-form [metadata](https://docs.hyperprop.com/concepts/metadata.md) on accounts, traders and purchases,
  then filter with `?metadata=key:value`.

## 3. Backfill with cursor pagination

Read each collection page by page with `cursor`. Cursors stay stable while
data changes underneath you, so a backfill never skips or repeats a record.
See [pagination](https://docs.hyperprop.com/concepts/pagination.md).

```javascript theme={null}
const BASE = 'https://api.hyperprop.com/platform/v1/organization';
const headers = { 'X-API-Key': process.env.HYPERPROP_SYNC_KEY };

// Yields each page's items: data.accounts, data.traders, data.purchases, ...
async function* pages(path, collection, params = {}) {
  let cursor;
  do {
    const query = new URLSearchParams({ limit: '100', ...params, ...(cursor ? { cursor } : {}) });
    const res = await fetch(`${BASE}${path}?${query}`, { headers });
    const body = await res.json();
    if (!res.ok) throw new Error(`${res.status} ${body.code}`);
    yield body.data[collection];
    const { hasMore, nextCursor } = body.data.pagination;
    cursor = hasMore ? nextCursor : undefined;
  } while (cursor);
}

for await (const accounts of pages('/trading-accounts', 'accounts')) {
  await crm.upsertAccounts(accounts);
}
```

Backfill in this order so references resolve: trading plans and rules,
traders, trading accounts, purchases, then payouts per account. Invoices and
credits are short lists you read in one call.

## 4. Keep it in sync in real time

After the backfill, apply changes as they happen. Two channels carry the same
events with the same payloads; most CRMs use both.

| | [Webhooks](https://docs.hyperprop.com/guides/webhooks.md) | Event stream |
| - | - | - |
| Transport | HTTPS POST to your endpoint | WebSocket you open with your API key |
| Delivery | Retried with backoff, signed, redeliverable | Live while connected; catch up with `?since=` |
| Best for | Durable updates your CRM must not miss | Live screens, and environments without a public endpoint |

Subscribe a webhook endpoint to the events your CRM models, for example
`trader.created`, `account.created`, `account.status_changed`,
`account.passed`, `account.failed`, `purchase.created`,
`purchase.status_changed` and `account.payout_processed`. Verify each
delivery's [signature](https://docs.hyperprop.com/guides/webhooks.md#verifying-signatures), and make
applying an event idempotent: deliveries can repeat. See
[deduplication](https://docs.hyperprop.com/guides/webhooks.md#deduplication).

For a live view, connect to the event stream with the read-only key:

```javascript theme={null}
import WebSocket from 'ws';

const ws = new WebSocket('wss://api.hyperprop.com/platform/v1/organization/events/stream', {
  headers: { 'X-API-Key': process.env.HYPERPROP_SYNC_KEY },
});
ws.on('message', (raw) => {
  const frame = JSON.parse(raw);
  if (frame.type === 'event') crm.applyEvent(frame.event); // same body as a webhook
});
```

The API key must be sent in the `X-API-Key` header. A key in the URL is not
accepted, and the connection is refused with `400`.

After a disconnect, reconnect with `?since=<ISO timestamp of the last event
you applied>` to receive what you missed, then live events resume. For live
positions, balances and fills per account, use the
[real-time trading stream](https://docs.hyperprop.com/guides/realtime-trading-stream.md).

## 5. Reconcile on a schedule

Events tell you what changed; a periodic check proves nothing was missed.

* **Balances and account state:** `GET /organization/balances?updatedSince=`
  returns only accounts whose balance or state changed since the `asOf` of
  your previous call. Run it every few minutes.
* **New records:** list trading accounts, traders and purchases with
  `startDate` set to your last check and add anything you do not have.
* **Event history:** `GET /organization/events` lists every event your
  organization produced, filterable by `eventType` and `accountId`, so you
  can replay a gap.
* **End of day:** after the trading day closes, read balances and fills for
  the day from the reconciliation endpoints for your books.

## 6. Write changes back safely

Send writes with the action key and an `Idempotency-Key` header. A retry with
the same key and body returns the original response instead of repeating the
change, so a timeout never creates two accounts or two payouts. See
[idempotency](https://docs.hyperprop.com/concepts/idempotency.md).

```bash theme={null}
curl -X POST "https://api.hyperprop.com/platform/v1/organization/trading-accounts" \
  -H "X-API-Key: hp_live_your_key_here" \
  -H "Idempotency-Key: crm-order-8814" \
  -H "Content-Type: application/json" \
  -d '{"type": "evaluation", "tradingPlanId": "22222222-2222-4222-8222-222222222222", "email": "trader@example.com", "customerId": "crm-cust-4471"}'
```

Use your own record ID in the idempotency key (`crm-order-8814`), so the same
order always maps to the same key.

## 7. Bring in billing and your team

* **Invoices and credits:** show what you owe Hyperprop next to your revenue.
  See [Billing and invoices](https://docs.hyperprop.com/guides/billing-and-invoices.md).
* **Team:** add and remove dashboard users and give them roles from your own
  admin screens. See [Team and roles](https://docs.hyperprop.com/guides/team-and-roles.md).
* **Support:** open and follow up on support tickets with
  `GET /organization/support/tickets` and `POST /organization/support/tickets`.

## Handle errors

Every error has the same body, and `code` is the field to switch on. See
[errors](https://docs.hyperprop.com/concepts/errors.md).

```json theme={null}
{ "success": false, "statusCode": 403, "error": "Forbidden", "code": "READ_ONLY_KEY", "message": "This API key is read-only. Use a key with full access for requests that change data." }
```

| Status | Typical codes | What to do |
| - | - | - |
| 400 | `VALIDATION_ERROR`, `INVALID_CURSOR` | Fix the request; don't retry as is |
| 401 | `UNAUTHORIZED` | The key is missing, wrong or revoked |
| 403 | `READ_ONLY_KEY`, `IP_NOT_ALLOWED`, `INSUFFICIENT_PERMISSIONS` | Use the right key or address |
| 404 | `NOT_FOUND` and resource-specific codes | The record is not in your organization |
| 409 | `IDEMPOTENCY_KEY_IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED`, state conflicts | Back off and retry, or use a new key for a different request |
| 429 | `RATE_LIMITED` | Wait `Retry-After` seconds |
| 5xx | `INTERNAL_ERROR` | Retry with the same `Idempotency-Key` and backoff |

## Rate limits

The Platform API has no general request-rate limit, so a backfill needs no
throttling. A few endpoints have their own limits (live account reads, invoice
PDFs and payment instructions, billing details updates and CSV exports); they
answer `429` with `Retry-After`. See [rate limits](https://docs.hyperprop.com/concepts/rate-limits.md).


## Related topics

- [HDL for builders](https://docs.hyperprop.com/hdl/api.md)
- [Custom indicators](https://docs.hyperprop.com/indicators/overview.md)
- [Credit or debit a trading account](https://docs.hyperprop.com/platform-api/trading-accounts/credit-or-debit-a-trading-account.md)
- [Real-time trading stream](https://docs.hyperprop.com/guides/realtime-trading-stream.md)
- [Introduction](https://docs.hyperprop.com/introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.