agentsclimarketplace

Funding transfers

Skill alpacahq/alpaca-skills/skills/broker-api/funding-transfers

Move money between an Alpaca brokerage account and the EXTERNAL banking world via the Broker API — ACH relationships, wire recipient banks, classic transfers (deposits/withdrawals), the v1beta funding wallet (international/instant), transfer status lifecycles, and fees. Use when building deposit/withdrawal flows or connecting external bank accounts on Alpaca in any language. For moving cash/shares BETWEEN accounts inside your own omnibus, use journals instead.From its SKILL.md

Install
npx -y skills add alpacahq/alpaca-skills --skill funding-transfers

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

9.3 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Alpaca Broker API — Funding & Transfers

Getting cash into and out of end-user accounts. There are three rails, and the model splits cleanly into bank links (persistent) and transfers (the actual money movement).

Read alpaca-broker-integration first. Broker API + HTTP Basic auth. For moving cash between accounts in your omnibus (vs. to/from the outside world), see alpaca-broker-journals — that's a different mechanism.

Reference

  • Guide: https://docs.alpaca.markets/docs/funding-accounts
  • API ref: https://docs.alpaca.markets/reference/createtransferforaccount
  • Live schema: alpaca-docs MCP → get-endpoint title "Broker API" path /v1/accounts/{account_id}/transfers

1. The funding model

External bank ──(relationship: a persistent link)──┐
                                                    ├──> Transfer (the money movement) ──> Account cash
ACH relationship  (rail A: ACH, US domestic)        │
Bank relationship (rail B: wire, domestic + intl)   │
Funding wallet    (rail C: v1beta, multi-currency)  ┘
  • A relationship links an external bank. It moves no money and has its own status; for wires it must be APPROVED before a transfer can progress.
  • A transfer references a relationship by ID and moves the money. One relationship backs many transfers.
Railtransfer_typeDirectionsRelationshipNotes
ACHachINCOMING + OUTGOINGACH relationship (relationship_id)US domestic; set up via Plaid processor_token (recommended)
WirewireOUTGOING onlyBank relationship (bank_id)Domestic + international (SWIFT). Incoming wires are pushed by the sending bank and booked automatically
Funding wallet(separate /v1beta API)incoming / outgoing (lowercase)Funding-wallet recipient bankMulti-currency, swift_wire/local_rails

2. Endpoints

MethodPathPurpose
POST/GET/DELETE/v1/accounts/{id}/ach_relationships[/{rel_id}]Manage ACH bank links
POST/GET/DELETE/v1/accounts/{id}/recipient_banks[/{bank_id}]Manage wire recipient banks
POST/v1/accounts/{id}/transfersCreate transfer (ACH deposit/withdraw, or wire withdraw)
GET/v1/accounts/{id}/transfersList transfers
DELETE/v1/accounts/{id}/transfers/{transfer_id}Request cancel
POST/GET/v1beta/accounts/{id}/funding_walletCreate / get funding wallet
POST/GET/DELETE/v1beta/accounts/{id}/funding_wallet/recipient_bankFunding-wallet recipient bank
POST/v1beta/accounts/{id}/funding_wallet/withdrawalFunding-wallet withdrawal
GET/v1beta/accounts/{id}/funding_wallet/transfers[/{transfer_id}]List / get wallet transfers
GET/v2/events/funding/statusSSE — unified funding status stream (see §6)

The current wire-bank endpoint is /recipient_banks (schema Bank/CreateBankRequest). The older /banks name is a legacy alias.

3. Create-transfer request (POST /v1/accounts/{id}/transfers)

Required for all: transfer_type, amount (decimal string, > 0), direction.

// ACH deposit
{ "transfer_type": "ach", "relationship_id": "<uuid>", "amount": "100.00", "direction": "INCOMING" }

