Submit member L1 KYC directly from your server
x-idempotency-key
Submits a person-level L1 KYC profile for this member. Review is per person, not per card BIN: a person may have only one non-rejected profile at a time. card_product_id is optional; when supplied, it associates the submission with a product visible in your catalog by creating a snapshot. Omit it to submit only the person-level profile. Do not supply an issuer or BIN yourself.
After L1 approval, apply for a second or third card product directly through POST /v1/cards/applications, changing product_id. Do not call this endpoint again. The issuance gate checks only whether this person's L1 is approved.
For the same merchant + member + product, another submission when one is nonterminal or approved returns the existing application with 200. Rejected applications may be resubmitted. Submitting for another product while the first is pending returns state_invalid. Wait for GET /v1/kyc to report the result; do not submit two profiles in parallel.
If L1 is already approved, calling this endpoint for a different product only adds an approved snapshot; profile may be an empty object. It does not trigger another review, and card issuance does not depend on it.
Poll GET /v1/kyc for the result. Submission itself does not emit a webhook; approval / rejection / document-supplement requests emit kyc.result.updated.
Request Headers
| Field | Type | Required | Description |
|---|---|---|---|
x-idempotency-key |
string | Required | |
x-on-behalf-of |
string | Required | The member's external_member_id or mem_<uuid>. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
card_product_id |
string | Optional | Optional card product identifier, with an optional cpd_ prefix, from GET /v1/cards/products.
When supplied, creates only a product snapshot, which issuance does not read; omitted means person-level profile only.
After approval, use product_id on the card application endpoint to apply for other products. |
profile |
object | Required | The member's L1 identity profile. The first submission must be complete.
After L1 approval, subsequent calls may use {}. |
Response
status=pending). Without card_product_id,
id is the person-level kyc_<id> and card_product_id is an empty string. With a product,
an approved snapshot may instead be added for that product. For other card products, use
the person-level result from GET /v1/kyc.{
"id": "cpkyc_visa_virtual_usd_42",
"card_product_id": "visa_virtual_usd",
"kyc_id": "42",
"status": "pending",
"level": 0
}invalid_request · product_not_available · state_invalidmember_not_found · card_product_not_foundidempotency_key_required · idempotency_key_reusedcurl -X POST 'https://api.zinfra.vip/v1/kyc/applications' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
-H 'content-type: application/json' \
-d '{
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
}'const res = await fetch("https://api.zinfra.vip/v1/kyc/applications", {
method: "POST",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
body: JSON.stringify({
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
}),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();import requests
res = requests.post(
"https://api.zinfra.vip/v1/kyc/applications",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
json={
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
},
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/kyc/applications",
strings.NewReader(`{
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
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/kyc/applications"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-idempotency-key", "$IDEMPOTENCY_KEY")
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
}
"""))
.build();
// Use String / BigDecimal for amounts, not double.$ch = curl_init('https://api.zinfra.vip/v1/kyc/applications');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-idempotency-key: $IDEMPOTENCY_KEY',
'content-type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"profile": {
"first_name": "Jane",
"last_name": "Doe",
"nationality": "US"
}
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
{
"id": "cpkyc_visa_virtual_usd_42",
"card_product_id": "visa_virtual_usd",
"kyc_id": "42",
"status": "pending",
"level": 0
}