Skip to main content

Verify the account holder

Renting a phone line requires a verified account holder: a CNPJ or a CPF, a photo or PDF of the document that shows it, and a confirmed contact. It is asked for once per workspace, before the first purchase. These endpoints do it without the dashboard — and it is the same verification as the dashboard’s (Phone lines → Buy phone lines): whichever you use, the other sees the result.
Requires a tenant-scoped key, like every /v1/phone-lines endpoint. With an OAuth / MCP token, only the workspace Owner may verify — the permission is phone_lines:purchase, the same as buying — and any other role gets 403 PERMISSION_DENIED. The state is sent with Cache-Control: no-store.

How it works

1

Submit the data and the document

POST /v1/phone-lines/verification with the CNPJ or CPF, a contact e-mail, a WhatsApp number and the document file. We check the number (check digits; a CNPJ must also be found and active in the public registry), send the contact codes, store the file privately and read the number printed on it.
2

Confirm the contact codes

Each channel in contact.requiredChannels receives a 6-digit code. Confirm each one with POST …/contact/{channel}/confirm.
3

Read the decision

As soon as the last code is confirmed and the document is in, the verification is decided — in that same response: APPROVED, or IN_REVIEW when a person needs to look at it. Both let you buy (canPurchase: true). The decision is also delivered as the phone_line.verification_updated webhook event.
Which codes. An e-mail typed into an API call is proven only by its code, so here EMAIL is always required, sent to contactEmail. WHATSAPP is required too whenever Pilot Status is sending WhatsApp codes — then both are. contact.requiredChannels says exactly which, and the list is frozen when the verification starts. The decision. APPROVED when the number read from the document is the number you submitted and the public registry could be consulted (a CPF has no registry: the document decides). Otherwise it waits for a person — IN_REVIEW — who approves it, rejects it, or asks for another document (NEEDS_DOCUMENT).
Buying while IN_REVIEW depends on the review. If it is rejected, the lines bought during the review are returned to the carrier 3 days after the decision, and every charge they paid is refunded to the wallet — see identity verification.

Submit

POST https://pilotstatus.com.br/v1/phone-lines/verification — answers 201 with the state, in PENDING_CONTACT: the codes are on their way.
string
required
Any string up to 255 characters with no control characters — a UUID per submission. Missing or blank → 400 IDEMPOTENCY_KEY_REQUIRED; longer, or with a control character → 400 IDEMPOTENCY_KEY_INVALID. See Idempotency.
string
required
"CNPJ" or "CPF".
string
required
The CNPJ or CPF, with or without punctuation (11.222.333/0001-81 or 11222333000181). Alphanumeric CNPJs are accepted.
string
required
The account holder’s contact e-mail, up to 254 characters. It receives a code.
string
required
The account holder’s WhatsApp number in international format — country code, area code and number, e.g. +55 11 90000-0000; punctuation is ignored. It receives a code when WHATSAPP is required.
string
required
The document file as a base64 data URI: data:<type>;base64,<content>. Types: application/pdf, image/jpeg, image/png, image/webp; at most 10 MB of file (about 14 MB once encoded). For a CNPJ: the CNPJ card (Cartão CNPJ), the articles of incorporation or the registration proof. For a CPF: an ID card (RG), a driver license (CNH) or the CPF registration proof, with the number readable.
Any other body field is refused with 400 UNKNOWN_FIELDS. The file is checked before anything starts: a wrong type or size sends no code and creates nothing.
The request returns once the document has been read — usually within seconds, up to about a minute. Encode the file without line breaks (base64 -w0 on Linux, base64 -i file on macOS). Submitting again. While the verification is still PENDING_CONTACT, a new submit replaces it — use it to fix a typo: the codes already sent stop working and new ones go out. Once it moved on, a submit answers 409 VERIFICATION_WRONG_STATE (IN_REVIEW or APPROVED — you can already buy — or NEEDS_DOCUMENT — send the document instead) or 403 VERIFICATION_REJECTED (talk to support).

Confirm a code

POST https://pilotstatus.com.br/v1/phone-lines/verification/contact/{channel}/confirm — {channel} is whatsapp or email, lowercase (anything else → 400 INVALID_CONTACT_CHANNEL).
string
required
The 6-digit code received on that channel.
Answers 200 with the state. When it was the last required code and the document is in, the state already carries the decision.
  • A code expires 10 minutes after it was sent (400 CONTACT_CODE_EXPIRED).
  • A wrong code is 400 CONTACT_CODE_INVALID and counts as an attempt; after 5 wrong attempts the code is locked (429 CONTACT_CODE_TOO_MANY_ATTEMPTS) — send a new one.
  • A code is used once. Retrying a confirm that already succeeded answers CONTACT_CODE_INVALID: read the state with GET instead.
  • A channel not in requiredChannels is 400 CONTACT_CODE_NOT_REQUIRED. No verification yet is 404 VERIFICATION_NOT_FOUND.

Send a new code

