# ACH Local Payment Initiates a domestic same-currency bank transfer over the ACH clearing network. The request carries debtor account, instructed amount, debit currency, creditor account + bank reference, and 1–2 lines of remittance information. JSON and CSV response formats are supported via the Accept header. ### Supported Currencies Only USD and BMD are accepted. debitCurrency and instructedAmount.currency must be identical - cross-currency is not supported on this endpoint. ### Settlement and Cut-Off Transfers settle same-day when submitted before 3:15 PM local AST; later submissions are queued for the next business day. The value/effective date in the response reflects the settlement date. ### Creditor Bank Reference Provide creditorBank with the destination bank bankCode and currency. The API validates this combination against the configured bank reference lookup. If the bankCode and currency combination is not found, the API returns 400 Bad Request with error code BANK_REFERENCE_NOT_FOUND. The ACH local bank reference lookup contains the following values: | Bank Code | Currency | Bank Name | |-----------|----------|-----------| | BUTTERFIELD | BMD | Bank of N.T. Butterfield & Sons | | HSBC | BMD | HSBC Bermuda | | CLARIEN | BMD | Clarien Bank Bermuda | | BUTTERFIELD | USD | Bank of N.T. Butterfield & Sons | | HSBC | USD | HSBC Bermuda | | CLARIEN | USD | Clarien Bank Bermuda | ### Request basics - Endpoint: POST https://api.bcb.bm/v1/payments/ach-local - Authorization: Bearer {token} (required). - Accept: application/json (default) or text/csv. - Idempotency-Key: optional UUID v4 header (≤ 255 chars). See below. ### Idempotency The API supports idempotency through the optional Idempotency-Key header. When supplied, the server caches the successful response and replays it on repeat calls. - If no idempotency key is provided, the request is processed normally (duplicates are possible). - If a valid UUID key is supplied, the system stores the response of the first 2xx outcome and replays it on subsequent calls with the same key. - TTL (cache lifetime): the stored response is retained for a limited window (currently on the order of one hour). After expiry the same key is treated as a new request - but instructionIdentification deduplication still applies for 24 hours. Plan client-side retries to land inside the cache window. - Scope: keys are partitioned by client/user, HTTP method, and request path. The same key on /payments/ach-local and /payments/swift is not shared - each endpoint has its own cache slot. - Concurrency: if a second request with the same key arrives while the first is still in flight, the second is rejected with 409 Conflict and message "Request is already being processed." Wait and retry. - Body mismatch: request bodies are not compared on replay. If you reuse a key with a different body, the original cached response is returned - the new body is ignored. Mint a fresh key for any logically different request. - Non-2xx responses are not cached. A 4xx validation failure or 5xx server error can be retried with the same key and will execute again. ### Deduplication A separate, business-level guard on the instructionIdentification field, applied independently of the Idempotency-Key header: - Reusing an instructionIdentification for an active ACH local payment is rejected with 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID. - Use a fresh instructionIdentification for each new payment intent. Reuse it (with the same Idempotency-Key) only when retrying the exact same payment. - The deduplication window is currently configured as 24 hours from the first accepted request. ### How Idempotency-Key and instructionIdentification combine They are independent mechanisms with different purposes. - Idempotency-Key (header) is a transport-level retry token. It lets you safely retry the same HTTP call and get the original response replayed. - instructionIdentification (body) is the business identifier of the payment. It is persisted on the payment record and forwarded downstream. To prevent the same payment intent from being submitted twice, a value that is already in flight or recently accepted is rejected with 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID (window: 24 hours). | Idempotency-Key | instructionIdentification | Behaviour | |---|---|---| | no | no | No protection. Each call creates a new payment. | | yes | no | HTTP retries of the same call replay the original response. A new Idempotency-Key creates a new payment. | | no | yes | First call is processed normally. Reusing the same business identifier within 24 hours is rejected with 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID. The original success response is not replayed - record it client-side. | | yes | yes | Recommended. HTTP retries of the same call replay the original response. A new Idempotency-Key reusing an in-flight instructionIdentification is rejected with 409. | ### Resilience: classifying responses The dedupe and idempotency layers together cover three distinct failure modes. The status code tells you what to do next: - 503 DEDUPE_LOCK_UNAVAILABLE - the duplicate-check store could not acquire its lock for instructionIdentification. The payment was not submitted to core banking. Safe to retry after a short backoff with the same instructionIdentification (and the same Idempotency-Key, if you used one). This is not "a duplicate was detected" - it is "we could not verify uniqueness right now." - 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID - this instructionIdentification was already accepted within the 24-hour window. Do not retry the POST; query payment status to reconcile. - 5xx, connection timeout, or no response - outcome unknown. Retry with the same Idempotency-Key and same instructionIdentification. The server will either replay the original 201 (if the first attempt landed) or return 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID (also indicating the first attempt landed). Decision tree in pseudocode: javascript async function submitAchLocalPayment(paymentRequest, idempotencyKey) { for (let attempt = 1; attempt keep BOTH values stable; the server will replay or short-circuit safely retry(intent); // After 409 ACH_LOCAL_DUPLICATE_INSTRUCTION_ID // -> do NOT POST again; query status to reconcile await getPaymentStatus(intent.instructionIdentification); ### Idempotency-Key lifecycle (client-side checklist) - Mint the key before the HTTP call and persist it alongside the payment intent so a process restart or retry can still find it. - Reuse it for every retry of the same intent; treat it as "spent" only after a terminal (2xx or non-retryable 4xx) response. - Plan retries to land inside the cache window (~1 hour). Beyond that, instructionIdentification (24 h) is your remaining guard. ### Validation Rules | Field | Constraint | |-------|-----------| | instructionIdentification | Optional, max 16 characters | | debtorAccount.identification | Required, max 36 characters | | debitCurrency | Required, exactly 3 characters, USD or BMD only | | instructedAmount.amount | Required, max 18 characters, valid decimal (up to 2 decimal places) | | instructedAmount.currency | Required, exactly 3 characters, USD or BMD only, must equal debitCurrency | | creditorAccount.identification | Required, max 17 characters | | creditorAccount.name | Required, max 22 characters | | creditorBank.bankCode | Required, 1-100 characters (see Creditor Bank Reference below) | | creditorBank.currency | Required, exactly 3 characters, must match debitCurrency | | remittanceInformation | Required, 1–2 non-empty lines, each max 35 characters | ### Minimal Request Example javascript const res = await fetch('https://api.bcb.bm/v1/payments/ach-local', { method: 'POST', headers: { 'Authorization': Bearer ${token}, 'Content-Type': 'application/json', 'Accept': 'application/json', 'Idempotency-Key': idempotencyKey, // UUID v4, per payment intent }, body: JSON.stringify(paymentRequest), // shape: see request schema below }); The full request and response schemas, including field types and a worked example, are rendered below from the API contract. Required Permission: payment-ach This endpoint requires the permission claim payment-ach to be present in the JWT token. These permissions are embedded in the token during the authentication process and cannot be modified afterward. The token must be obtained with the appropriate permissions to access this endpoint. Endpoint: POST /v1/payments/ach-local Version: v1 Security: Authorization, Feature Permissions, Authorization ## Request fields (application/json): - `instructionIdentification` (string,null) Unique identification as assigned by an instructing party for an instructed party to unambiguously identify the instruction. Max 16 characters. - `debtorAccount` (object, required) Unambiguous identification of the account of the debtor to which a debit entry will be made as a result of the transaction. - `debtorAccount.identification` (string, required) Unique identification of the account, such as an Bank Account Number or a local account number. - `debitCurrency` (string, required) ISO 4217 currency code for the debit side of the transfer. Must be USD or BMD and must match InstructedAmount currency. - `instructedAmount` (object, required) Amount of money to be moved between the debtor and creditor, expressed in the currency as ordered by the initiating party. The currency must be USD or BMD and must match DebitCurrency. - `instructedAmount.currency` (string, required) Currency ISO code - `instructedAmount.amount` (string, required) Amount - `creditorAccount` (any, required) Identification and name of the creditor (beneficiary) account. Identification max 17 characters; name max 22 characters. - `creditorBank` (object, required) Bank reference details used to resolve the creditor bank for the ACH transfer. Includes the bank code and the 3-letter currency code for the destination bank. The currency must be USD or BMD and must match DebitCurrency. - `creditorBank.bankCode` (string, required) Bank code used to identify the destination bank in the bank reference lookup. - `creditorBank.currency` (string, required) Three-letter ISO currency code used together with BankCode to resolve the destination bank reference. For ACH local transfers, only USD and BMD are supported. - `remittanceInformation` (array, required) Information supplied to enable the matching/reconciliation of an entry with the items that the payment is intended to settle. Required: 1 to 2 lines; each line max 35 characters. ## Response 201 fields (application/json): - `id` (string, required) Unique identifier assigned by the bank for the SWIFT transfer. This ID can be used for future reference and tracking of the payment. - `status` (string, required) The current status of the payment processing. Possible values include: - success: Payment has been accepted for processing - failed: Payment failed - `transactionStatus` (string, required) Detailed transaction status indicating the current state of the transfer. This provides more granular status information than the main status field. - `uniqueIdentifier` (string, required) A unique identifier specific to this instance of the SWIFT transfer. This identifier remains constant throughout the lifecycle of the payment and can be used for reconciliation purposes. - `details` (object, required) Detailed information about the payment transaction including amounts, accounts, and settlement information. - `details.extReference` (string, required) External reference number assigned by the bank's payment processing system. This reference can be used for tracking and reconciliation purposes. - `details.instructedAmount` (object, required) The amount and currency of the payment as instructed by the payment initiator. This represents the original payment amount before any charges or conversions. - `details.instructedAmount.currency` (string, required) Currency ISO code - `details.instructedAmount.amount` (string, required) Amount - `details.debtorAccount` (object, required) Reference information for the account from which the payment is debited. Includes the account identifier and scheme information. - `details.debtorAccount.schemeName` (string,null) The name of the identification scheme, e.g. "BBAN", "AccountNumber". - `details.debtorAccount.identification` (string, required) The identifier within that scheme (e.g. account number). - `details.debtorAccount.Name` (string, required) The name of the account, if available (optional). - `details.beneficiaryAccount` (object, required) Reference information for the beneficiary's account. This may differ from the creditor account in certain scenarios. - `details.creditorAccount` (object, required) Reference information for the account to be credited with the payment. This represents the immediate recipient account of the funds. - `details.settlementDetails` (object, required) Details about the settlement of the payment, including credited and debited amounts, value dates, and settlement status. - `details.settlementDetails.amountCredited` (object, required) The amount that was credited to the beneficiary account. This may differ from the instructed amount due to currency conversion or intermediary bank charges. - `details.settlementDetails.amountDebited` (object, required) The total amount debited from the sender's account. This includes the payment amount plus any applicable charges. - `details.settlementDetails.valueDate` (string, required) The date on which the payment is settled between banks. This is the date when the funds become available to the beneficiary. Format: ISO 8601 date string (YYYY-MM-DD) - `details.settlementDetails.recordStatus` (string, required) The current status of the settlement record. - `details.chargeDetails` (object, required) Information about the charges applied to the payment, including charge type, amounts, and the account to which charges are applied. - `details.chargeDetails.chargesType` (string, required) Specifies how the charges for the payment are allocated. Possible values: - OUR: All charges are paid by the sender - BEN: All charges are paid by the beneficiary - SHA: Charges are shared between sender and beneficiary - `details.chargeDetails.chargeAmount` (object, required) The total amount of charges applied to the payment. Includes both sending and receiving bank charges. - `details.chargeDetails.chargeAccount` (object, required) Reference information for the account from which charges are collected. This may be different from the main debit account. - `details.chargeDetails.chargeAnalysis` (object, required) Detailed breakdown of how charges are distributed between the sending and receiving parties. - `details.chargeDetails.chargeAnalysis.sender` (object, required) The amount of charges applied by and payable to the sending bank. This may include processing fees, wire transfer fees, or other sending bank charges. - `details.chargeDetails.chargeAnalysis.receiver` (object, required) The amount of charges applied by and payable to the receiving bank. This may include processing fees, incoming wire fees, or other receiving bank charges. - `details.remittanceInformation` (array,null) Collection of unstructured information provided with the payment to help with reconciliation and payment identification. - `details.remittanceInformation.unstructured` (string, required) The narrative text (e.g. remittance line, beneficiary note, etc.). - `details.beneficiary` (any, required) Detailed information about the ultimate beneficiary of the payment. Includes identification, name, and additional information. - `details.beneficiaryAgent` (any, required) Information about the beneficiary's bank or financial institution. Typically, includes the BIC/SWIFT code and bank details. - `details.intermediaryAgent` (any, required) Details of any intermediary bank involved in the payment chain. This is optional and only present for payments requiring intermediary processing. - `linkedActivities` (array, required) Collection of related financial activities associated with this payment. This may include charges, forex conversions, or other related transactions. - `linkedActivities.id` (string, required) The unique identifier for the activity. - `linkedActivities.transactionStatus` (string, required) The current processing status of the transaction. - `linkedActivities.status` (string, required) The general status of the activity. - `linkedActivities.uniqueIdentifier` (string,null) A unique identifier specific to this instance of the activity. - `linkedActivities.activityDetails` (object, required) Detailed information about the activity. - `linkedActivities.activityDetails.arrangementId` (string, required) Unique identifier for the arrangement associated with this activity. - `linkedActivities.activityDetails.activityId` (string, required) Identifier for the type of activity. - `linkedActivities.activityDetails.productId` (string, required) Product identifier related to the activity. - `linkedActivities.activityDetails.currencyId` (string, required) Currency code for the activity. - `linkedActivities.activityDetails.effectiveDate` (string, required) Date when the activity becomes effective. ## Response 400 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 401 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 403 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 409 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 429 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 500 fields (application/json): - `error` (string, required) - `message` (string, required) ## Response 503 fields (application/json): - `error` (string, required) - `message` (string, required)