Z Zise Developers 简体中文

Create or link a payee: local creation does not call the upstream

POST /v1/remit/payees scope: remittances:write
On behalf of a member · x-on-behalf-of required Requires x-idempotency-key

A payee is a provider-independent entity. This call only writes our database and invokes no upstream endpoint. Choosing a provider at creation would pin a record that should be reusable across providers to whichever is enabled today. Upstream onboarding occurs at order dispatch.

Two consequences affect your product design: ① Successful local creation does not guarantee the payee can receive a remittance. We validate formats as far as possible, using pattern from GET /v1/remit/payee-form-schema, but the upstream has additional criteria. ② Local creation is fast, inexpensive, and can be offered whenever users need it.

⚠ The cost of ① reaches your user late. Upstream rejection can concern duplicates, sanctions matches, or a bank absent from its directory, not just formatting. It is discovered at order dispatch, after funds have moved from the member’s available balance into the locked bucket. The order remains processing pending manual intervention. Call POST /v1/remit/payees/{id}/verify after creating the payee to discover that failure before any money is involved. Verification moves no funds.

Prerequisites

  • Corridor available, revalidated using the same criteria as GET /v1/remit/payee-form-schema
  • At least one identifier—account number / IBAN / proxy—must be nonempty
FieldTypeRequiredDescription
x-on-behalf-of string Required Member on whose behalf the call is made. The payee association belongs to this member.
x-idempotency-key string Required UUID v4. The same key and body replay the initial result, including errors, within 24 hours.

Request Body

FieldTypeRequiredDescription
country_code string Required Country of the receiving bank, two uppercase letters. Alias: country.
currency string Required Payout currency, three uppercase letters.
payment_method string Required LOCAL or SWIFT. Alias: method.
clearing_system string Required Clearing network, preserving case exactly. Alias: clearing.
entity_type "INDIVIDUAL" | "COMPANY" Required Alias: entity.
nickname string Optional The member’s nickname for this payee. Private to this association and invisible to other members. Truncated beyond 64 characters.
fields object Required Values for the field contract’s key entries, in one flat level: dotted keys, not nested objects. Keys outside the contract are discarded.{"bank_details.iban": "GB33BUKB20201555555555", …}

Response

201Payee created or linked. All three outcome values return 201. linked / review are not errors; they mean we recognized an existing account.
{
  "id": "pye_1042",
  "outcome": "created",
  "status": "active"
}
400invalid_fields: per-field validation failed, with a fields array. Each entry is {key, reason}, where reason is required / too_long / pattern / not_in_enum / iban_length / iban_checksum / account_identifier_required / unknown_clearing_system. A generic “incorrect information” message is unhelpful on a form with many fields. corridor_not_supported: incomplete four-part corridor, disabled corridor, restricted jurisdiction, or no determinable routing-code type.
409idempotency_key_reused: the same key was reused with another request body or at another endpoint.

Additional Details

Three outcomes: inspect outcome

  • created: a new payee.
  • linked: the account already exists with the same holder name; linked rather than duplicated.
  • review: the account already exists, but the holder name differs. Linking still succeeds with 201, and name_mismatch is stored. Ask the user to review the holder name in your UI.

The criterion is the payee fingerprint, derived from the four corridor components plus account identifiers: account number / IBAN / routing code / proxy. The holder name uses a secondary fingerprint. Entering the same account twice does not create two entities; changing the name for that account is detected.

Three rules for fields

① Keys outside the contract are discarded without error and are not stored. Do not place your business fields here. ② Values are limited to 256 characters; longer values are truncated, not rejected. ③ We normalize before validating. Fields marked alnum_upper lose hyphens and spaces and become uppercase; accounts copied from bank Apps often include separators that upstream patterns disallow. We separately retain the original user input for dispute evidence.

⚠ Beyond pattern, IBAN validation includes country-specific length and mod-97 check digits. The error reason distinguishes iban_length / iban_checksum, so users know whether to count digits or check the original document. This is necessary: an IBAN one character short that passes locally may only be rejected at dispatch, when the upstream payee has already entered a terminal state.

Request
curl -X POST 'https://api.zinfra.vip/v1/remit/payees' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "country_code": "GB",
    "currency": "GBP",
    "payment_method": "LOCAL",
    "clearing_system": "FASTER PAYMENTS",
    "entity_type": "INDIVIDUAL",
    "nickname": "房东",
    "fields": {
      "first_name": "MARY",
      "last_name": "SMITH",
      "bank_details.account_holder": "MARY SMITH",
      "bank_details.bank_name": "Barclays",
      "bank_details.bank_address": "1 Churchill Place, London",
      "bank_details.swift_code": "BUKBGB22",
      "bank_details.iban": "GB33BUKB20201555555555",
      "bank_details.routing_code_value1": "202015",
      "bank_details.routing_code_type1": "sort_code",
      "address.country": "GB",
      "address.state": "Greater London",
      "address.city": "London",
      "address.street_address": "221B Baker Street",
      "address.postal_code": "NW1 6XE"
    }
  }'
