Skip to content

Send KYC documents for a phone number order (pbx)

Send KYC documents for an order you already placed, typically because POST /numbers/orders/check answered kyc_required_at_order: false and the order was placed without them. Also use it to replace a document the carrier rejected (kyc.provided[].status: rejected): send a new file under the same documentType.

Must be called as multipart/form-data: one file part kyc[<documentType>] per file document (PDF, JPEG or PNG, up to 10 MB each by default), and a plain form field kyc[<documentType>]=<value> for identifier-only types such as business_registration_number. At least one document is required. The documentType values are the ones in kycRequirements from the check endpoint; they may only contain letters, digits, underscores and hyphens.

The documents are forwarded to the carrier synchronously. On success the response is the updated order (same shape as GET /numbers/orders/{id}), whose kyc.provided now lists what you sent. The carrier then reviews them asynchronously: you are told per document (kyc.document_accepted, kyc.document_rejected) and once the whole KYC is signed off (kyc.validated) through your webhook: see Webhooks in the Phone Number Orders introduction.

Documents already on file

For an order on a shared (deferred) carrier contract, documents your account already holds for the order's address may be sent to the carrier for you, once the address verification has been approved. Your order then shows them in kyc.provided, and you may receive kyc.document_* webhook events without having called this endpoint. Documents you upload through this API are sent to the carrier only; they are not kept for later orders.

Errors

  • 404: no such order in your PBX.
  • 409: the order has not been placed with the carrier yet (it has no order_reference), so there is nothing to attach documents to.
  • 422: either the order cannot take documents any more (its KYC is already validated, or the order was canceled), a document failed validation (wrong file type, too large), or the carrier refused them; in the last case errors names the offending kyc[<documentType>] parameters.
  • 502: the carrier could not be reached, so the documents may not have been delivered. Check GET /numbers/orders/{id} (kyc.provided) before sending them again.
HTTP: IntegrationApiKey

API key created on the Cockpit Integrations page (an "API key" integration). It is a bearer token owned by the customer's PBX and limited to the scopes selected when the key was created (e.g. phone-numbers, push-configuration).

Send it in the Authorization header:

Authorization: Bearer <api_key>

Manage keys (create / reveal / revoke) from Integrations → New integration → API key.

HTTP Authorization Scheme
bearer
id path · string · uuid *
Order UUID returned by `POST /numbers/orders`.

Request body · required · multipart/form-data

Request schema
kyc*object
The KYC documents to send, one entry per `documentType` the order still owes (see `kyc.missing` on the order; the types come from `POST /numbers/orders/check`). At least one entry is required. Send file documents as a file part `kyc[<documentType>]` and identifier-only documents as a form field `kyc[<documentType>]=<value>`. Accepted file types: `application/pdf`, `image/jpeg`, `image/png`. For an OR group returned by the check endpoint (an array of types), provide any one of the listed `documentType`s.
business_registration_certificatefile · application/pdf
Business registration certificate (file part `kyc[business_registration_certificate]`). PDF, JPEG or PNG.
example: certificate.pdf
utility_billfile · application/pdf
Utility bill (file part `kyc[utility_bill]`). PDF, JPEG or PNG.
example: bill.pdf
id_documentfile · image/jpeg
Identity document (file part `kyc[id_document]`). PDF, JPEG or PNG.
example: passport.jpg
business_registration_numberstring
Identifier-only document, sent as a plain form field `kyc[business_registration_number]`.
length: 0–255
example: BE-12345

Responses

