mcpField
A
alpaca-broker-journals

alpaca-broker-journals

github.com/alpacahqOther157107 installsUpdated 1w ago
Repository

About alpaca-broker-journals

This skill explains moving cash or securities between firm and user accounts within an Alpaca omnibus through the Broker API. It is for teams implementing internal account transfers, covering single and batch journals, idempotent retries, replayable status events, and the possibility that executed journals can be reversed.

Key features

  1. 01Create cash or securities journals
  2. 02Batch and reverse-batch cash transfers
  3. 03Retry safely with idempotency keys
  4. 04Receive replayable status events through SSE
  5. 05Handle lifecycle statuses and reversals

Use cases

  1. 01Move pre-funded firm cash into user accounts
  2. 02Collect user cash into a firm account
  3. 03Issue share-based referral rewards
  4. 04Reconcile journal corrections and retry after timeouts

MCP support

The documentation references alpaca-docs MCP and get-endpoint for the live schema of the Broker API journals endpoint.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/alpacahq/alpaca-skills --skill alpaca-broker-journals

After installing, type/alpaca-broker-journalsin Claude Code to confirm it loaded.

Bundled filesreference.md

Details

From the project README

Alpaca Broker API — Journals

Journals move value between two accounts within your own Alpaca omnibus — typically between a pre-funded firm/sweep account and a user account. They are the engine behind "instant funding," cashback, and share rewards. They never touch the outside banking world (that's alpaca-broker-funding-transfers).

Read alpaca-broker-integration first for base URLs and auth (client-credentials Bearer token; legacy Basic still works).

Reference

  • Guide: https://docs.alpaca.markets/docs/funding-via-journals
  • API ref: https://docs.alpaca.markets/reference/createjournal
  • Live schema: alpaca-docs MCP → get-endpoint title "Broker API" path /v1/journals

1. Endpoints

MethodPathPurpose
POST/v1/journalsSingle journal (JNLC cash or JNLS shares)
POST/v1/journals/batchOne source → many destinations (JNLC only)
POST/v1/journals/reverse_batchMany sources → one destination (JNLC only)
GET/v1/journalsList (filters: after, before, status, entry_type, to_account, from_account, limit)
GET/v1/journals/{journal_id}Retrieve one
DELETE/v1/journals/{journal_id}Cancel a pending journal (204)
GET/v2/events/journals/statusSSE journal status stream (v1 is legacy)

2. JNLC vs JNLS

entry_type is exactly "JNLC" or "JNLS".

  • JNLC — cash. Moves USD between accounts. Allowed firm ↔ user, both directions. Not customer-to-customer.
  • JNLS — securities. Moves whole/fractional shares. Allowed firm → user only. Used for signup/referral share rewards.
// JNLC (cash)
{ "entry_type": "JNLC", "from_account": "<firm-uuid>", "to_account": "<user-uuid>", "amount": "100.00" }

// JNLS (shares)
{ "entry_type": "JNLS", "from_account": "<firm-uuid>", "to_account": "<user-uuid>", "symbol": "AAPL", "qty": "0.5" }
FieldJNLCJNLSNotes
from_account / to_accountrequiredrequiredaccount UUIDs
amountrequired—decimal string
symbol / qty—requiredqty is a string; fractional allowed
currencyoptionaloptionalno documented default
descriptionoptionaloptional≤1024 chars; accepts sandbox fixtures
transmitter_*optional (JNLC)n/aTravel Rule fields

Responses: 200 journal · 403 amount/assets not available · 404 account not found · 422 idempotency-key reused with a different body.

3. Idempotency-Key header — USE IT

Pass an Idempotency-Key header (≤128 chars; a client-generated UUID is recommended) on journal creates.

  • Same key + identical body → returns the original journal (no duplicate).
  • Same key + different body → 422.

Lesson: this is the correct way to make money movement retry-safe. Without it, a network timeout on POST /v1/journals leaves you unsure whether the cash moved — and a blind retry can double-fund. Generate the key deterministically from your own transaction ID and send it on every attempt.

4. Batch vs reverse-batch (JNLC only, all-or-nothing)

Batch — one-to-many (fan a sweep account out to many users):

{ "entry_type": "JNLC", "from_account": "<firm-uuid>",
  "entries": [ { "to_account": "<u1>", "amount": "1000" }, { "to_account": "<u2>", "amount": "250" } ] }

Reverse batch — many-to-one (pull cash from many users back to the firm account):

{ "entry_type": "JNLC", "to_account": "<firm-uuid>",
  "entries": [ { "from_account": "<u1>", "amount": "10" }, { "from_account": "<u2>", "amount": "100" } ] }

Every entry must validate or the entire batch fails (one bad account ID kills it). They diverge after that: /batch accepts Idempotency-Key and returns BatchJournalResponse (the Journal object + an error_message per entry that failed), while /reverse_batch documents neither — it returns a plain array of JNLC with no per-item error, so guard reverse-batch replays locally.

5. Status lifecycle

JournalStatus: queued, sent_to_clearing, pending, executed, activity_created, rejected, canceled, refused, deleted, correct.

Happy path depends on the JNLC version Alpaca has set for you. JNLS and JNLC v1: queued → sent_to_clearing → executed. JNLC v2 (single cash journals only; batches still run v1): the create response frequently comes back already executed, then activity_created follows, and sent_to_clearing appears only on the manual-approval path. Accept all ten statuses either way — executed is the one that means buying power moved.

StatusMeaningTerminal
queuedIn queueno
sent_to_clearingSubmitted to books-and-recordsno
pendingNeeds Alpaca ops approval (usually a JNLC limit — see §7)no
executedBalances updated — but NOT final, can still be reversed by cashieringno (not final)
activity_createdNon-trade activity created (JNLC v2 only); informational, still reversibleno (not final)
rejectedManually rejectedno
refusedFailed preliminary checks; never hit the ledger (e.g. a fast replay failing the balance check)no
canceledCanceled via API/opsFINAL
deletedRemoved from ledgerFINAL
correctA prior executed journal was cancelled and re-created with a corrected amountFINAL

Two critical lessons:

  1. executed ≠ final. Don't treat executed as irreversible — Alpaca cashiering can reverse a journal that wasn't permitted. Reconcile against later events.
  2. correct creates a NEW journal ID. A correction cancels the original and issues a new journal with the corrected amount — it is not an in-place edit. If you reconcile by journal ID, the original ID transitions to correct/cancelled while a different ID carries the real funds. Handle both. (This is why event consumers must be idempotent and ID-keyed — see alpaca-broker-reconciliation-idempotency.)

6. SSE journal events

GET /v2/events/journals/status pushes JournalStatusEventV2: event_id (ULID, sortable), journal_id, entry_type, status_from, status_to, description, idempotency_key, idempotency_key_type (single|batch), batch_error_message. Replay rules: since required if until set; since_id required if until_id set; can't mix since with since_id. Without a since/since_id, no history is returned. See alpaca-broker-sse-events.

7. Constraints & gotchas

  • Eligibility: the cash-pooling/journals use case requires Alpaca review and possibly a local license — check with counsel.
  • JNLS account states: to_account must be ACTIVE; from_account must be ACTIVE or CLOSE.
  • Sufficient funds: JNLC create → 403 if the amount isn't available; reverse-batch 403 = insufficient balance/assets.
  • JNLC limits push to pending (manual ops approval): a per-transaction limit (default $50) and a daily aggregate limit (default $1,000). Both are configurable; an over-limit journal executes in the next day's BOD job.
  • Pre-check cash_transferable on the trading account — it is "Cash available for transfer (JNLC)", unlike cash, which counts unsettled sale proceeds.
  • GET /v1/journals returns 422 if the result set exceeds 100,000 records — always filter with after/before/limit.
  • Delete is pending-only: DELETE succeeds (204) only when pending; an executed journal → 422. To reverse an executed journal, create a mirror journal in the opposite direction, don't try to delete it.
  • Travel Rule: include transmitter info on money-moving journals (required on all incoming deposits regardless of amount).
  • Sandbox fixtures: put fixtures in description (e.g. /fixtures/status=rejected/fixtures/) to simulate rejected/pending outcomes for testing.

8. The sweep-account funding pattern (why journals exist)

The canonical Broker API funding architecture:

Bulk external wire ──> FIRM / SWEEP account (pre-funded) ──JNLC──> user accounts (instant)
user account ──JNLC──> FIRM account ──external wire/ACH──> outside world (withdrawal)

You collect money your own way, hold it in a firm account, and journal it to users instantly rather than running a per-user external transfer. Withdrawals reverse the flow. This is what makes "instant deposit" UX possible on top of slow banking rails.

Related skills: external money in/out → alpaca-broker-funding-transfers; retry-safety & corrections → alpaca-broker-reconciliation-idempotency; decimal handling → alpaca-broker-money-precision; events → alpaca-broker-sse-events.

Comments

Markdown supported