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-transfers → 202 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:
- Memo post — the transaction is immediately reflected in
AvailableBalancebut not yet committed to the ledger. The transfer status isAcceptedwithsettlement.postingType: "MemoPosted". - Hard post — the transaction is committed to the ledger. Status becomes
Bookedwithsettlement.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}/holds → 202 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.