Customer Service

Contact-center / customer service operations through ORCA. The flagship workflow is an intra-bank internal transfer between two related parties at the same FI — the "mother → son" scenario — initiated by a customer-service representative on behalf of a customer. The page also covers customer and account lookups, balance checks, transaction inquiries, and the two-phase memo-post / hard-post settlement model.

Business context

Contact-center agents need a single, normalized interface to handle the operations a customer can ask them to perform: look up an account, check a balance, recite recent transactions, post a transfer between family members' accounts, place or release a hold. Each Core (Fiserv, FIS, Jack Henry, etc.) has its own dialect for these reads and writes.

ORCA exposes the read endpoints needed for verification and a small set of write endpoints (/internal-transfers, /accounts/{id}/deposits, /accounts/{id}/withdrawals, /accounts/{id}/holds) that cover the most common servicing actions. The contact-center application makes one set of API calls regardless of the underlying Core.

Scope

In-scope for this use case:

  • Customer search & verification — lookup by name, SSN, phone, email, account number; retrieve detail to confirm identity
  • Account inquiry — account detail, status, owners, parties, balances, transactions
  • Internal transfer initiation — intra-bank transfers between any two accounts at the same servicer (the mother → son scenario)
  • Transfer confirmation — two-phase settlement: memo-post first, hard-post on confirmation
  • Cancellation — request cancellation of an internal transfer (where supported)
  • Deposits and withdrawals — agent-initiated cash, check, or other transaction entries
  • Holds — place or release temporary holds on funds
  • Card status changes — agent-initiated lock/unlock if the customer reports a lost card (delegates to the Cards endpoints)

Out-of-scope (handled elsewhere):

  • External payments (RTP, FedNow, ACH, Wire) — handled by the Payments Processing workflow
  • Account opening — see Account Opening
  • Identity verification (KYC/CIP) of the caller — performed by the contact-center authentication flow before reaching ORCA
  • Fraud decisioning, dispute case management — separate systems
  • Voice/IVR/CRM application layer — ORCA is the API surface; the contact-center application owns the agent UX

Architecture context

sequenceDiagram
    autonumber
    participant FT as Fintech<br/>(Contact Center App)
    participant ORCA as ORCA API
    participant Core as FI Core

    Note over FT: Customer calls in, agent authenticates them
    FT->>ORCA: GET /persons (lookup by SSN / phone / name)
    ORCA->>Core: Customer read
    Core-->>ORCA: Person data
    ORCA-->>FT: 200 [Persons]

    FT->>ORCA: GET /persons/{personId}/accounts
    ORCA->>Core: Account list
    Core-->>ORCA: Accounts
    ORCA-->>FT: 200 [Accounts]

    FT->>ORCA: GET /accounts/{accountId}/balances
    ORCA->>Core: Balance read
    Core-->>ORCA: Balances
    ORCA-->>FT: 200 [Balance[]]

    FT->>ORCA: GET /accounts/{accountId}/transactions
    ORCA->>Core: Transactions
    Core-->>ORCA: Transactions
    ORCA-->>FT: 200 [Transaction[]]

    Note over FT,Core: Agent confirms intent, initiates mother → son transfer
    FT->>ORCA: POST /internal-transfers (entries[])
    ORCA->>Core: Forward balanced debit/credit
    Core-->>ORCA: transferId, status=Accepted (memo-posted)
    ORCA-->>FT: 202 {transferId, status: "Accepted"}

    opt Confirmation (hard post)
        FT->>ORCA: POST /internal-transfers/{transferId}/confirmation
        ORCA->>Core: Commit to ledger
        Core-->>ORCA: status=Booked
        ORCA-->>FT: 202 {status: "Booked"}
    end

    opt Cancellation (before hard post)
        FT->>ORCA: POST /internal-transfers/{transferId}/cancellation
        ORCA->>Core: Reverse memo post
        Core-->>ORCA: status=Cancelled
        ORCA-->>FT: 202 {status: "Cancelled"}
    end

    opt Other servicing actions
        FT->>ORCA: POST /accounts/{id}/deposits OR /withdrawals
        FT->>ORCA: POST /accounts/{id}/holds
        FT->>ORCA: PATCH /cards/{cardId} (lock lost card)
    end

Endpoints used

