Numbers

Search available numbers

GET /v1/numbers/available?area_code=415

Returns up to 5 available numbers. In the sandbox this is a deterministic fake inventory; with live access it searches real US/Canada inventory across 200+ area codes.

Purchase a number

POST /v1/numbers with { "phone_number": "+1..." } (optionally "customer_id": "cus_..." to assign it to one of your customers)

Adds the number to your account (quota applies: default 2 live numbers, 3 sandbox). Returns the Number object. Emits a number.purchased event. Send an Idempotency-Key header and a retried purchase returns the original 201. Without one, the retry gets "You already own this number."

List your numbers

GET /v1/numbers (?customer_id=cus_... for one customer's). Each number carries customer_id (null when unassigned) and sender (below). GET /v1/numbers/+14155550132 returns one.

Sender status

A live US number works the moment you buy it, with one restriction: until it is registered with the carriers it is test only. It can text your verified numbers (your own phone, and any number you verify under Billing → Verified numbers), and nobody else. That is enough to build and test your integration end to end while the registration is in review. Once the number is linked to an approved registration it can text anyone.

Every number carries its standing:

json
"sender": {
  "state": "pending",
  "registration_id": "reg_a1B2c3D4e5F6",
  "reason": null,
  "updated_at": "2026-10-04T16:20:00.000Z"
}
StateMeaningCan text
test_onlyNo registration has been submitted for itYour verified numbers
pendingA registration is in carrier review, or approved and being linkedYour verified numbers
activeLinked to an approved registrationAnyone
action_neededSomething needs you: the registration was rejected, its website check fails, or the link failed. reason says exactly what to doYour verified numbers

sender is null on sandbox numbers (they never reach a real phone) and on numbers that need no registration (toll-free and Canadian; see below).

Toll-free numbers

A toll-free number (800, 833, 844, 855, 866, 877, 888) is not registered: it is verified with the carriers for one business and one use case. Until its verification is approved it texts your verified numbers only, like a test_only local number; once approved it texts anyone who opted in, and its texts and STOP/HELP replies name the verified business. You can buy one before your account has live access: add credit, buy it, then verify it in the console (Numbers → Toll-free verification): you give your legal business name, EIN, registered address and what you send, and we fill in the rest. When we know your website, we read its public pages (with the fixed rules of Fill in from your website) and fill in your legal name, brand and address where they are still blank, marked for you to check, and use your own privacy policy and terms (we never write them for you; the website check says what they need); Fill from website reads it again. We also fill in you as the contact, from your account; a description of your program; the volume; the HELP and STOP replies, example messages, your website, and, when you have no registration page of your own, a hosted opt-in page that links your own terms and privacy policy. Everything we filled in stays editable. When you submit, you sign for the business the same way as for a registration (once per business: your name, your title and a short statement that you can act for it). Our team checks the form, usually the same business day, then files it with the carriers, with a screenshot of your opt-in page, and the carriers write to our support address, not yours: we answer what we can and forward anything only you can answer. You are told when it is approved (usually days, sometimes weeks). When the carriers approve it, that also verifies the business with us (no separate business review to wait for), and an account that had no live access goes live.

Sending something else. An approved verification can't be changed. To send what a number isn't verified for (marketing from a number verified for codes, say), add another toll-free number: its form starts as a copy of the verified one, you pick the new use case, and the first number keeps its approval.

Before your account is live. Once you own a live toll-free number you can create a live API key without waiting for live access. Until the account goes live, live sends (POST /v1/messages, and the console's Send message) go out only from that toll-free number and only to your verified numbers, billed from your credit like any live message; a send from another number, or to anyone else, answers 403 sender_not_registered and is not charged. Such a key can also read messages, numbers, your balance and usage; every other live endpoint answers 403 live_access_required until the account is live, and then the same key works everywhere.

Each live toll-free number carries its verification:

json
"toll_free": {
  "status": "verified",
  "business": "Example Dental",
  "use_case": "Appointment reminders",
  "verified_at": "2026-10-20T16:00:00.000Z"
}

status is not_started, pending, verified or rejected. toll_free is null on every other number. Canadian numbers cannot be registered through us yet and text your verified numbers only.

status is pending from when you submit the form until the carriers answer. Toll-free numbers need no 10DLC registration or registration fees, and their sender is null, but the carriers verify each toll-free number: a number you buy later gets a form of its own, filled in as a copy of your last one. A local number is the other way round: once its registration is approved, up to 49 numbers join it with no new review.

Toll-free verification by API

Platforms can verify their users' toll-free numbers without the console. It is the same form, filled in the same way, and it goes to the same review.

  1. POST /v1/toll-free-verifications with the numbers and anything you know (legal name, EIN, address, use case). We fill in the rest and return a draft with problems: what the carriers would send it back for.
  2. PATCH /v1/toll-free-verifications/{id} until ready is true.
  3. POST /v1/toll-free-verifications/{id}/submit, with the business attestation the first time for a business. It comes back in_review.
bash
curl -X POST https://api.jointextkit.com/v1/toll-free-verifications \
  -H "Authorization: Bearer $TEXTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numbers":["+18335550100"],"business_name":"Acme Plumbing LLC","ein":"12-3456789","street":"1 Main St","city":"Springfield","state":"IL","postal_code":"62701","use_case":"customer_care"}'
statusWhat it meansWho acts
draftNot submittedYou
in_reviewA person on our team is checking it, usually within a business day. Nothing has gone to the carriers yet; you can still change itUs
submittedWe sent it to the carriers. Usually days, sometimes weeksThe carriers
verifiedIts numbers text anyone who opted inNobody
rejectedSent back. rejection.reason says why: fix it with PATCH, then submit againYou

Every form is checked by a person before it goes to the carriers; the API never sends one straight there. Poll GET /v1/toll-free-verifications/{id} for the answer; we also email you when it is verified or sent back. The calls need a key with the registrations scope. A verification can't change once it is with the carriers or verified.

You do not have to do anything to move a number along. Submit one registration and every live number on the account follows it: pending while the carriers review, then linked and active when the registration is approved. A number bought later links on its own, usually within 1 business day. Each change emits number.sender_updated (data: phone_number, state, previous_state, registration_id, reason).

If our team ever records an approval by mistake (nothing was filed with the carriers), we undo it: the registration goes back to our filing queue, or back to you with a reason, and its numbers leave active (pending or action_needed, with the same event and an entry in your audit log).

A send to anyone else from a number that is not active answers 403 sender_not_registered with an X-TextKit-Sender-State header and a message that says what to do. Nothing is sent and nothing is charged. Broadcasts, scheduled sends and automations follow the same rule: those recipients fail with sender_not_registered, never silently.

Choose a registration for a number

POST /v1/numbers/+14155550132/registration with { "registration_id": "reg_..." }

Only needed when your account has more than one approved registration (the number is action_needed until you choose), or to retry a link that failed. You can also pass registration_id when you buy the number. One registration holds up to 49 numbers, the most carriers allow; attaching a 50th answers 409. Returns the number with its new sender.

Assign a number to a customer

PATCH /v1/numbers/+15005550132 with { "customer_id": "cus_..." }, or { "customer_id": null } to unassign. Messages to and from the number are attributed to that customer from then on; earlier messages keep theirs.

Release a number

DELETE /v1/numbers/+15005550132

Marks the number released (live mode removes it from its registration, then disconnects it at the carrier). Rate limited to 10 releases per 30 minutes. Emits number.released.

Live number purchase and release require live access; sandbox numbers work for everyone immediately.

Request a short code

Short codes aren't bought on the spot: carriers approve each one, in 8 to 12 weeks. File the request and our team sets it up with you by email:

bash
curl -X POST https://api.jointextkit.com/v1/numbers/requests \
  -H "Authorization: Bearer ssms_sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "short_code", "business_name": "Acme Inc", "monthly_volume": "over_100k",
        "use_case": "Order and delivery updates to customers who opted in at checkout." }'

