Skip to content

JikoNet transfers

A JikoNet transfer — also known as an On-Us transfer in the API — is the easiest way to send cash directly between two Jiko pockets. Nothing touches an external network, so there is no cut-off, no return window and no clearing delay.

JikoNet Use Cases

  • Build a peer to peer payment network like venmo
  • Debit and Credit your users Jiko pockets

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
allowed_pocket_typesPassing an explicit type when creating a pocket. A comma-separated list, for example JIKO_PAY,JIKO_STORE. A type outside the list is refused403 on pocket creation. Omitting type is accepted and you get your partnership's default type

Nothing gates POST /api/v1/transfers/on-us/ itself, and an On-Us counterparty — type: "on_us" with name_match verification — needs no flag either. The flag above matters only because the rail needs a particular kind of pocket, and that is what decides whether you can ask for one.

Everything else

  • JikoPay pockets at both ends. A portal on a JikoStore pocket does not carry the ON_US rail. Read the portal's payment_rails rather than assuming, and confirm with your Jiko point of contact which pocket types your partnership can open.
  • Approved customers and pockets in OPEN status on both sides of the transfer.
  • A partner-owned facilitation account, for funding and defunding. Jiko provisions it; you address it by pocket ID in from_account or to_account. Without one, PARTNER_CUSTOMER_FUNDING and PARTNER_CUSTOMER_DEFUNDING have no near end.
  • An on_us counterparty in LINKED status, for peer-to-peer. Registered from the recipient portal's account_number and the account holder's exact legal_name. The recipient portal must be OPEN, must carry ON_US, and must not belong to the registering customer — a customer moving money between their own pockets uses INTERNAL_REALLOCATION, not a counterparty. A name mismatch stops the counterparty being created, and the error is about the name rather than the account.
  • Two pockets of the same customer, for internal reallocation. SECURITY_TRANSFER is currently limited to JikoPay pockets.
  • Balance that can actually move. Use FULL_WITHDRAWAL where you mean "everything", rather than reading a balance and sending it back as a requested amount.
  • A webhook subscription, since the recommended async_mode: true reports progress only through events: transfers.on-us.processing, transfers.on-us.success and transfers.on-us.rejected on the originating side, transfers.on-us.received on the receiving side. Subscriptions are recorded against the API user that creates them.

The four types

typeMoves moneyTypical use
PARTNER_CUSTOMER_FUNDINGFrom a partner-owned facilitation account to a customer's pocketDisbursing funds to a customer
PARTNER_CUSTOMER_DEFUNDINGFrom a customer's pocket to a partner-owned facilitation accountCollecting funds from a customer
PEER_TO_PEERBetween two of your customersCustomer-to-customer payment
INTERNAL_REALLOCATIONBetween two pockets of the same customerMoving a customer's own money

Funding and defunding carry an extra descriptive field — source on funding, target on defunding — recording where the money came from or went to on your own side. Free text up to 200 characters. It is your reference; Jiko does not interpret it.

Reallocation has two flavors

INTERNAL_REALLOCATION takes a transfer_type:

  • BANK_TRANSFER (default) — sells holdings in the origin pocket and buys new holdings for the destination.
  • SECURITY_TRANSFER — moves the securities directly between pockets with no bank transaction. Instant, and currently limited to JikoPay pockets.

Addressing the far end

How you identify the destination depends on the type.

Funding, defunding and internal reallocation address a pocket directly:

"to_account": { "id": "<pocket_id>", "type": "JIKO_ACCOUNT" }

Peer-to-peer addresses an On-Us counterparty:

"to_account": { "id": "<counterparty_id>", "type": "COUNTERPARTY" }

The sending customer registers the recipient by their Jiko network address — the account number of a virtual bank account portal that carries the ON_US rail — and Jiko verifies it by name match against the account holder:

POST /api/v2/customers/{customer_id}/counterparties/

with type: "on_us", the recipient's account_number, and a name_match verification carrying the recipient's legal_name.

Get both values from the recipient portal's funding instructions:

GET /api/v2/pockets/{pocket_id}/portals/{portal_id}/funding-instructions/

The ON_US instruction returns account_number (the Jiko network address) and legal_name. Pass both to the sender — a name mismatch prevents the counterparty being created, and the error at that point is about the name, not the account.

A portal only carries ON_US if the pocket is JikoPay. Read the portal's payment_rails rather than assuming.

Two things are rejected at registration: a portal that is not OPEN or does not allow ON_US, and a portal belonging to the registering customer themselves. Use INTERNAL_REALLOCATION to move a customer's money between their own pockets.

Workflow, for a peer-to-peer transfer

Partner systems:

  1. Ask the recipient for their Jiko network address. It comes from the ON_US funding instruction on their portal: an account_number and a legal_name.
  2. Register it against the sending customer with type: "on_us" and a name_match verification carrying that exact legal name.
  3. Wait for counterparty.status.linked.
  4. Create the transfer with type: "PEER_TO_PEER", async_mode: true, and to_account addressing the counterparty.
  5. Track it by on_us_transfer_id, which both ends share.

Funding, defunding and internal reallocation skip steps 1–3 — they address a pocket by ID and need no counterparty.

Relevant API endpoints