const res = await fetch("https://api.zinfra.vip/v1/remit/payees", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
    "x-on-behalf-of": "$MEMBER_ID",
    "x-idempotency-key": "$IDEMPOTENCY_KEY",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "country_code": "GB",
    "currency": "GBP",
    "payment_method": "LOCAL",
    "clearing_system": "FASTER PAYMENTS",
    "entity_type": "INDIVIDUAL",
    "nickname": "房东",
    "fields": {
      "first_name": "MARY",
      "last_name": "SMITH",
      "bank_details.account_holder": "MARY SMITH",
      "bank_details.bank_name": "Barclays",
      "bank_details.bank_address": "1 Churchill Place, London",
      "bank_details.swift_code": "BUKBGB22",
      "bank_details.iban": "GB33BUKB20201555555555",
      "bank_details.routing_code_value1": "202015",
      "bank_details.routing_code_type1": "sort_code",
      "address.country": "GB",
      "address.state": "Greater London",
      "address.city": "London",
      "address.street_address": "221B Baker Street",
      "address.postal_code": "NW1 6XE"
    }
  }),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/remit/payees",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-on-behalf-of": "$MEMBER_ID",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "country_code": "GB",
      "currency": "GBP",
      "payment_method": "LOCAL",
      "clearing_system": "FASTER PAYMENTS",
      "entity_type": "INDIVIDUAL",
      "nickname": "房东",
      "fields": {
        "first_name": "MARY",
        "last_name": "SMITH",
        "bank_details.account_holder": "MARY SMITH",
        "bank_details.bank_name": "Barclays",
        "bank_details.bank_address": "1 Churchill Place, London",
        "bank_details.swift_code": "BUKBGB22",
        "bank_details.iban": "GB33BUKB20201555555555",
        "bank_details.routing_code_value1": "202015",
        "bank_details.routing_code_type1": "sort_code",
        "address.country": "GB",
        "address.state": "Greater London",
        "address.city": "London",
        "address.street_address": "221B Baker Street",
        "address.postal_code": "NW1 6XE"
      }
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/remit/payees",
    strings.NewReader(`{
  "country_code": "GB",
  "currency": "GBP",
  "payment_method": "LOCAL",
  "clearing_system": "FASTER PAYMENTS",
  "entity_type": "INDIVIDUAL",
  "nickname": "房东",
  "fields": {
    "first_name": "MARY",
    "last_name": "SMITH",
    "bank_details.account_holder": "MARY SMITH",
    "bank_details.bank_name": "Barclays",
    "bank_details.bank_address": "1 Churchill Place, London",
    "bank_details.swift_code": "BUKBGB22",
    "bank_details.iban": "GB33BUKB20201555555555",
    "bank_details.routing_code_value1": "202015",
    "bank_details.routing_code_type1": "sort_code",
    "address.country": "GB",
    "address.state": "Greater London",
    "address.city": "London",
    "address.street_address": "221B Baker Street",
    "address.postal_code": "NW1 6XE"
  }
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// Decode amount fields as string, not float64.
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zinfra.vip/v1/remit/payees"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-on-behalf-of", "$MEMBER_ID")
    .header("x-idempotency-key", "$IDEMPOTENCY_KEY")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "country_code": "GB",
  "currency": "GBP",
  "payment_method": "LOCAL",
  "clearing_system": "FASTER PAYMENTS",
  "entity_type": "INDIVIDUAL",
  "nickname": "房东",
  "fields": {
    "first_name": "MARY",
    "last_name": "SMITH",
    "bank_details.account_holder": "MARY SMITH",
    "bank_details.bank_name": "Barclays",
    "bank_details.bank_address": "1 Churchill Place, London",
    "bank_details.swift_code": "BUKBGB22",
    "bank_details.iban": "GB33BUKB20201555555555",
    "bank_details.routing_code_value1": "202015",
    "bank_details.routing_code_type1": "sort_code",
    "address.country": "GB",
    "address.state": "Greater London",
    "address.city": "London",
    "address.street_address": "221B Baker Street",
    "address.postal_code": "NW1 6XE"
  }
}
"""))
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/remit/payees');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-on-behalf-of: $MEMBER_ID',
    'x-idempotency-key: $IDEMPOTENCY_KEY',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "country_code": "GB",
  "currency": "GBP",
  "payment_method": "LOCAL",
  "clearing_system": "FASTER PAYMENTS",
  "entity_type": "INDIVIDUAL",
  "nickname": "房东",
  "fields": {
    "first_name": "MARY",
    "last_name": "SMITH",
    "bank_details.account_holder": "MARY SMITH",
    "bank_details.bank_name": "Barclays",
    "bank_details.bank_address": "1 Churchill Place, London",
    "bank_details.swift_code": "BUKBGB22",
    "bank_details.iban": "GB33BUKB20201555555555",
    "bank_details.routing_code_value1": "202015",
    "bank_details.routing_code_type1": "sort_code",
    "address.country": "GB",
    "address.state": "Greater London",
    "address.city": "London",
    "address.street_address": "221B Baker Street",
    "address.postal_code": "NW1 6XE"
  }
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
201
{
  "id": "pye_1042",
  "outcome": "created",
  "status": "active"
}