// Wire withdrawal
{ "transfer_type": "wire", "bank_id": "<uuid>", "amount": "500.00", "direction": "OUTGOING",
  "fee_payment_method": "user", "additional_information": "..." }
  • relationship_id required iff ach; bank_id required iff wire (and must be the other one's empty).
  • fee_payment_method (wire): user (fee deducted from amount; warn the user in UI) or invoice (firm billed monthly). Only outgoing wire fees auto-process.
  • additional_information is wire-only — sending it on a non-wire request returns 422.
  • The Transfer response adds id, status, fee, requested_amount (original ask), reason, timestamps.

4. Wire recipient bank (POST /v1/accounts/{id}/recipient_banks)

Required: name, bank_code, bank_code_type, account_number.

  • bank_code_type: ABA (9-digit routing, domestic) or BIC (SWIFT, international).
  • When BIC: country, city, state_province, postal_code, street_address become required.
  • extra_fields carries intermediary/correspondent BICs (intermediary_bank1_bic…). Omitting them on international wires can cause auto-selection, delays, or extra fees — gather them up front for cross-border.
  • A new bank starts QUEUED; it must reach APPROVED before a wire transfer against it progresses.

5. Transfer status state machines

Classic transfers (TransferStatus): QUEUED → APPROVAL_PENDING → PENDING → SENT_TO_CLEARING → (APPROVED) → COMPLETE, with REJECTED / CANCELED / RETURNED as failure exits.

TerminalMeaning
COMPLETESettled
REJECTEDRejected
CANCELEDClient-initiated cancel
RETURNEDBank issued an ACH return

(The SSE Transfer entity also reports EXPIRED, which is effectively terminal.)

Funding-wallet transfers (FundingWalletTransferStatus): PENDING, EXECUTED, COMPLETE, CANCELED, FAILED (last three terminal). Note lowercase incoming/outgoing directions here — different casing from classic transfers.

6. Events vs polling — the key reliability lesson

Classic transfers HAVE an SSE stream: GET /v2/events/funding/status. It is unified across four entity_type values — Transfer, BankRelationship, WireBank, FundingWallet — and is replayable via since/until (timestamps) or since_id/until_id (ULIDs). Use it instead of polling for classic ACH/wire status.

Funding-wallet per-transfer status appears NOT to be pushed — only wallet-level status (active/pending) is in the stream. Individual wallet transfer status (PENDING→EXECUTED→COMPLETE) must be polled via GET /v1beta/.../funding_wallet/transfers/{id}.

Lesson (hard-won): rails differ in event coverage. Decide per rail whether you consume SSE or poll, and build a status-reconciliation poller for anything not covered by events (and as a safety net even for those that are — SSE can drop). Map each Alpaca status to your own internal status with an explicit lookup table, and only poll transfers still in a non-terminal state. See alpaca-broker-reconciliation-idempotency.

Legacy caveat: the older us/sse-events "Transfer Events" payload uses an integer event_id and lowercase statuses; the modern /v2/events/funding/status uses ULIDs. Migrate to v2.

7. Documented gotchas

  • Wire fees (since 2022-06-01): outgoing domestic + international wires are charged. Reflect requested_amount vs amount+fee in your UI.
  • Incoming wires need an FFC (For Further Credit) instruction to auto-book; otherwise they're handled manually.
  • Travel Rule: Alpaca requires transmitter/originator info on all incoming deposits regardless of amount (below the usual FinCEN $3,000 threshold). Pass it at settlement creation; retained ≥5 years.
  • ACH uses Plaid: pass the bank via processor_token. There's an instant flag on the relationship. Account types limited to CHECKING/SAVINGS.
  • timing: immediate is deprecated and silently ignored (sunset 2026-08-26) — stop sending it.
  • Permission errors: 403 if the account's depositable_status/withdrawable_status isn't allowed; 422 for incoming-wire attempts, missing/mismatched relationship vs bank IDs, or amounts under the (undocumented) minimums.
  • Sandbox wire behavior: simulated end-to-end but asynchronous and auto-completes on weekdays only — weekend submissions don't progress until Monday. (ACH in sandbox settles instantly.)

8. The omnibus / sweep-account pattern (architecture lesson)

Many production brokers don't fund each user account by a separate external transfer. Instead:

  1. Users pay you through your payment processor (or you receive a bulk wire into a firm/sweep account held at Alpaca).
  2. You then journal cash from the firm account to the user's account instantly (JNLC) — no external ACH/wire per user. See alpaca-broker-journals.
  3. Withdrawals reverse it: journal from user → firm account, then send one external transfer out.

This decouples your funding UX from Alpaca's transfer rails and enables "instant" deposits. It requires Alpaca review (and possibly a local money-transmitter license) — confirm with counsel. The classic transfer endpoints in this skill then handle only the firm-account-to-outside-world leg.

Related skills: internal cash movement → alpaca-broker-journals; missed-status recovery → alpaca-broker-reconciliation-idempotency; money formatting → alpaca-broker-money-precision; live status → alpaca-broker-sse-events.

What ships with it: 1 file

1.2 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.