POST /payments
Create a payment session
AJV-strict validation (additionalProperties: false — unknown fields are 400s; localInstrument is not an input: the rail is derived from currency). The pricing snapshot, certMode and the resolved brand's branding are stamped at create; a later rebrand never alters an in-flight journey.
Licence gates (LIVE credentials only). Answered before the body is read, in this order: 403 MVP-4301 (live payments not enabled for the account) → 403 MVP-4307 (the account holds its own licence, so live payments run only under the Client Licence, BYO_CERT, which is not active on it) → 403 MVP-4306 (live payments under the MVP Payments Licence, MVP_CERT, are not available yet). Sandbox credentials never meet MVP-4306 or MVP-4307.
Parameters (3)
| Name | In | Required | Type | Description |
|---|---|---|---|---|
x-mvp-client-id | header | yes | string | |
x-mvp-client-secret | header | yes | string | |
Idempotency-Key | header | no | string | 24-hour dedupe window, unique per client per environment (the environment of the credential that signs the call, sandbox or live) — the same key under a sandbox and a live credential names two different requests and never replays across. Replays return the original result; a different body under the same key in the same environment is a 409. |
Request body — CreatePaymentRequest
| Field | Type | Required | Description |
|---|---|---|---|
amount | string · pattern ^(0|[1-9]\d{0,9})\.\d{2}$ | yes | Decimal string, two decimals ("12.02"). Per-rail ceiling: GBP is capped at 1000000.00 (the Faster Payments scheme value limit) — above it is 400 MVP-4013, path /amount. EUR/SEK/RON carry no platform rail ceiling. A bank's own per-payment cap is separate and independent, and is enforced at bank selection. |
currency | Currency | yes | See Currency |
beneficiary | object | object | yes | Exactly one account form, decided by currency. EUR/SEK/RON require iban and reject sortCode/accountNumber; GBP requires sortCode+accountNumber and rejects iban — including a GB IBAN. A mismatch is 400 MVP-4011 with the offending field in errors[].path. Nothing is derived between the two forms in either direction. |
beneficiary.name | string · ≤70 chars | yes | |
beneficiary.iban | string | no | Creditor IBAN (mod-97 validated). EUR/SEK/RON only. |
beneficiary.sortCode | string · pattern ^[0-9]{6}$ | no | UK sort code, exactly 6 digits. GBP only. |
beneficiary.accountNumber | string · pattern ^[0-9]{8}$ | no | UK account number, exactly 8 digits (leading zeros are significant). GBP only. |
beneficiary.bic | string | no | Optional creditor agent |
beneficiary.address | object | no | |
beneficiary.address.line1 | string | no | |
beneficiary.address.city | string | no | |
beneficiary.address.postcode | string | no | |
beneficiary.address.country | string · ≤2 chars | no | |
endToEndId | string · ≤35 chars | no | ISO 20022 EndToEndIdentification, SEPA charset; defaults to the mvpId. Per-rail ceiling: 35 for EUR/SEK/RON, 31 for GBP (the Faster Payments scheme carries 31). Over the GBP ceiling is 400 MVP-4012; over 35 on EUR/SEK/RON is 400 MVP-4000, unchanged. |
remittanceInformationUnstructured | string · ≤140 chars | no | SEPA charset, ≤140 on every rail. At most one remittance form; both omitted → defaults to the mvpId. |
remittanceInformationStructured | object | no | |
remittanceInformationStructured.reference | string | yes | ISO 11649 RF reference (mod-97 validated when RF) or domestic scheme reference. Per-rail ceiling: 35 for EUR/SEK/RON, 18 for GBP (the Faster Payments reference field). Over the GBP ceiling is 400 MVP-4012. |
remittanceInformationStructured.referenceType | string · default "SCOR" | no | |
remittanceInformationStructured.referenceIssuer | string | no | |
callbackUrl | string · pattern ^https:// · ≤2048 chars | yes | Required, https-only, at most 2048 characters. No fallback URL of any kind. Where the hosted journey sends the payer when the session finishes. The released redirect is this URL with one query parameter appended: mvpid, the payment's id from the create response. Your own query string and fragment come back byte for byte, and a mvpid you set yourself is left untouched. Treat it as a lookup key, never as the outcome — it travels through the payer's browser, which may be a different device from the one that started the payment (a desktop QR journey returns on the phone), and anyone can send a browser to your return page with any value on it. Look the id up in the payments you created and ignore ids you have no record of; having a record is not authorisation either — the id is an identifier the payer can see and share, not a secret — so show a returning browser the outcome and a reference, and order detail only to a session that already owns the order. Read the outcome from GET /v1/payments/{mvpId} with your credentials, or from the signed webhook. A response without the full-tier fields (endToEndId, callbackUrl, timestamps) means the payment is not yours OR your credentials were not accepted — the two are deliberately indistinguishable and both answer 200 — so log it and check your credentials before assuming the former. The released URL can exceed the 2048-character limit by the appended pair. |
paymentPath | enum: HOSTED | API_ONLY · default "HOSTED" | no | |
debtorIban | string | no | Optional pre-fill — avoids consumer account entry. EUR/SEK/RON only. |
debtorSortCode | string · pattern ^[0-9]{6}$ | no | Optional debtor pre-fill, GBP only; supply with debtorAccountNumber. |
debtorAccountNumber | string · pattern ^[0-9]{8}$ | no | Optional debtor pre-fill, GBP only; supply with debtorSortCode. |
consumerCountry | string · ≤2 chars | no | Optional bank-picker pre-filter |
brandName | string | no | Selects one of the client's trading brands; omitted → default brand; unknown → 400 |
bankId | string · ≤64 chars · ≥1 chars | no | A bank the client's own picker already chose, for the hosted page to pre-select (the payer still confirms it and may choose another). Checked against the registry exactly as select-bank checks a selection: unknown or not a bank for this payment → 400 MVP-4005; above the bank's cap → 400 MVP-4013. Never the selection itself — the bank is chosen at select-bank. |
branchId | string · ≤64 chars · ≥1 chars | no | With bankId, the member bank to pre-select for a bank whose branches is not null. Must be one the bank offers this payment (else 400 MVP-4015); without bankId it is 400 MVP-4000 at /branchId. The bank may be named alone and the branch left to the payer. |
Responses
| Status | Schema | Description |
|---|---|---|
201 | CreatePaymentResponse | Payment session created |
400 | ErrorEnvelope | Validation failed (envelope + AJV details) |
401 | ErrorEnvelope | Missing or invalid credentials |
403 | ErrorEnvelope | Client disabled (MVP-4302) or live not enabled (MVP-4301); on a
LIVE POST /v1/payments, also a licence refusal: MVP-4307 — the
account holds its own licence, so live payments run only under the
Client Licence (BYO_CERT), which is not active on it — or
MVP-4306 — live payments under the MVP Payments Licence (MVP_CERT)
are not available yet. Sandbox never answers MVP-4306 or MVP-4307.
|
409 | ErrorEnvelope | Idempotency conflict or invalid status transition |
429 | ErrorEnvelope | Too many requests |