Phone numbers
Salvy and BYON WhatsApp number lifecycle.
Provision and connect responses include advisory warnings for display names: stable code, field and informational message. Tyxter advice does not block submission or predict Meta approval. Names are not rewritten. Older cached idempotency responses may omit warnings; replay preserves the accepted result.
Available regions
Response
| Field | Type | Notes |
|---|---|---|
objectrequired | "list" | |
datarequired | object[] |
Provision (Salvy)
In sandbox, provision returns 202 with a simulated number. In production it rents nothing: it creates a rental order, as POST /v1/phone-numbers/orders does, and returns 409 phone_number_rental_approval_required whose error.details.order carries the order's id, approval_url and expires_at, with the error.feedback pointer. The JavaScript SDK exposes those fields as TyxterApiError.rentalOrder. Do not also create an order for the same number. A keyed replay spends a rate-limit token and answers the 409 stored at creation, so read the order below for its status. Migration note (October 2026): production provision used to return 202 with a rented number; there is no deprecation window, a replay of an Idempotency-Key first used before the change still returns its original 202, and numbers already rented are unchanged. Reading the order needs the phone_numbers:read scope, which a key that only had phone_numbers:write for provision lacks.
Production provision and order creation need both the business-identity declaration and a separate current owner/admin billing WhatsApp recipient with explicit transactional-alert consent only when the default-off PHONE_PROVISIONING_REQUIRE_BILLING_WHATSAPP deployment policy is enabled. The legal KYC contact phone is not a delivery address. Missing or stale evidence then returns 403 billing_whatsapp_required before an order is written, and the approval checks it again; existing rentals, sandbox, and BYON connect are unaffected.
Request body
| Field | Type | Notes |
|---|---|---|
dddrequired | string | pattern: ^\d{2}$ |
display_name | string | len 1..255 |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
recent_messagesrequired | object[] | |
warnings | object[] |
Rental orders
A rental order asks to rent one Tyxter-provisioned number. In production it is created awaiting_approval and nothing is rented or charged: hand its approval_url to an owner or admin of the organization, who approves or declines it in the dashboard within 24 hours. No API key can approve an order: each production number is a phone line held in the organization's name, so a person there, an owner or admin, must authorize the rental. In sandbox the order is approved on creation with a free preview. A replay of the same Idempotency-Key returns the order as it was created; read the order for its current status.
The approval link opens the order's approval page in the dashboard, which shows the order's charge summary, the rental terms and a link to the renewal policy. Any organization member with access to the order's project can view the order (owners and admins always can), and only an owner or admin can approve or decline it. No /v1 route approves or declines an order, and no webhook event or notification announces a new one, so relay the link yourself. Approval re-runs the business-identity, billing WhatsApp, plan allowance and Tyxter-provisioned number limit checks, compares the current price, charge dates and terms_version with the order's preview, and reserves the first month. A refused approval, such as 402 insufficient_balance, leaves the order awaiting_approval until expires_at. An undeterminable price or charge date answers 503 phone_number_rental_preview_unavailable and creates no order.
Creation shares the per-organization provision rate limit (burst 5, sustained 10/hour). A keyed replay still spends a token before the stored response is found, so with an empty bucket it answers 429 rate_limited with Retry-After. Without a key every request creates a new order. The provisioning feature control blocks creation with 403 feature_disabled, not the read or the dashboard decision.
Request body
| Field | Type | Notes |
|---|---|---|
dddrequired | string | pattern: ^\d{2}$ |
display_name | string | len 1..255 |
purpose | string | Why the number is needed, shown to the owner or admin who approves the order. len 1..500 |
Response
| Field | Type | Notes |
|---|---|---|
objectrequired | "phone_number_order" | |
idrequired | string | |
statusrequired | "awaiting_approval" | "approved" | "declined" | "expired" | "provisioning" | "fulfilled" | "failed" | awaiting_approval until an owner or admin approves or declines it, or it expires. approved, provisioning, fulfilled and failed follow the phone number the approval started. |
project_idrequired | string | |
environment_idrequired | string | |
dddrequired | string | |
display_namerequired | string | null | |
purposerequired | string | null | |
previewrequired | object | object | What the rental will charge, as computed when the order was created. A sandbox order is free. |
terms_versionrequired | string | The rental terms the order was created under. |
approval_urlrequired | string | null | The dashboard page where an owner or admin approves or declines the order. Null unless the order awaits approval. |
expires_atrequired | string<ISO-8601> | An order not approved by this instant expires. pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
phone_number_idrequired | string | null | The phone number the approval started; null before approval. |
failurerequired | object | null | Why a declined or expired order ended; null for every other status. |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
approved_atrequired | string<ISO-8601> | null | |
declined_atrequired | string<ISO-8601> | null |
Returns the order with its status computed now. A declined or expired order carries failure.code phone_number_order_declined, phone_number_order_expired or phone_number_order_terms_changed; an unknown id answers 404 phone_number_order_not_found.
Poll it about once a minute. Reading spends no provision rate-limit token; the production credit hard-block still applies (402 credit_balance_exhausted). The status moves from awaiting_approval through approved (first month reserved, phone_number_id set) and provisioning to a final fulfilled (the number became active) or failed (the number never became active: provisioning failed after approval, which returns the reservation and sets the phone's error_code, or the number was released or disconnected first, which does not refund a month charged at delivery). declined and expired are final too, and rented and charged nothing: create a new order.
Connect (BYON)
Request body
| Field | Type | Notes |
|---|---|---|
phonerequired | string | pattern: ^\+\d{8,15}$ |
meta_phone_number_idrequired | string | len 1..∞ |
display_name | string | len 1..255 |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
recent_messagesrequired | object[] | |
warnings | object[] |
Import a selected Salvy number
Imports one selected cached resource from a connected Salvy account. This write requires phone_numbers:write, Idempotency-Key, and continuation_terms_version: "salvy_byok_v1". It creates a customer-owned BYON phone with provider_number_id: null; the call neither calls Salvy nor starts a Tyxter rental, release, wallet, fee, or usage operation. A foreign connection or resource returns 404 without revealing scope. The nullable salvy_management object is the enrollment record, and renewal is null.
Importing needs an active Standard or Growth plan; without one the call returns 403 salvy_byok_subscription_required. While that plan stays active, and through the end of the UTC month in which it ends, management of an imported number costs nothing. After that, billing charges one full phone.management.month fee from the balance on each month’s 1st. On the free plan, a number whose month is still unpaid at 12:00 UTC on the month’s last UTC day is disabled. While neither a plan nor a charged month covers the number, sends from it fail with phone_service_suspended; the number is not released. The renewal policy gives the full terms. Every phone response’s salvy_management reports billing’s live coverage, next charge and state, with suspension_reason while suspended. GET /v1/billing/phone-management reads the same values, plus payment status and the disable deadline; convert-to-byon ends management for free.
Request body
| Field | Type | Notes |
|---|---|---|
provider_connection_idrequired | string | len 1..∞ |
provider_number_idrequired | string | len 1..∞ |
meta_phone_number_id | string | len 1..∞ |
display_name | string | len 1..255 |
continuation_terms_versionrequired | "salvy_byok_v1" |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
Complete imported Salvy registration
Queues independent Meta WABA ownership validation for the current imported attachment. Send the Meta phone id with an Idempotency-Key. A connected Salvy account, carrier status, or received SMS never activates messaging; activation follows only a same-environment Meta ownership confirmation.
Request body
| Field | Type | Notes |
|---|---|---|
meta_phone_number_idrequired | string | len 1..∞ |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
List
Response
| Field | Type | Notes |
|---|---|---|
objectrequired | "list" | |
datarequired | object[] | |
has_morerequired | boolean | |
next_cursorrequired | string | null |
Retrieve
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewalrequired | object | null | |
recent_messagesrequired | object[] |
verified_name is Meta's current verified display name; in production it is observed by the scheduled health sweep and is null before that observation. Sandbox supplies a deterministic simulated value. display_name remains the customer-entered row label. pending_name_review is null or { requested_name: string | null; status: string | null; observed_at: ISO timestamp }: the pending Graph observation from a successful complete health sweep. Its nullable status is open Meta vocabulary and observed_at is the same freshness fact as meta_health_synced_at. A successful complete sweep is authoritative and writes or clears it. A completed callback can clear it immediately only when its requested name matches and its effective time is not older than observed_at; all other callbacks wait for the next sweep. Failed or partial refreshes leave prior values and freshness intact. Sandbox deterministically returns pending_name_review: null, which means no pending observation, not approval.name_review is separately null until a durable completed callback decision arrives; then its nullable requested_name and reason accompany reviewed_at and an open, forward-compatible Meta decision string. These read responses never call Meta.
remaining_messaging_allowance_estimate is how many more unique recipients this number can contact outside their customer-service windows before Tyxter begins holding sends. It is not a remaining-message count. A recipient already holding a slot in the moving 24-hour period costs nothing more, and any message from that recipient opens a separate 24-hour customer-service window whose sends also cost no allowance. It counts against a cap reduced by the configured safety margin and sending-phone quality ratio, so it may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values. The raw health tier is descriptive; an unlinked Meta phone uses a conservative per-phone fallback, while sandbox follows its simulated phone tier. Treat it as a planning estimate, not a quota to spend exactly: concurrent sends move it, slots expire one by one with no reset moment, and a paused number can report a number above zero before a send observes recovery. Missing authoritative linked capability or an unread unlinked tier uses a deliberately low default rather than an assumed unlimited allowance. null means Tyxter cannot estimate it at all, and never means zero.
Disconnect
Disconnects a number from the current environment and returns its terminal record. This operation is an idempotent no-op for an already disconnected, released, or failed number. Disconnecting does not end a rental: a Tyxter-rented number keeps billing until POST /v1/phone-numbers/{id}/release.
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
Convert to BYON
Send exactly {} with an Idempotency-Key to end an optional customer-Salvy management episode while retaining the same phone, Meta link, andsource: 'byon'. This free exit never releases or deregisters the number. It remains available when the customer credential was explicitly disconnected and cleanup is still queued.
Request body
| Field | Type | Notes |
|---|---|---|
| No fields. | ||
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
Release
Releases the upstream resource. Salvy numbers first enter release_requested while a worker cancels the rental; BYON numbers move directly to released. The request has no JSON body and supports Idempotency-Key.
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
Transfer
Moves a non-terminal number between projects or environments in the same organization. The body confirms both scopes and the route identifier; cross-organization targets return 404. This write supports Idempotency-Key.
Request body
| Field | Type | Notes |
|---|---|---|
source_project_idrequired | string | len 1..∞ |
source_environment_idrequired | string | len 1..∞ |
target_project_idrequired | string | len 1..∞ |
target_environment_idrequired | string | len 1..∞ |
confirm_phone_number_idrequired | string | len 1..∞ |
reason | string | len 1..500 |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "phone_number" | |
sourcerequired | "salvy" | "byon" | |
statusrequired | "requested" | "provisioning" | "provisioned" | "verifying" | "active" | "failed" | "release_requested" | "released" | "disconnected" | |
environmentrequired | "sandbox" | "production" | |
display_namerequired | string | null | |
dddrequired | string | null | |
phonerequired | string | null | |
provider_number_idrequired | string | null | |
meta_phone_number_idrequired | string | null | |
waba_idrequired | string | null | |
salvy_management | object | null | |
quality_ratingrequired | "green" | "yellow" | "red" | "unknown" | |
messaging_tierrequired | "tier_50" | "tier_250" | "tier_1k" | "tier_2k" | "tier_10k" | "tier_100k" | "unlimited" | "unknown" | |
messaging_limit_tierrequired | string | null | |
meta_throughput_tierrequired | "1" | "2" | "3" | "4" | "unlimited" | null | |
meta_quality_ratingrequired | string | null | |
verified_namerequired | string | null | The current display name Meta has verified for this number. Production values come from the scheduled Meta health sweep; sandbox uses its deterministic simulated health snapshot. Null means Tyxter has not observed a verified name on a completed sweep yet. |
pending_name_reviewrequired | object | null | The latest pending Meta display-name review observed on a completed health sweep. Null when Meta reported no pending review. This is distinct from name_review, which records a completed callback decision; display_name remains the customer-entered name for this row. |
name_reviewrequired | object | null | The latest durable Meta display-name review. Null until Tyxter has received a review decision. The decision is an open Meta value; display_name remains the customer-entered name for this row. |
meta_health_synced_atrequired | string<ISO-8601> | null | When this number's Meta-reported health (quality_rating, messaging_tier, messaging_limit_tier) was last read from Meta. Null means it has never been read yet. A failed refresh leaves both the values and this marker untouched, so an older timestamp means those fields are as of that moment rather than now. Active numbers are refreshed by a background sweep roughly every 6 hours; a large fleet of numbers can take several cycles to come around. |
current_24h_unique_recipientsrequired | integer | range 0..9007199254740991 |
remaining_messaging_allowance_estimaterequired | integer | null | Estimated number of NEW recipients this number can still start a conversation with before Tyxter starts holding sends. Measured against a rolling 24-hour window whose slots expire one by one — there is no reset moment — and against a cap reduced by the configured safety margin and sending-phone quality ratio. It may reach zero before the raw allowance is spent. Linked phones compare shared portfolio usage with their own quality-adjusted thresholds, so different quality can yield different remaining values; the phone’s raw health tier is descriptive, not a second quota. An unlinked Meta number uses a conservative per-phone fallback; sandbox follows its deterministic simulated phone tier. It is an ESTIMATE, not a quota you can spend exactly: concurrent sends move it, recipients already inside an open conversation window cost nothing against it, and a paused number can report a number above zero before any send has observed that allowance freed up. While Tyxter has no authoritative linked portfolio capability, or an unlinked number’s limit tier has not been read from Meta yet, this counts down from a deliberately low default rather than an assumed unlimited allowance, so a fresh number can look tighter than it is — messaging_tier reports "unknown" in that case. Null means Tyxter cannot estimate it at all — for example, Meta explicitly reports no finite linked portfolio cap — and never means zero. |
verification_coderequired | string | null | |
verification_code_received_atrequired | string<ISO-8601> | null | |
monthly_fee_brlrequired | string | null | |
error_coderequired | string | null | |
error_messagerequired | string | null | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
updated_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
activated_atrequired | string<ISO-8601> | null | |
released_atrequired | string<ISO-8601> | null | |
renewal | object | null | |
recent_messagesrequired | object[] |
The renewal field on phone reads
GET /v1/phone-numbers and GET /v1/phone-numbers/{id} return a required renewal field. It is null for BYON or sandbox. For a production number Tyxter rents for you, it reports the current UTC month under the renewal policy:
- next_renewal_at is the next monthly charge, 00:05 UTC on the 1st. From November 2026, the first month charged on its 1st, until this month’s charge for the number is recorded, it stays on this month’s 1st, even after 00:05 UTC has passed; a number bought this month, whose first charge paid the month, reads next month’s 1st. It is null before the number is first active and once a release is requested.
- State at_risk, with reason insufficient_credit and recommended_action add_credit_or_enable_auto_topup, means this month’s rental charges left the organization’s balance below zero, this number was charged for the month, and the balance has not come back to zero or above. State upcoming means the month is paid, or the number was not charged for it (a number bought this month pays it with its first charge).
- On the free plan, release_cutoff_at is when the number is released if the month is still unpaid: 12:00 UTC on the month’s last UTC day. It is null when nothing is due to be released.
- automatic_release_enabled is false while a paid plan keeps your numbers. An unpaid month then still reads at_risk, with no release_cutoff_at.
- amount_brl is this month’s rental charge for the number, or null when it had none.
- State release_pending means a release was requested; released means it is done. cycle_id, renewal_warning_48h_at, renewal_warning_24h_at, grace_selected_at, grace_ends_at, release_requested_at and provider_evidence_observed_at are always null.
Use phone_numbers:read to list the non-released roster in the key’s project and environment. Follow next_cursor as starting_after until has_more is false, then retrieve a phone to refresh it. Retained released rows remain retrievable while they exist. These two reads remain available during credit exhaustion and do not repair phone lifecycle, create billing work, charge, notify, or call providers. Authentication, scope, suspension and tenant checks still apply.
To top up, use the Billing page for the same organization and project, or use billing:write with POST /v1/billing/packages/purchase and Idempotency-Key, or PUT /v1/billing/auto-topup. Read the package or auto-top-up contract before submitting. Then refresh GET /v1/billing/balance with billing:read: production_blocked false means production API access is back, and a balance of zero or above means the month is paid, which the renewal field then reads as upcoming. Phone read scope does not grant billing access or expose the organization wallet, ledger or other projects’ obligations.
Unpaid month on the free plan
{
"id": "pn_abc",
"object": "phone_number",
"source": "salvy",
"status": "active",
"environment": "production",
"display_name": "Tyxter Brazil",
"ddd": "11",
"phone": "+5511987654321",
"provider_number_id": "salvy_456",
"meta_phone_number_id": "456_phone",
"waba_id": null,
"quality_rating": "unknown",
"messaging_tier": "unknown",
"messaging_limit_tier": null,
"meta_throughput_tier": null,
"meta_quality_rating": null,
"verified_name": null,
"pending_name_review": null,
"name_review": null,
"meta_health_synced_at": null,
"current_24h_unique_recipients": 0,
"remaining_messaging_allowance_estimate": 225,
"verification_code": null,
"verification_code_received_at": null,
"monthly_fee_brl": "49.90",
"error_code": null,
"error_message": null,
"created_at": "2026-04-25T10:00:00Z",
"updated_at": "2026-04-25T11:30:00Z",
"activated_at": "2026-04-25T11:30:00Z",
"released_at": null,
"recent_messages": [],
"renewal": {
"state": "at_risk",
"reason_codes": [
"insufficient_credit"
],
"cycle_id": null,
"amount_brl": "49.9000",
"currency": "brl",
"next_renewal_at": "2026-12-01T00:05:00.000Z",
"release_cutoff_at": "2026-11-30T12:00:00.000Z",
"renewal_warning_48h_at": null,
"renewal_warning_24h_at": null,
"grace_selected_at": null,
"grace_ends_at": null,
"automatic_release_enabled": true,
"release_requested_at": null,
"recommended_action": "add_credit_or_enable_auto_topup",
"evaluated_at": "2026-11-10T12:00:00.000Z",
"provider_evidence_observed_at": null
}
}Unpaid month on a paid plan
{
"state": "at_risk",
"reason_codes": [
"insufficient_credit"
],
"cycle_id": null,
"amount_brl": "49.9000",
"currency": "brl",
"next_renewal_at": "2026-12-01T00:05:00.000Z",
"release_cutoff_at": null,
"renewal_warning_48h_at": null,
"renewal_warning_24h_at": null,
"grace_selected_at": null,
"grace_ends_at": null,
"automatic_release_enabled": false,
"release_requested_at": null,
"recommended_action": "add_credit_or_enable_auto_topup",
"evaluated_at": "2026-11-10T12:00:00.000Z",
"provider_evidence_observed_at": null
}Paid month
{
"state": "upcoming",
"reason_codes": [],
"cycle_id": null,
"amount_brl": "49.9000",
"currency": "brl",
"next_renewal_at": "2026-12-01T00:05:00.000Z",
"release_cutoff_at": null,
"renewal_warning_48h_at": null,
"renewal_warning_24h_at": null,
"grace_selected_at": null,
"grace_ends_at": null,
"automatic_release_enabled": true,
"release_requested_at": null,
"recommended_action": "none",
"evaluated_at": "2026-11-10T12:00:00.000Z",
"provider_evidence_observed_at": null
}