Documents accepted by the carrier. The updated order.
Response schema
idstring · uuid
Internal order id assigned by us. Use this with `GET /numbers/orders/{id}` to fetch the latest state, and `POST /numbers/orders/{id}/documents` to send KYC documents. Also the `id` in every webhook payload (the webhook body is this same object). Your own reference is `custom_order_reference`.
example: 550e8400-e29b-41d4-a716-446655440000
order_idstring | null
DEPRECATED: same value as `custom_order_reference`, kept for existing integrations. Read `custom_order_reference` instead.
length: 0–64deprecated
example: po-2026-00042
custom_order_referencestring | null
Your own reference for the order, supplied as `custom_order_reference` (or the deprecated `order_id`) on `POST /numbers/orders`. Echoed back here, in `GET /numbers/orders` (filterable by it) and in every webhook payload. If you sent none, the carrier's generated one is backfilled here.
length: 0–64
example: po-2026-00042
order_referencestring | null · uuid
The carrier's own identifier for this order (a UUID they assign). Null until the order is placed. This is the reference the carrier says to use to identify the order, and it is distinct from `custom_order_reference` (your own reference, opaque to the carrier). Not settable by you.
example: 01a0ae76-0000-4000-8000-000000000000
customer_namestring
Legal name of the customer the numbers were ordered for, as supplied on create (and editable on retry).
example: ACME GmbH
statusstring
Lifecycle state of this order. * `requested`: sent upstream, awaiting fulfilment. * `submission_failed`: rejected during synchronous submission; editable + retryable. * `processing`: async success callback received; provisioning in progress. * `success`: provisioning complete; `numbers` is populated. * `success_kyc_required`: the numbers are provisioned (`numbers` is populated) but the carrier has not validated the KYC yet; see `kyc`. Moves to `success` once the KYC is validated. * `error`: async rejection; terminal. * `processing_failed`: internal processing exhausted retries; admin-only retry. * `canceled`: a reseller closed a `submission_failed` order that provably never reached the carrier; terminal. Nothing was canceled at the carrier and no webhook is sent.
enum: created requested submission_failed processing success success_kyc_required error processing_failed canceled
example: requested
addressobject
streetstring
example: Adenauerallee
street_numberstring
example: 1
postal_codestring
example: 53113
citystring
example: Bonn
area_codestring | null
Country-conditional (e.g. set for CH); null otherwise.
countrystring
example: DE
formatted_addressstring | null
Single-line, comma-separated address derived from the persisted components (street + number, postal code + city, country). Empty components are dropped.
example: Adenauerallee 1, 53113 Bonn, DE
quantityinteger
example: 1
prefixstring | null
Dial prefix in E.164 (country + area code if applicable).
example: +49228
numbersarray<string>
E.164-formatted numbers assigned to this order. Empty (`[]`) until the order reaches `success` - they are never returned synchronously by `POST /numbers/orders` (which only ever returns `requested`); they arrive later via the webhook and then appear here on `GET /numbers/orders/{id}`.
[]string
phone_numbersarray<object>
The assigned numbers, in the same shape as `GET /numbers` (id, number, name, is_active, call_flow), so you can read each number's id and current call flow and act on it directly (e.g. `PUT /numbers/{id}/call-flow`). Populated once the order reaches `success`.
idstring · uuid
example: 550e8400-e29b-41d4-a716-446655440000
numberstring
Phone number in E.164 format.
example: +12125550100
namestring | null
example: Customer Support
is_activeboolean
example: 1
call_flow_idstring | null · uuid
example: 550e8400-e29b-41d4-a716-446655440100
call_flowobject | null
idstring · uuid
namestring
enabledboolean
error_messagestring | null
Short error reason when status is `error` or `submission_failed`.
kycobject
The order's KYC (know-your-customer) progress with the carrier. Present on every order, including orders placed with KYC deferred (`kyc_required_at_order: false` on `POST /numbers/orders/check`), where `missing` lists what is still to be sent via `POST /numbers/orders/{id}/documents`.
statestring | null
Overall KYC state. * `pending`: no verdict yet. * `validated`: the carrier signed the KYC off (`validated_at` is set). * `rejected`: not validated and at least one provided document is rejected. Null until the carrier has reported on this order.
enum: pending validated rejected null
example: pending
validated_atstring | null · date-time
When the carrier validated the KYC. Null until then.
providedarray<object>
Documents the carrier has on file, with their individual verdict.
Each item — One KYC document the carrier has on file for the order, with the carrier's verdict on it.
document_typestring | null
The KYC document type code (the `documentType` you uploaded it under).
example: utility_bill
statusstring | null
The carrier's verdict on this document. * `pending`: received, not reviewed yet. * `accepted`: reviewed and accepted. * `rejected`: reviewed and refused; see `rejection_reason` and upload a replacement. * `unknown`: the carrier reported a status we do not recognize.
enum: pending accepted rejected unknown null
example: accepted
rejection_reasonstring | null
Why the carrier refused the document. Null unless `status` is `rejected`.
created_atstring | null · date-time
When the carrier received the document (a replacement carries a new value).
example: 2026-05-12T11:15:03+00:00
updated_atstring | null · date-time
When the carrier last changed the document (for example reviewed it).
example: 2026-05-13T08:02:41+00:00
missingarray<object>
Documents still needed. Each entry is either an object (that exact document is needed) or an array of objects (provide any ONE of them), like `kycRequirements` on the check endpoint.
[]object
webhook_urlstring | null
The webhook URL we will POST the final outcome to.
webhook_statusstring
enum: not_required pending retrying delivered failed cancelled
webhook_next_retry_atstring | null · date-time
created_atstring · date-time
updated_atstring · date-time
Authorization Token Missing. This error is returned when the authorization token is missing.
Response schema
errorstring
Error message
example: Authorization Token is missing
Order not found in the caller's PBX.
Response schema
errorstring
messagestring
The order has not been placed with the carrier yet, so documents cannot be sent.
Response schema
message*string
The documents were not accepted. Same `{ message, errors }` shape as every other API validation error, except the "settled" case which carries only a `message`.
Response schema
message*string
errorsobject
Free-form object
The carrier could not be reached, so the documents may not have been delivered.
Response schema
message*string
post https://cockpit.voxbi.com/api/v1/numbers/orders/{id}/documents
Base URL
Request sample
curl -X POST 'https://cockpit.voxbi.com/api/v1/numbers/orders/{id}/documents' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'kyc[business_registration_certificate]=@certificate.pdf' \
  -F 'kyc[utility_bill]=@bill.pdf' \
  -F 'kyc[id_document]=@passport.jpg' \
  -F 'kyc[business_registration_number]=BE-12345'
