This is v2.2-draft — a working draft of the v2.2 standards, published for review and not yet ratified. It MUST NOT be used as the basis for a production implementation. For the current standards, switch to v2.1 using the version selector. See the v2.1 → v2.2-draft changelog for every change in this version.
Confirmation of Payee — API Guide 2 min read
Confirmation of Payee (CoP) lets a TPP verify that an IBAN belongs to the named individual or business before initiating a payment.
End-to-end Confirmation of Payee
Return the name(s) held against an IBAN
/customers/action/cop-queryBoth bodies changed in v2.2. The request no longer carries data.account.name — drop it from your parser, and stop reading it if you used it to select which name to return. The response is flattened: replace data[].verifiedClaims[].verification and data[].verifiedClaims[].claims with a single data[].name, mapping claims.fullName to name.fullName, givenName to firstName, familyName to lastName, and organisationClaims.name to name.businessName. Everything else the envelope carried — trust framework, assurance evidence, and the wider customer fields such as emiratesId, birthDate and salary — is removed and MUST NOT be sent.
Headers, query parameters, status codes and error codes are unchanged, so this is a rewrite of your request parser and response builder rather than a new integration.
Request headers
| Header | Required | Description |
|---|---|---|
o3-provider-id | Yes | Identifier for your LFI registered in the Hub |
o3-aspsp-id | Yes (deprecated) | Deprecated alias for o3-provider-id. Will be removed in a future version — use o3-provider-id |
o3-caller-org-id | Yes | Organisation ID of the TPP making the underlying request |
o3-caller-client-id | Yes | OIDC client ID of the TPP application |
o3-caller-software-statement-id | Yes | Software statement ID of the TPP application |
o3-api-uri | Yes | The parameterised URL of the API being called by the TPP |
o3-api-operation | Yes | The HTTP method of the operation carried out by the TPP (e.g. POST) |
o3-ozone-interaction-id | Yes | Hub-generated interaction ID. Equals o3-caller-interaction-id if the TPP provided one |
o3-caller-interaction-id | No | Interaction ID passed in by the TPP, if present |
Query parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
page | Yes | 1 | Page number for paginated results |
page-size | Yes | 100 | Number of records per page |
Request body
Content-Type: application/json
The Hub sends a plain JSON body — not a JWS. The body identifies a single account by IBAN, and carries nothing else. The name the TPP submitted is not sent to you: look the account up by IBAN and return the holders you have, and the Hub does the comparing.
data.account
| Field | Type | Required | Description |
|---|---|---|---|
schemeName | string | Yes | Always IBAN |
identification | string | Yes | The IBAN to look up |
Example
{
"data": {
"account": {
"schemeName": "IBAN",
"identification": "AE070331234567890123456"
}
}
}
Response
Content-Type: application/json
Return 200 in all lookup scenarios — whether the account is found or not. The Hub interprets the data array contents to determine the match result returned to the TPP.
200 — Account found
Return a data array containing one entry per account holder. id and name are the only required members of an entry.
Personal account
name.fullName is mandatory and is the value the Hub matches on today. Every other field is optional — supply firstName, middleName, lastName, fullNameAr and alsoKnownAs where your systems hold them separately, so that improvements to the matching algorithm can use them without a further change to this contract. A response carrying only fullName is fully conformant.
{
"data": [
{
"id": "cust-001",
"name": {
"fullName": "Ahmed Al Mansouri",
"firstName": "Ahmed",
"lastName": "Al Mansouri",
"fullNameAr": "أحمد المنصوري"
}
}
],
"meta": {
"totalPages": 1,
"totalRecords": 1
}
}
Personal account — minimum conformant response
{
"data": [
{
"id": "cust-001",
"name": {
"fullName": "Ahmed Al Mansouri"
}
}
],
"meta": {
"totalPages": 1,
"totalRecords": 1
}
}
Joint account
Return one entry per holder. The Hub evaluates every entry in data, so the order is not significant — do not attempt to place the most likely match first.
{
"data": [
{
"id": "cust-001",
"name": {
"fullName": "Ahmed Al Mansouri",
"firstName": "Ahmed",
"lastName": "Al Mansouri"
}
},
{
"id": "cust-002",
"name": {
"fullName": "Fatima Al Mansouri",
"firstName": "Fatima",
"lastName": "Al Mansouri"
}
}
],
"meta": {
"totalPages": 1,
"totalRecords": 2
}
}
Business account
Populate name.businessName with the registered business name on the account. businessNameAr and alsoKnownAs are optional.
{
"data": [
{
"id": "cust-003",
"name": {
"businessName": "Al Mansouri Trading LLC",
"alsoKnownAs": ["Al Mansouri Trading"]
}
}
],
"meta": {
"totalPages": 1,
"totalRecords": 1
}
}
200 — Account not found, opted out
Return 200 with an empty data array where no account was found matching the IBAN, the account is under a bar, or the customer has opted out of CoP. Do not use 204, 404, 201 or 202 — the Hub expects 200 and treats an empty array as a no-result response.
The three cases are deliberately indistinguishable to the TPP, so that a CoP query cannot be used to probe for the existence of an account.
{
"data": [],
"meta": {
"totalPages": 0,
"totalRecords": 0
}
}
Error responses
All error bodies must include errorCode and errorMessage.
403 — Forbidden
errorCode | errorMessage | When to use |
|---|---|---|
Consent.AccountTemporarilyBlocked | The account is blocked from receiving payments. | The account is blocked from receiving payments for a temporary reason — e.g. account status is Suspended |
Consent.PermanentAccountAccessFailure | The account is blocked from receiving payments. | The account is blocked from receiving payments permanently — e.g. account status is Closed, Deceased, or Unclaimed |