Step Method Path Purpose
1 GET /persons Customer lookup
1 GET /persons/{personId} Customer detail (identity verification)
2 GET /persons/{personId}/accounts Accounts owned by the customer
2 GET /accounts/{accountId} Account detail
2 GET /accounts/{accountId}/owners Account owners
2 GET /accounts/{accountId}/parties All parties (owners, beneficiaries, POAs)
3 GET /accounts/{accountId}/balances Balance check
3 GET /accounts/{accountId}/transactions Recent transaction history
4 POST /internal-transfers Initiate intra-bank transfer
4 GET /internal-transfers/{transferId} Retrieve transfer status
4 POST /internal-transfers/{transferId}/confirmation Confirm (hard-post) the transfer
4 POST /internal-transfers/{transferId}/cancellation Cancel the transfer (before hard post)
5 POST /accounts/{accountId}/deposits Agent-initiated deposit
5 POST /accounts/{accountId}/withdrawals Agent-initiated withdrawal
5 POST /accounts/{accountId}/holds Place a hold
5 GET /holds/{holdId} Retrieve hold detail
5 PATCH /holds/{holdId} Update a hold
5 DELETE /holds/{holdId} Release a hold
5 PATCH /cards/{cardId} Lock a lost card (delegates to Cards)

Common headers

Header Purpose
idempotencyId Required for POST and PATCH. Critical for transfers — duplicates are very disruptive.
servicerId Identifies the servicing institution.
servicerBranchId Identifies the agent's branch/center for audit.

Step 1 — Look up the customer

Standard GET /persons with available identifiers. For contact-center use, common search parameters are SSN (or last 4), phone number, and full name.

GET /persons?ssn.last4=6789&lastName.eq=Doe HTTP/1.1

Once located, retrieve full detail:

GET /persons/person-7f3a8c92-4b1e-4d2f-9a0c-1b2c3d4e5f60 HTTP/1.1

Use the response to perform secondary verification questions (last address, date of birth, recent transaction reference, etc.).


Step 2 — List the customer's accounts

GET /persons/person-7f3a8c92-4b1e-4d2f-9a0c-1b2c3d4e5f60/accounts HTTP/1.1

For each account, the agent may need to verify the customer's role:

GET /accounts/acct-mother-001/parties HTTP/1.1

Returns all parties associated with the account (owners, beneficiaries, authorized signers, POAs).


Step 3 — Balance and transaction inquiry

GET /accounts/acct-mother-001/balances HTTP/1.1
[
  { "name": "AvailableBalance", "amount": "3250.45", "currency": "USD", "creditDebitIndicator": "Credit" },
  { "name": "CurrentBalance",   "amount": "3250.45", "currency": "USD", "creditDebitIndicator": "Credit" }
]

Recent transactions:

GET /accounts/acct-mother-001/transactions?creationDate.gte=2026-05-01&limit=10 HTTP/1.1

Step 4 — Internal transfer (mother → son scenario)

The hero scenario: the customer (mother) calls in and asks the agent to transfer $100 from her account to her son's account at the same FI. Both accounts exist; the agent has verified the customer.

POST /internal-transfers202 Accepted with InternalTransfer in the body.

ISO 20022 — internal transfer entries

/internal-transfers operates on ledger entries. Each entry has a creditDebitIndicator:

  • Debit — decreases the account balance (funds leaving the account)
  • Credit — increases the account balance (funds entering the account)

A balanced transfer has at least one Debit and one Credit, and the sum of Debit amounts must equal the sum of Credit amounts.

Key request fields (InternalTransferRequest)

Field Required Description
entries[] yes At least two entries — debit(s) and credit(s) — with matching totals.
externalId no Caller-side identifier (e.g., the CRM ticket ID).
descriptionLines[] recommended Free-text description shown on statements.
transactionCode no Core-defined transaction code.
supplementaryData no Flex fields.

Each entry (InternalTransferEntry)

Field Required Description
account.accountId yes The account being debited or credited.
amount yes Decimal string.
creditDebitIndicator yes Debit or Credit.
description no Per-entry description.

Mother → Son transfer example

The mother's account is debited $100; the son's account is credited $100.