StepPurposeEndpoint
1Read the recipient's ON_US address and legal nameGET /api/v2/pockets/{pocket_id}/portals/{portal_id}/funding-instructions/
2Register the recipient as an On-Us counterpartyPOST /api/v2/customers/{customer_id}/counterparties/
3Check its statusGET /api/v2/customers/{customer_id}/counterparties/{counterparty_id}/
4Dry-run a security-transfer reallocationPOST /api/v1/transfers/on-us/quotes/
5Create the transferPOST /api/v1/transfers/on-us/
6Read it back from either side, where both ends are yoursGET /api/v1/transfers/on-us/{transfer_id}/

Specifying the amount

Two forms are accepted, discriminated on amount.type:

  • REQUESTED_AMOUNT — a specific amount in USD cents (amount_usdc).
  • FULL_WITHDRAWAL — liquidates all holdings in the source pocket and moves the proceeds.

FULL_WITHDRAWAL is available on defunding, peer-to-peer and internal reallocation. It is not valid for PARTNER_CUSTOMER_FUNDING.

Use FULL_WITHDRAWAL rather than reading a balance and sending it as a requested amount — it avoids the race between the two calls, and the source pocket's balance is invested, not idle cash.

The top-level amount_usdc field on the request body is deprecated in favor of the amount object. New integrations should send amount.

Synchronous and asynchronous

The request takes an async_mode flag, defaulting to false.

Use async_mode: true. Jiko accepts the instruction, returns immediately with the transfer in PENDING, and reports progress through webhooks. With async_mode: false the call blocks until the transfer resolves, which ties your request to Jiko's processing time and makes timeouts your problem to reconcile.

Quotes

POST /api/v1/transfers/on-us/quotes/

A quote is a dry run of a security-transfer internal reallocation: the same validation and the same ruleset evaluation as the real thing, but nothing is booked and nothing is persisted. It returns:

FieldMeaning
is_allowedWhether the transfer would be permitted at evaluation time
rejection_reasonPartner-facing reason when is_allowed is false, otherwise null
rejection_detailHuman-readable explanation, otherwise null
settlement_typeThe settlement the transfer would receive, for example T+0 or T+1
eligible_transfer_amountMaximum the customer is eligible to move given their short-maturity holdings. Zero when the request was rejected before holdings could be evaluated

Only type: "INTERNAL_REALLOCATION" with transfer_type: "SECURITY_TRANSFER" is quotable. Funding, defunding, peer-to-peer and bank-transfer reallocation are rejected with 422.

A successful quote is indicative. It is not a guarantee that the transfer will book.

Lifecycle

StatusMeaning
PENDINGAccepted, in progress
COMPLETEDBoth legs posted
REJECTEDNot processed

A rejection carries a reason: INSUFFICIENT_FUNDS, ACCOUNT_UNAVAILABLE, INVALID_TRANSFER, INVALID_CURRENCY, SANCTION_SCREEN_FAILED or CANCELED.

The transfer resource also returns a fees list. Fees are a distinct posting against the pocket and are never netted against the transfer amount.

Webhooks

To the originating side:

EventMeaning
transfers.on-us.processingInstruction accepted and in progress
transfers.on-us.successTransfer completed
transfers.on-us.rejectedTransfer rejected

Payload: on_us_id, origin_account_id, destination_account_id.

To the receiving side:

EventMeaning
transfers.on-us.receivedFunds credited to the receiving pocket

Payload: on_us_id, receiving_pocket_id, transaction_activity_id.

That is the most complete payload of any rail — the resulting transaction is addressable directly, with no search of the feed.

You receive both sets of events for a peer-to-peer transfer only when both ends are your own customers. Where the recipient belongs to another partner, you get the originating events and their partner gets received.

Endpoints

OperationEndpoint
Create a transferPOST /api/v1/transfers/on-us/
Quote a security transferPOST /api/v1/transfers/on-us/quotes/
Get a transferGET /api/v1/transfers/on-us/{transfer_id}/

The create request is discriminated on type and accepts:

  • transfer_id — client-generated UUID for idempotency
  • end_to_end_identification — up to 35 characters
  • description — up to 100 characters

GET /api/v1/transfers/on-us/{transfer_id}/ resolves from either side of a transfer where both ends are yours. It returns the transfer's type, amount, status, rejection reason where applicable, and any fees.

Reading the money

Both legs appear in the transaction feed as type: "ON_US":

  • on_us_transfer_id — the transfer, shared by both sides
  • on_us_transfer_type — PEER_TO_PEER_TRANSFER, INTERNAL_REALLOCATION and so on
  • counterparty_name — the party at the other end
  • associated_pocket_id — the pocket at the other end
  • end_to_end_identification — the sender's reference, visible to the recipient
  • direction — DEBIT on the sending pocket, CREDIT on the receiving one

Filter with filter[on_us_transfer] to resolve a transfer ID to its transactions, or filter[type]=ON_US for the rail.

Tracing

JikoNet is the simplest rail to trace. on_us_transfer_id is a single identifier shared by both ends and present on the transfer resource, on both transactions, and on every webhook. Where both ends are your own customers you can see the complete transfer from either side, which no other rail offers. Where the recipient belongs to another partner you see your own leg only, as on any external rail.

Where the sender populates end_to_end_identification, that reference travels with the transfer and is visible to the recipient — the closest thing Jiko offers to a payer-supplied reference both parties agree on in advance.

See also