An ACH origination is an entry Jiko sends on your customer's behalf to a counterparty they have registered and verified. It is the only Jiko rail that moves money in both directions from a single endpoint:
- Credit — funds leave the pocket and are pushed to the counterparty. A withdrawal or disbursement.
- Debit — funds are pulled from the counterparty into the pocket. A funding pull.
Both are originated by the same call against the same counterparty. What makes a debit "outbound" is that Jiko originates it, not the direction the money travels.
Feature flags are set by Jiko per partner and evaluated against the API user making the request. A call that needs a flag you do not have fails with 403. Ask your Jiko point of contact for the ones you need.
| Flag | Needed for | Without it |
|---|---|---|
pre_verified_ach_counterparty | Registering an ACH counterparty. POST /api/v2/customers/{customer_id}/counterparties/ with type: "ach" accepts pre_verified verification and nothing else, so every ACH counterparty created through the v2 API passes through this flag | 403 at counterparty creation. You never reach LINKED, so you cannot originate at all |
ccd_sec_code | Originating with sec_code: "CCD" | 403 at origination. WEB is unaffected |
The ccd_sec_code check runs before the idempotency block, so a denied request does not spend your X-Jiko-Idempotency value or your transfer_id.
- ACH origination enabled on your partnership. Commercial rather than a flag. Contact your Jiko point of contact.
- An approved customer.
customer_idcomes from an application that reachedAPPROVED— see Onboarding individuals and Onboarding businesses. - A pocket in
OPENstatus. A pocket isPENDINGwhile it is being opened;FROZENandCLOSEDpockets cannot originate. - An ACH counterparty in
LINKEDstatus, registered against the customer who owns the pocket. This is the one that catches integrations out. Jiko never sends to a raw routing and account number, andPENDINGis not enough.FAILED,UNLINKEDandCLOSEDare terminal — none of them returns toLINKED, so recovering means registering a new counterparty. Wait for thecounterparty.status.linkedwebhook rather than assuming. - Balance that can actually move, for a
CREDIT. A pocket's balance is held in T-Bills. Readliquidation_value_t0andliquidation_value_t1on the pocket's portfolio, nottotal_value. ADEBIThas no such requirement — funds are arriving, not leaving. - An
X-Jiko-Idempotencyheader. Required on every create call. Supply atransfer_idas well. - A webhook subscription, unless you intend to poll.
POST /api/v1/subscriptions/fortransfers.ach.out.sent,transfers.ach.out.success,transfers.ach.out.rejectedandcounterparty.status.linked. Subscriptions are recorded against the API user that creates them, so subscribe with the user whose traffic you want to hear about.
POST /api/v2/customers/{customer_id}/counterparties/type: "ach" accepts one verification type, pre_verified. You assert that you have already verified the account on your side, and say how:
verification.verification_method | Means |
|---|---|
PLAID | The customer authenticated into the external bank through Plaid on your side |
MICRO_DEPOSIT | The customer confirmed two small deposits on your side |
{
"type": "ach",
"routing_number": "021000021",
"account_number": "1234567890",
"account_type": "CHECKING",
"counterparty_name": "Jane Roe",
"verification": { "type": "pre_verified", "verification_method": "PLAID" }
}There is no verification Jiko drives on your behalf here — no Plaid flow Jiko hosts, no micro-deposits Jiko sends. Both values describe work you did. That makes pre_verified_ach_counterparty a hard prerequisite rather than an optimization; see Prerequisites.
A debit in particular cannot be made against an unverified account, because verification is what establishes your customer's right to pull from it.
Partner systems:
- Verify the external account on your own side, by Plaid or by micro-deposits.
- Create the counterparty with
type: "ach"and apre_verifiedverification block naming which method you used. - Wait for the
counterparty.status.linkedwebhook.PENDINGis not enough to originate against. - Originate against
counterparty_id.
| Step | Purpose | Endpoint |
|---|---|---|
| 1 | Register the external account as a counterparty | POST /api/v2/customers/{customer_id}/counterparties/ |
| 2 | Check the counterparty's status directly, rather than waiting on the webhook | GET /api/v2/customers/{customer_id}/counterparties/{counterparty_id}/ |
| 3 | List a customer's counterparties | GET /api/v2/customers/{customer_id}/counterparties/ |
POST /api/v1/jiko-accounts/{account_id}/ach-originating/| Field | Notes |
|---|---|
counterparty_id | The linked ACH counterparty |
amount_usdc | Amount in USD cents |
direction | CREDIT or DEBIT |
sec_code | WEB (default) or CCD. CCD is enabled per partner |
company_entry_description | Up to 10 characters, letters, digits and spaces only. Required when sec_code is CCD. Defaults when omitted on WEB: Funding TX for a credit, Release TX for a debit |
payment_related_information | Addenda text. At most one entry on WEB and CCD, each up to 80 characters |
transfer_id | Client-generated UUID for idempotency. Optional, strongly recommended |
X-Jiko-Idempotency is a required header on this endpoint.
Send a transfer_id you generate yourself. Retrying with the same transfer_id returns the original transfer instead of creating a second one, so a request that times out mid-flight can be repeated without risking a duplicate debit.
- Generate
transfer_idonce per transfer, not once per attempt. Generating a new one inside a retry loop creates a new transfer on every attempt. - A retry returns
200with the transfer's current status, which is indistinguishable from a fresh create. A transfer created two days ago comes back with the status it has now, notPENDING. transfer_idmust be unique across all of your transfers, not just ACH originations. Reusing a value spent on another transfer type is rejected.- You cannot reuse a
transfer_idto retry aREJECTEDtransfer — the rejected transfer is what comes back. Make a fresh attempt with a newtransfer_id. - The nil UUID (
00000000-0000-0000-0000-000000000000) is rejected. It is what an uninitialized client variable serializes to, and it would succeed once and then conflict with itself forever.
Reusing a transfer_id with a different body returns 409 jiko.errors.transfers.ACHTransferIdConflictError. A retry must repeat both the same transfer_id and the same body.
If you omit transfer_id, X-Jiko-Idempotency behaves as it does everywhere else: a repeated value within the hour is rejected with 409 jiko.errors.DuplicateRequestError. That header only rejects duplicates — it cannot hand you back the original — so transfer_id is the better choice for anything that needs to be retried.
| Step | Purpose | Endpoint |
|---|---|---|
| 1 | Create the origination | POST /api/v1/jiko-accounts/{account_id}/ach-originating/ |
| 2 | Read one back, by the transfer_id you supplied | GET /api/v1/jiko-accounts/{account_id}/ach-originating/{transfer_id}/ |
| 3 | List a pocket's originations | GET /api/v1/jiko-accounts/{account_id}/ach-originating/ |
| 4 | List a customer's originations | GET /api/v1/customers/{customer_id}/ach-originating/ |
Settlement on this rail is covered in full by ACH Transfer Settlement Timing, which explains the processing windows, the delay before an entry reaches the network, and the delay after it before Jiko marks a transfer settled. Read it before you quote a payout time to an end customer.
One point belongs here, because it is about the call you are making rather than about the network.
A pocket's balance is held in T-Bills, not idle cash. Before an outbound credit can be released, the amount has to be liquidated and that sale has to settle. Read liquidation_value_t0 and liquidation_value_t1 on the pocket's portfolio before instructing: total_value is what the customer owns, and the liquidation values are what can actually be moved, and when. An instruction whose liquidation has not settled cannot reach the network that day, whatever the amount and whatever the hour.
A debit origination has no such leg. Funds are arriving, not leaving.
| Status | Meaning |
|---|---|
PENDING | Accepted by Jiko, not yet released |
APPROVED | Cleared Jiko's checks |
IN_TRANSIT | Sent to the ACH network |
PROCESSED | Completed |
REJECTED | Rejected before or after sending |
FAILED | Could not be processed |
CANCELLED | Cancelled before release |
Completion is not finality. The receiving bank can return an entry days after it settles — insufficient funds, a closed account, an unauthorized debit. A return reverses the money movement and appears as a separate transaction, not as a status change on the original origination. Do not treat PROCESSED as the end of the story, particularly for debits.
| Event | Meaning |
|---|---|
transfers.ach.out.sent | Entry released to the network |
transfers.ach.out.success | Entry processed |
transfers.ach.out.rejected | Entry rejected |
Payload: jiko_account_id and ach_origination_id. The origination is directly retrievable, so there is no need to scan the transaction feed. Intermediate statuses such as PENDING and APPROVED raise no event.
| Operation | Endpoint |
|---|---|
| Create an origination | POST /api/v1/jiko-accounts/{account_id}/ach-originating/ |
| List a pocket's originations | GET /api/v1/jiko-accounts/{account_id}/ach-originating/ |
| Get an origination | GET /api/v1/jiko-accounts/{account_id}/ach-originating/{transfer_id}/ |
| List a customer's originations | GET /api/v1/customers/{customer_id}/ach-originating/ |
Both list endpoints are cursor-paginated and accept filter[status] and direction.
An origination appears in the transaction feed as type: "ACH" with ach_direction: "ORIGINATION":
ach_id— the origination ID, matchingach_origination_idfrom the webhookcompany_name,company_entry_description— as submitteddirection—DEBITwhere funds left the pocket,CREDITwhere they were pulled intrace_number— not populated on originations. It is only present on entries Jiko received
ach_direction distinguishes ORIGINATION from RECEIVING. It is not the credit/debit direction — use the base direction field for that.
Returns appear separately. A returned origination produces a further transaction, also type: "ACH" but with ach_direction: "RECEIVING", carrying the return's own trace_number and ach_id. The return does not carry the original origination's ID, so reconciling end to end means matching the original transaction with any later return against the same counterparty and amount.
Feeds:
| Operation | Endpoint |
|---|---|
| Pocket transactions | GET /api/v2/pockets/{pocket_id}/transactions/ |
| Customer transactions | GET /api/v2/customers/{customer_id}/transactions/ |
| All partner transactions | GET /api/v2/transactions/ |
| Single transaction | GET /api/v2/transactions/{transaction_activity_id}/ |
/api/v1/customers/{customer_id}/ach-originating/ lists outgoing entries only. Money arriving over ACH is received against a portal, not a counterparty, and surfaces only in the transaction feed as type: "ACH" with ach_direction: "RECEIVING". Events are transfers.ach.in.success and transfers.ach.in.rejected, whose payload carries jiko_account_id and nothing else — treat them as a signal to go read the pocket's transactions, not as a description of what happened.
One consequence worth stating plainly to anyone designing an inbound flow: a portal's routing and account number are the customer's bank account details, reachable in both directions. Anyone holding them can originate a debit against the pocket, exactly as they could against any bank account. Jiko returns the entry if the portal is closed, is not ACH-enabled, or cannot fund it, but a valid debit against an open, funded portal will settle.
- ACH Transfer Settlement Timing — when an entry reaches the network, and when it settles
- Create ACH origination — full request and response schema
- Counterparties — registering and verifying the destination
- Webhooks