POST /internal-transfers HTTP/1.1
Host: api.portx.io
Content-Type: application/json
idempotencyId: 7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b
servicerId: bank-001
servicerBranchId: cc-austin-01
{
  "entries": [
    {
      "account": { "accountId": "acct-mother-001" },
      "amount": "100.00",
      "creditDebitIndicator": "Debit"
    },
    {
      "account": { "accountId": "acct-son-002" },
      "amount": "100.00",
      "creditDebitIndicator": "Credit"
    }
  ],
  "descriptionLines": [
    "Transfer from Mom"
  ],
  "externalId": "CRM-TICKET-887623"
}
{
  "transferId": "xfer-1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "status": "Accepted",
  "entries": [
    {
      "account": { "accountId": "acct-mother-001" },
      "amount": "100.00",
      "creditDebitIndicator": "Debit"
    },
    {
      "account": { "accountId": "acct-son-002" },
      "amount": "100.00",
      "creditDebitIndicator": "Credit"
    }
  ],
  "settlement": {
    "postingType": "MemoPosted",
    "memopostedIndicator": true,
    "time": "2026-05-19T15:08:22Z"
  },
  "descriptionLines": [
    "Transfer from Mom"
  ]
}

Multi-entry example (split between two of the son's accounts)

A more complex case — the mother sends $100 split between her son's checking ($60) and savings ($40):

{
  "entries": [
    {
      "account": { "accountId": "acct-mother-001" },
      "amount": "100.00",
      "creditDebitIndicator": "Debit"
    },
    {
      "account": { "accountId": "acct-son-checking-002" },
      "amount": "60.00",
      "creditDebitIndicator": "Credit"
    },
    {
      "account": { "accountId": "acct-son-savings-003" },
      "amount": "40.00",
      "creditDebitIndicator": "Credit"
    }
  ],
  "descriptionLines": ["Birthday gift split between checking and savings"]
}

Internal transfer statuses

Status Meaning
Accepted Forwarded to the Core; typically memo-posted.
Pending Awaiting downstream processing.
Booked Hard-posted to the ledger. Terminal success state.
Rejected Validation failure or business-rule rejection. statusReason carries why.
Cancelled Cancellation accepted before hard post.

Two-phase settlement: memo post → hard post

Many Cores use a two-phase settlement model:

  1. Memo post — the transaction is immediately reflected in AvailableBalance but not yet committed to the ledger. The transfer status is Accepted with settlement.postingType: "MemoPosted".
  2. Hard post — the transaction is committed to the ledger. Status becomes Booked with settlement.postingType: "HardPosted".

ORCA exposes this via POST /internal-transfers/{transferId}/confirmation:

POST /internal-transfers/xfer-1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d/confirmation HTTP/1.1
Content-Type: application/json
idempotencyId: 8f9a0b1c-2d3e-4f5a-6b7c-8d9e0f1a2b3c
{}
{
  "transferId": "xfer-1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "status": "Booked",
  "settlement": {
    "postingType": "HardPosted",
    "memopostedIndicator": false,
    "time": "2026-05-19T15:08:48Z"
  }
}

When is confirmation required?

Some Cores hard-post immediately on POST /internal-transfers and do not require a separate confirmation. Others require explicit confirmation, especially for higher-value transfers, transfers that crossed a fraud-screening threshold, or transfers that need supervisor approval. Check the Core's behavior.

Cancellation

POST /internal-transfers/{transferId}/cancellation

{
  "cancellationReason": "Customer changed their mind"
}

Possible only before hard-post. Once Booked, recovery requires a reverse transfer (a new POST /internal-transfers with the debit and credit sides swapped).

Retrieve transfer status

GET /internal-transfers/xfer-1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d HTTP/1.1

Step 5 — Other servicing actions

5.1 Agent-initiated deposit / withdrawal

For cash or check deposits handled by the agent (e.g., remote-deposit support, walk-in branch):

POST /accounts/acct-son-002/deposits HTTP/1.1
{
  "transactionType": "Cash",
  "amount": "20.00"
}

Or a withdrawal:

POST /accounts/acct-son-002/withdrawals HTTP/1.1
{
  "transactionType": "Cash",
  "amount": "20.00"
}

5.2 Place a hold

When a customer reports a check they want to stop, place a StopPayment hold; when funds need to be held pending dispute resolution, use Amount; when a deposited check is being verified, use Check.

POST /accounts/{accountId}/holds202 Accepted with AccountHold in the body.

AccountHoldType values: Amount (any amount, any purpose), Check (holds a check pending clearance), StopPayment (stop a specific check from clearing), Caution (suspicious activity), Payments, PaymentReversal, PaymentAdjustment, PaymentAuthorization.

POST /accounts/acct-mother-001/holds HTTP/1.1
Content-Type: application/json
idempotencyId: 9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d
{
  "holdType": "StopPayment",
  "amount": "500.00",
  "check": {
    "checkNumber": "1024",
    "amount": "500.00"
  },
  "valueDate": "2026-05-19T15:15:00Z",
  "expirationDate": "2026-08-19T00:00:00Z"
}
{
  "holdType": "Amount",
  "amount": "250.00",
  "valueDate": "2026-05-19T15:15:00Z",
  "expirationDate": "2026-05-26T00:00:00Z",
  "codes": [
    { "codeType": "HoldReason", "value": "DisputeInProgress" }
  ]
}
{
  "holdId": "hold-3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
  "accountId": "acct-mother-001",
  "status": "Active",
  "holdType": "StopPayment",
  "amount": "500.00",
  "bookingDate": "2026-05-19T15:15:02Z",
  "expirationDate": "2026-08-19T00:00:00Z"
}

Manage existing holds with GET /holds/{holdId}, PATCH /holds/{holdId} (extend, change amount), and DELETE /holds/{holdId} (release).

5.3 Lock a lost card

If the customer reports a lost card, delegate to the Cards endpoint:

PATCH /cards/card-2e3f4a5b-6c7d-8e9f-0a1b-2c3d4e5f6a78 HTTP/1.1
Content-Type: application/merge-patch+json
{
  "status": "Lost",
  "statusReason": "CardLostOrStolen"
}

See Cards for the full status-change workflow.


Error handling

Status When
400 Bad Request Validation failure — entries[] empty or unbalanced (sum of debits ≠ sum of credits), missing required fields.
404 Not Found accountId references an unknown account.
409 Conflict Idempotency collision.
422 Unprocessable Entity Business-rule rejection — insufficient funds on debited account (where Core blocks overdraft), account frozen, restriction in place.
500 Internal Server Error Unexpected ORCA or Core failure.
502 Bad Gateway Core unreachable.

Edge cases and caveats

Balanced entries required

The sum of Credit entry amounts must equal the sum of Debit entry amounts. The Core rejects unbalanced transfers with 400. Multi-entry transfers (one debit, multiple credits, or vice versa) are valid as long as the totals match.

Available vs current balance

Pre-transfer balance checks should consult AvailableBalance, not CurrentBalance. Holds, pending transactions, and authorization holds reduce available balance even when the current balance shows higher.

Memo-post visibility

Once the transfer is Accepted with MemoPosted, the funds are reflected in both customers' available balances immediately. Customers may see the transfer in their mobile app before the agent gets the confirmation response. Don't tell the customer "I'm working on it" once the 202 is returned — it's already visible.

Idempotency for agents

Agent workflows often involve double-clicks, accidental refreshes, and network blips that retry the request. Always include idempotencyId — a UUID per agent intent — to prevent the customer from being charged twice. The contact-center app should generate a stable idempotency key per "transfer intent" (e.g., one ticket = one key), not per HTTP request.

Internal transfers are ledger-level

The spec describes /internal-transfers as moving funds "between ledger accounts, or between a holding account and a final account." The mother→son scenario is a valid usage — both accountIds are customer-facing — but the same endpoint is used for general-ledger plumbing inside the Core too. The customer-facing semantics depend entirely on the accountId values you pass.

Cancellation window

Cancellation is only possible while the transfer is in Accepted (memo-posted) or Pending state. Once Booked, the only remedy is a reverse transfer. Some Cores hard-post immediately and never expose a memo-post window — in that case, cancellation always fails with 422.

External transfers are not handled here

/internal-transfers is for intra-bank transfers only — both accounts at the same servicer. To send money to an external account (different bank), use Payments Processing with POST /credit-transfers.

Related building blocks

  • Account Opening — Customer and account creation prerequisites. See Account Opening.
  • Payments Processing — External transfers (RTP, FedNow, ACH, Wire). See Payments Processing.
  • Cards — Card lock/unlock flow when a customer reports a lost card. See Cards.