POST https://pilotstatus.com.br/v1/phone-lines/verification/contact/{channel}/send — no body. Sends a new code on a required channel and answers 200 with the state. The new code replaces the previous one. The first codes are sent by the submit itself: call this for a code that expired, was locked, or did not arrive.
  • At most one code per channel every 60 seconds, counting the one the submit sent (429 CONTACT_CODE_COOLDOWN).
  • Only while the verification is PENDING_CONTACT (409 VERIFICATION_WRONG_STATE afterwards), and only on a channel in requiredChannels (400 CONTACT_CODE_NOT_REQUIRED).
  • 503 SUPPLIER_UNAVAILABLE: the code could not be queued for sending. Nothing counts against the cooldown — try again.

Send a new document

POST https://pilotstatus.com.br/v1/phone-lines/verification/document with { "document": "data:…;base64,…" } — the same file rules as the submit, and it counts against the same submission limit. It replaces the document of the current verification and answers 200 with the state. It is accepted in two states:
  • PENDING_CONTACT — to replace the document you sent. Do it when document.status is UNREADABLE or UNCLEAR: we could not read a number we trust from the file, so send a sharper photo or the PDF. If you confirm the last code without replacing it, the verification goes to manual review (IN_REVIEW, reviewReason: "DOCUMENT_CHECK").
  • NEEDS_DOCUMENT — the reviewer asked for another document (reviewNote may say which). The contact codes were already confirmed, so the new document takes the verification straight back to a decision, in this same response.
In any other state it answers 409 VERIFICATION_WRONG_STATE; with no verification, 404 VERIFICATION_NOT_FOUND.

Read the state

GET https://pilotstatus.com.br/v1/phone-lines/verification answers 200 with the state of the workspace’s current verification. Before any submit:

The state object

Every endpoint on this page answers with it.
string
NONE, PENDING_CONTACT, IN_REVIEW, APPROVED, NEEDS_DOCUMENT or REJECTED — see the table below. The phone_line.verification_updated webhook uses the same values for the last four.
boolean
Whether POST /v1/phone-lines is allowed now: true for APPROVED and IN_REVIEW.
object | null
null for NONE.
string
Identifies the verification — the verificationId of the webhook event, and what support will ask for.
string
CNPJ or CPF.
string
Masked: **.***.333/0001-** (CNPJ) or ***.456.789-** (CPF). The full number is never returned.
The company name from the public registry (CNPJ). null for a CPF, and when the registry could not be consulted.
string
The contact e-mail, masked (a***@example.com).
string | null
The contact WhatsApp, masked (+5511****0000).
string[]
WHATSAPP and/or EMAIL: the codes this verification needs.
string[]
The required channels already confirmed.
string[]
The required channels still waiting for their code.
string
RECEIVED, UNREADABLE, UNCLEAR, REQUESTED or MISSING — see the table below.
string | null
Only while IN_REVIEW: REGISTRY_UNAVAILABLE (the CNPJ registry could not be consulted) or DOCUMENT_CHECK (the document needs a look). null otherwise.
string | null
The reviewer’s message, on REJECTED and NEEDS_DOCUMENT only. null otherwise, and it may be null there too.
string
ISO 8601 — when this verification was submitted.
string
ISO 8601 — its last change.

Idempotency

The submit requires an Idempotency-Key, because it has effects a retry must not repeat blindly: it sends the codes, and a second submit replaces the first (the codes just sent stop working). For 24 hours:
  • Same key, same request, first one finished → 200 (not 201) with the workspace’s current state and the header Idempotent-Replayed: true. Nothing is sent or read again.
  • Same key while the first is still running → 409 IDEMPOTENCY_KEY_IN_USE. Wait, then send it again to get its result.
  • Same key, different request (any field, the file included) → 409 IDEMPOTENCY_KEY_REUSED. Use a new key for new data.
  • A request refused as a whole (any 4xx or 5xx answer) keeps nothing: the same key can be sent again, and runs.
The same key sent by two workspaces never collides. The other verification endpoints take no key: a confirm is safe to retry (see above), a send is limited by its cooldown, and a document replaces the previous one.

Limits

  • 10 submissions per hour per workspace, the submit and the document together → 429 RATE_LIMITED, with a Retry-After header (seconds) and retryAfterSeconds in the body. A request refused by validation before it runs — a wrong field, type or file — does not count.
  • Contact codes: valid 10 minutes, 5 wrong attempts each, one per channel every 60 seconds.
  • The file: PDF, JPEG, PNG or WEBP, 10 MB at most.

Privacy

The document is personal data. It is stored in a private bucket, only Pilot Status reviewers can open it (every opening is logged), and no endpoint returns it — nor the full document number, nor what was read from the file. The responses mask the document number and the contacts, and never return a CPF holder’s name.

Errors

The envelope is the one of every phone lines error: { "error": "English. | Português.", "code": "…" } — branch on code. Validation errors may add details (unknownFields, maxLength). Plus the errors of every phone lines endpoint: 401, 403 NUMBER_SCOPE_NOT_ALLOWED, 403 PERMISSION_DENIED, 403 WORKSPACE_MEMBERSHIP_REQUIRED and 500 INTERNAL_ERROR.