monthly_volume is under_1k, 1k_10k, 10k_100k or over_100k. One open request per type; asking again updates it. GET /v1/numbers/requests lists the open ones. Short-code carrier fees are passed through at cost, on top of the per-segment rate. The console's Add a number has the same form.

Buy in bulk

POST /v1/numbers/bulk-purchase

json
{ "area_code": "415", "quantity": 10, "pool_id": "pool_a1B2c3D4e5F6", "tags": ["spring"] }

Up to 50 per call. Your number limit is checked for the whole quantity first: if it doesn't fit you get 429 quota_exceeded saying how many more you can add, and nothing is bought. After that, a number the carrier refuses is listed in failed and replaced from spare inventory; shortfall is how many the area code couldn't supply. Test keys mint sandbox numbers.

Replacing a filtered number

If you release a live number that carriers were filtering as spam and buy another within 7 days, new live numbers are put on hold for your account: purchases answer 403 number_purchase_held until our team has reviewed it, and the owner and admins are emailed. Your existing numbers keep working. Carriers treat swapping a filtered number for a fresh one as evading their filters ("number cycling") and fine it; a new number doesn't fix what got the old one filtered, so look at the wording, the opt-in and the volume instead.

Tags, labels, and bulk actions

Every number carries an optional label ("Front desk") and up to 20 tags (lowercased). Filter on them anywhere: GET /v1/numbers?tag=spring&pool=pool_...&mode=live&q=front.

