Skip to content

ACH Originating

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.

Prerequisites

Feature flags

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.

FlagNeeded forWithout it
pre_verified_ach_counterpartyRegistering 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 flag403 at counterparty creation. You never reach LINKED, so you cannot originate at all
ccd_sec_codeOriginating 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.

Everything else

  • ACH origination enabled on your partnership. Commercial rather than a flag. Contact your Jiko point of contact.
  • An approved customer. customer_id comes from an application that reached APPROVED — see Onboarding individuals and Onboarding businesses.
  • A pocket in OPEN status. A pocket is PENDING while it is being opened; FROZEN and CLOSED pockets cannot originate.
  • An ACH counterparty in LINKED status, 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, and PENDING is not enough. FAILED, UNLINKED and CLOSED are terminal — none of them returns to LINKED, so recovering means registering a new counterparty. Wait for the counterparty.status.linked webhook rather than assuming.
  • Balance that can actually move, for a CREDIT. A pocket's balance is held in T-Bills. Read liquidation_value_t0 and liquidation_value_t1 on the pocket's portfolio, not total_value. A DEBIT has no such requirement — funds are arriving, not leaving.
  • An X-Jiko-Idempotency header. Required on every create call. Supply a transfer_id as well.
  • A webhook subscription, unless you intend to poll. POST /api/v1/subscriptions/ for transfers.ach.out.sent, transfers.ach.out.success, transfers.ach.out.rejected and counterparty.status.linked. Subscriptions are recorded against the API user that creates them, so subscribe with the user whose traffic you want to hear about.

Register the ACH counterparty

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_methodMeans
PLAIDThe customer authenticated into the external bank through Plaid on your side
MICRO_DEPOSITThe 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.

Workflow

Partner systems:

  1. Verify the external account on your own side, by Plaid or by micro-deposits.
  2. Create the counterparty with type: "ach" and a pre_verified verification block naming which method you used.
  3. Wait for the counterparty.status.linked webhook. PENDING is not enough to originate against.
  4. Originate against counterparty_id.

Relevant API endpoints

StepPurposeEndpoint
1Register the external account as a counterpartyPOST /api/v2/customers/{customer_id}/counterparties/
2Check the counterparty's status directly, rather than waiting on the webhookGET /api/v2/customers/{customer_id}/counterparties/{counterparty_id}/
3List a customer's counterpartiesGET /api/v2/customers/{customer_id}/counterparties/

Create an origination

POST /api/v1/jiko-accounts/{account_id}/ach-originating/
FieldNotes
counterparty_idThe linked ACH counterparty
amount_usdcAmount in USD cents
directionCREDIT or DEBIT
sec_codeWEB (default) or CCD. CCD is enabled per partner
company_entry_descriptionUp 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_informationAddenda text. At most one entry on WEB and CCD, each up to 80 characters
transfer_idClient-generated UUID for idempotency. Optional, strongly recommended

X-Jiko-Idempotency is a required header on this endpoint.

Retrying safely

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_id once per transfer, not once per attempt. Generating a new one inside a retry loop creates a new transfer on every attempt.
  • A retry returns 200 with 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, not PENDING.
  • transfer_id must 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_id to retry a REJECTED transfer — the rejected transfer is what comes back. Make a fresh attempt with a new transfer_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.

Relevant API endpoints

StepPurposeEndpoint
1Create the originationPOST /api/v1/jiko-accounts/{account_id}/ach-originating/
2Read one back, by the transfer_id you suppliedGET /api/v1/jiko-accounts/{account_id}/ach-originating/{transfer_id}/
3List a pocket's originationsGET /api/v1/jiko-accounts/{account_id}/ach-originating/
4List a customer's originationsGET /api/v1/customers/{customer_id}/ach-originating/

Settlement timing

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.

Lifecycle

StatusMeaning
PENDINGAccepted by Jiko, not yet released
APPROVEDCleared Jiko's checks
IN_TRANSITSent to the ACH network
PROCESSEDCompleted
REJECTEDRejected before or after sending
FAILEDCould not be processed
CANCELLEDCancelled 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.

Webhooks

EventMeaning
transfers.ach.out.sentEntry released to the network
transfers.ach.out.successEntry processed
transfers.ach.out.rejectedEntry 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.

Endpoints

OperationEndpoint
Create an originationPOST /api/v1/jiko-accounts/{account_id}/ach-originating/
List a pocket's originationsGET /api/v1/jiko-accounts/{account_id}/ach-originating/
Get an originationGET /api/v1/jiko-accounts/{account_id}/ach-originating/{transfer_id}/
List a customer's originationsGET /api/v1/customers/{customer_id}/ach-originating/

Both list endpoints are cursor-paginated and accept filter[status] and direction.

Reading the money

An origination appears in the transaction feed as type: "ACH" with ach_direction: "ORIGINATION":

  • ach_id — the origination ID, matching ach_origination_id from the webhook
  • company_name, company_entry_description — as submitted
  • direction — DEBIT where funds left the pocket, CREDIT where they were pulled in
  • trace_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:

OperationEndpoint
Pocket transactionsGET /api/v2/pockets/{pocket_id}/transactions/
Customer transactionsGET /api/v2/customers/{customer_id}/transactions/
All partner transactionsGET /api/v2/transactions/
Single transactionGET /api/v2/transactions/{transaction_activity_id}/

Inbound ACH, for contrast

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

See also