const body = new FormData();
body.append('kyc[business_registration_certificate]', fileInput.files[0]); // certificate.pdf
body.append('kyc[utility_bill]', fileInput.files[0]); // bill.pdf
body.append('kyc[id_document]', fileInput.files[0]); // passport.jpg
body.append('kyc[business_registration_number]', 'BE-12345');

const response = await fetch('https://cockpit.voxbi.com/api/v1/numbers/orders/{id}/documents', {
  method: 'POST',
  headers: { Authorization: `Bearer ${YOUR_TOKEN}` },
  body,
});

const data = await response.json();
console.log(data);
import requests

response = requests.post(
    'https://cockpit.voxbi.com/api/v1/numbers/orders/{id}/documents',
    headers={'Authorization': f'Bearer {YOUR_TOKEN}'},
    data={
        'kyc[business_registration_number]': 'BE-12345',
    },
    files={
        'kyc[business_registration_certificate]': open('certificate.pdf', 'rb'),
        'kyc[utility_bill]': open('bill.pdf', 'rb'),
        'kyc[id_document]': open('passport.jpg', 'rb'),
    },
)
response.raise_for_status()
data = response.json()
print(data)
<?php
$ch = curl_init('https://cockpit.voxbi.com/api/v1/numbers/orders/{id}/documents');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer YOUR_TOKEN']);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'kyc[business_registration_certificate]' => new CURLFile('certificate.pdf'),
    'kyc[utility_bill]' => new CURLFile('bill.pdf'),
    'kyc[id_document]' => new CURLFile('passport.jpg'),
    'kyc[business_registration_number]' => 'BE-12345',
]);

$data = json_decode(curl_exec($ch), true);
print_r($data);
Sample request · multipart/form-data
{ … }
"kyc": { … }
"business_registration_certificate": "certificate.pdf",
"utility_bill": "bill.pdf",
"id_document": "passport.jpg",
"business_registration_number": "BE-12345"
}
}
{ … }
"id": "550e8400-e29b-41d4-a716-446655440000",
"order_id": "po-2026-00042",
"custom_order_reference": "po-2026-00042",
"order_reference": "01a0ae76-0000-4000-8000-000000000000",
"customer_name": "ACME GmbH",
"status": "success_kyc_required",
"address": { … },
"street": "Adenauerallee",
"street_number": "1",
"postal_code": "53113",
"city": "Bonn",
"area_code": null,
"country": "DE"
},
"formatted_address": "Adenauerallee 1, 53113 Bonn, DE",
"quantity": 1,
"prefix": "+49228",
"numbers": [ … ],
"+4922812345678"
],
"phone_numbers": [ … ],
{ … }
"id": "550e8400-e29b-41d4-a716-446655440001",
"number": "+4922812345678",
"name": null,
"is_active": true,
"call_flow_id": null,
"call_flow": null
}
],
"error_message": null,
"kyc": { … },
"state": "pending",
"validated_at": null,
"provided": [ … ],
{ … },
"document_type": "utility_bill",
"status": "pending",
"rejection_reason": null,
"created_at": "2026-05-12T11:15:03+00:00",
"updated_at": "2026-05-12T11:15:03+00:00"
},
{ … }
"document_type": "id_document",
"status": "pending",
"rejection_reason": null,
"created_at": "2026-05-12T11:20:10+00:00",
"updated_at": "2026-05-12T11:20:10+00:00"
}
],
"missing": []
},
"webhook_url": "https://your-app.example/hooks/number-order",
"webhook_status": "delivered",
"webhook_next_retry_at": null,
"created_at": "2026-05-12T10:36:47+00:00",
"updated_at": "2026-05-12T11:20:10+00:00"
}
{ … }
"error": "Authorization Token is missing"
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57
{ … }
"error": "Resource not found",
"message": "The requested endpoint does not exist."
}
{ … }
"message": "This order has not been placed with the carrier yet, so documents cannot be sent."
}
{ … }
"message": "No documents can be sent for this order: its KYC is already validated or the order was canceled."
}
{ … }
"message": "The carrier could not be reached, so the documents may not have been delivered. Check the order before sending them again."
}