POST /v1/numbers/bulk applies one action to up to 1000 numbers:

actionExtra field
add_tags / remove_tagstags: [...]
set_labellabel (null clears)
add_to_pool / remove_from_poolpool_id
releaseconfirm: "RELEASE <count>"

The response is always one result per id you sent, so a batch where 3 of 143 ids were wrong tells you which 3:

json
{
  "object": "bulk_result", "action": "add_tags",
  "requested": 143, "succeeded": 140, "failed": 3,
  "results": [{ "id": "+14155550132", "ok": true }, { "id": "+1415555", "ok": false, "error": "invalid_id" }]
}

Per-id errors: invalid_id, duplicate, not_found, too_many_tags, mode_mismatch (a test key releasing a live number), carrier_error.

Release is guarded. Up to 100 per call, and confirm must be "RELEASE <number of ids sent>", so a script that built the wrong list fails before anything is disconnected. Released live numbers go back to carrier inventory and may not be recoverable.

Sender pools

A pool is a named group of your numbers. Send with "from": "pool_..." and TextKit picks a member, the same one for each recipient every time, so replies stay in one thread on their phone. Live keys draw only live members; test keys only sandbox members.

bash
curl https://api.jointextkit.com/v1/numbers/pools \
  -H "Authorization: Bearer $TEXTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Support"}'

GET/PATCH/DELETE /v1/numbers/pools/:id, and POST /v1/numbers/pools/:id/clone copies a pool (same description and members, new id). Numbers can sit in several pools. Deleting a pool keeps its numbers. Membership is set with the bulk actions above.

Local numbers

Create or patch a pool with "routing": "local" and a recipient gets a member in their area code when the pool has one, else one in their time zone, else any member. It is still the same number for that recipient every time. The default, "sticky", ignores location.

Throughput

GET /v1/numbers/:id returns throughput: the messages a minute TextKit holds the number to.

json
"throughput": { "per_minute": 240, "shared_by": "registration", "registration_id": "reg_..." }

The pace belongs to the number's registration and is shared by every number linked to it: 240 a minute, 75 for a low-volume registration. A direct send over it gets 429 rate_limited with Retry-After; batches and scheduled messages wait for the next minute instead. null means no TextKit pace applies (the number is not linked to a registration); carrier limits still do.

Export

GET /v1/numbers/export returns CSV (phone_number, mode, label, tags, pools, pool_ids, created_at) and takes the same filters as the list. The console exports a filter or an exact selection.