Phone numbers

Finds, buys, and releases US phone numbers for texting. $1.60 per number per month. The base URL is https://www.motor.dev/phone_numbers, and the OpenAPI 3.1 specification describes each endpoint.

Try it

Sign in to try the API here with one of your team's keys. Requests use that key's permissions and spending limit. Standard charges apply.

Buying a number

Buy a US local or toll-free number in one call. Set your criteria, and Motor buys the first match:

curl https://api.motor.dev/phone_numbers/numbers \
  -H "Authorization: Bearer $MOTOR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"type": "local", "area_code": "415", "label": "Order updates"}'

To choose a specific number, search first, then buy it by its phone_number. If it's no longer available, Motor returns 409 number_unavailable without charging you or buying a substitute.

curl "https://api.motor.dev/phone_numbers/available_numbers?region=CA&contains=777" \
  -H "Authorization: Bearer $MOTOR_KEY"

Search filters: type (local or toll_free), area_code, region (a state or DC, local numbers only), contains (1 to 10 digits), and limit (up to 50). Toll-free prefixes are 833, 844, 855, 866, 877, and 888. Motor doesn't sell 800 numbers.

Most numbers are active when you receive the response. A number may be pending while the carrier confirms it. Poll GET /numbers/pn_... until it's active or failed. Failed orders aren't charged. If a request times out, retry it with the same Idempotency-Key: Motor won't buy a second number.

Test mode. Send Motor-Mode: test to search and buy fictional numbers, such as +14155550100, for free. Test numbers activate immediately. To test a failed order, use "contains": "0000" to get a pending number that fails after 10 seconds.

Pricing, billing, and limits

Every number costs $1.60 a month, local or toll-free. There's no setup fee. Test numbers are free.

  • Billing starts when the number becomes active and renews on the same day each month. A number activated on the 31st renews on the last day of shorter months.
  • Rent is charged to the account associated with the key that bought the number. To change the key, set billing_key_id. Changes take effect next month.
  • A purchase returns 402 spending_limit if the first month would exceed the key's monthly spending limit. Renewals continue even if they exceed the limit. They count toward the limit and spending alerts.
  • Releasing a number stops future charges. Unused time isn't refunded.
LimitDefault, per team and mode
Numbers (excluding released and failed)100
Purchases20 an hour
Searches10 a second

To raise a limit, contact support@motor.dev.

Releasing a number

Release an active number you no longer need:

curl -X DELETE https://api.motor.dev/phone_numbers/numbers/pn_8Kq2vLxN4cT7 \
  -H "Authorization: Bearer $MOTOR_KEY"

The number becomes releasing, then released once the carrier confirms. Release is permanent. The carrier can reassign the number. Released number records remain available for 90 days.

If an SMS sender uses the number, the API returns 409 number_in_use with the sender ID in resource_id. Delete the sender, wait until the number's used_by is null, then release it.

Numbers

POST/phone_numbers/numbers

Buy a number

Buys a specific number or the first match for your criteria, trying up to three candidates. Most numbers are active in the response. If pending, poll until active or failed. Billing starts on activation. Motor never substitutes another number for a specific number you request. An Idempotency-Key is required; retry with the same key to avoid buying a second number.

ParameterLocationDescription
Idempotency-Keyheader1 to 128 printable ASCII characters. Use a new value for each purchase and reuse it for retries.
Motor-Modeheaderlive (default) or test. Test numbers are free and never connect to the phone network.
FieldTypeDescription
phone_numberstringA specific number in E.164, such as +14155550123, usually from a search. Can't be combined with criteria.
typestringRequired when buying by criteria.
area_codestringSame as the search parameter.
regionstringSame as the search parameter. Local numbers only.
containsstringSame as the search parameter. In test mode, 0000 creates a pending number that fails after 10 seconds.
labelstring or nullYour name for the number, 0 to 128 UTF-8 bytes.
Responses

201The number, active or pending.

{
  "id": "pn_8Kq2vLxN4cT7",
  "phone_number": "+14155550123",
  "type": "local",
  "status": "active",
  "label": "Order updates",
  "locality": "San Francisco",
  "region": "CA",
  "capabilities": {
    "sms": true,
    "mms": true,
    "voice": false
  },
  "used_by": null,
  "monthly_price_usd": "1.60",
  "billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
  "paid_through": "2026-11-09T18:22:04Z",
  "failure_reason": null,
  "created_at": "2026-10-09T18:22:01Z",
  "activated_at": "2026-10-09T18:22:04Z",
  "released_at": null
}

401The key is missing or incorrect.

{
  "error": {
    "code": "unauthorized",
    "message": "This API key is invalid or has been revoked."
  }
}

403The key can't use phone numbers, or it's a publishable key.

{
  "error": {
    "code": "forbidden",
    "message": "This key cannot access this API. Update its permissions on the API keys page."
  }
}

402The first month would exceed the key's monthly spending limit.

{
  "error": {
    "code": "spending_limit",
    "message": "The $1.60 charge for the first month would exceed this key's monthly spending limit. Raise the limit on the API keys page."
  }
}

409The number is unavailable (number_unavailable), the team's number limit was reached (quota_exceeded), or the idempotency key was used for a different request.

{
  "error": {
    "code": "number_unavailable",
    "message": "+14155550123 is no longer available. You weren't charged. Search for another number."
  }
}

422A field isn't valid, no number matches the criteria (no_numbers_available), or the number is an 800 number (unsupported_number).

{
  "error": {
    "code": "no_numbers_available",
    "message": "No numbers match these criteria. Try removing contains or region."
  }
}

429The team's hourly purchase limit was reached.

{
  "error": {
    "code": "rate_limited",
    "message": "Your team has reached its limit of 20 number purchases per hour. Retry later."
  }
}

503Motor's number provider couldn't accept the order. You weren't charged.

{
  "error": {
    "code": "temporarily_unavailable",
    "message": "Motor's number provider couldn't accept the order. You weren't charged. Retry in a few minutes."
  }
}

GET/phone_numbers/numbers

List your numbers

Lists the team's numbers in the selected mode, oldest first. Includes all statuses except released by default. Released and failed number records remain available for 90 days.

ParameterLocationDescription
statusqueryOnly numbers with this status.
typequery
area_codequeryThree digits.
in_usequerytrue for numbers used by a Motor resource; false for unused numbers.
limitqueryFrom 1 to 200; 50 by default.
cursorqueryThe previous page's next_cursor.
Motor-Modeheaderlive (default) or test. Test numbers are free and never connect to the phone network.
Responses

200A page of numbers.

{
  "data": [
    {
      "id": "pn_8Kq2vLxN4cT7",
      "phone_number": "+14155550123",
      "type": "local",
      "status": "active",
      "label": "Order updates",
      "locality": "San Francisco",
      "region": "CA",
      "capabilities": {
        "sms": true,
        "mms": true,
        "voice": false
      },
      "used_by": null,
      "monthly_price_usd": "1.60",
      "billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
      "paid_through": "2026-11-09T18:22:04Z",
      "failure_reason": null,
      "created_at": "2026-10-09T18:22:01Z",
      "activated_at": "2026-10-09T18:22:04Z",
      "released_at": null
    }
  ],
  "next_cursor": null
}

401The key is missing or incorrect.

{
  "error": {
    "code": "unauthorized",
    "message": "This API key is invalid or has been revoked."
  }
}

403The key can't use phone numbers, or it's a publishable key.

{
  "error": {
    "code": "forbidden",
    "message": "This key cannot access this API. Update its permissions on the API keys page."
  }
}

422A field or parameter isn't valid.

{
  "error": {
    "code": "invalid_field",
    "message": "`area_code` must be three digits, such as 415."
  }
}

GET/phone_numbers/numbers/{id}

Read a number

Returns a number. Poll a pending number here until it's active or failed.

ParameterLocationDescription
idpathThe number ID, such as pn_8Kq2vLxN4cT7.
Motor-Modeheaderlive (default) or test. Test numbers are free and never connect to the phone network.
Responses

200The number.

{
  "id": "pn_8Kq2vLxN4cT7",
  "phone_number": "+14155550123",
  "type": "local",
  "status": "active",
  "label": "Order updates",
  "locality": "San Francisco",
  "region": "CA",
  "capabilities": {
    "sms": true,
    "mms": true,
    "voice": false
  },
  "used_by": null,
  "monthly_price_usd": "1.60",
  "billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
  "paid_through": "2026-11-09T18:22:04Z",
  "failure_reason": null,
  "created_at": "2026-10-09T18:22:01Z",
  "activated_at": "2026-10-09T18:22:04Z",
  "released_at": null
}

401The key is missing or incorrect.

{
  "error": {
    "code": "unauthorized",
    "message": "This API key is invalid or has been revoked."
  }
}

403The key can't use phone numbers, or it's a publishable key.

{
  "error": {
    "code": "forbidden",
    "message": "This key cannot access this API. Update its permissions on the API keys page."
  }
}

404The number wasn't found for your team in this mode, or it was released or failed more than 90 days ago.

{
  "error": {
    "code": "not_found",
    "message": "The number doesn't exist."
  }
}

PATCH/phone_numbers/numbers/{id}

Change a number's label or billing key

Updates label and billing_key_id. Billing key changes take effect next month.

ParameterLocationDescription
idpathThe number ID, such as pn_8Kq2vLxN4cT7.
If-MatchheaderThe number's ETag, such as "pn:pn_8Kq2vLxN4cT7:3". Optional; when sent, the change applies only if the number hasn't changed.
Motor-Modeheaderlive (default) or test. Test numbers are free and never connect to the phone network.
Request body
{
  "label": "Support line"
}
FieldTypeDescription
labelstring or null0 to 128 UTF-8 bytes, or null to remove it.
billing_key_idstringThe ID of an active API key for your team, such as key_....
Responses

200The changed number.

{
  "id": "pn_8Kq2vLxN4cT7",
  "phone_number": "+14155550123",
  "type": "local",
  "status": "active",
  "label": "Support line",
  "locality": "San Francisco",
  "region": "CA",
  "capabilities": {
    "sms": true,
    "mms": true,
    "voice": false
  },
  "used_by": null,
  "monthly_price_usd": "1.60",
  "billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
  "paid_through": "2026-11-09T18:22:04Z",
  "failure_reason": null,
  "created_at": "2026-10-09T18:22:01Z",
  "activated_at": "2026-10-09T18:22:04Z",
  "released_at": null
}

401The key is missing or incorrect.

{
  "error": {
    "code": "unauthorized",
    "message": "This API key is invalid or has been revoked."
  }
}

403The key can't use phone numbers, or it's a publishable key.

{
  "error": {
    "code": "forbidden",
    "message": "This key cannot access this API. Update its permissions on the API keys page."
  }
}

404The number wasn't found for your team in this mode, or it was released or failed more than 90 days ago.

{
  "error": {
    "code": "not_found",
    "message": "The number doesn't exist."
  }
}

409The number changed during the update. Read it again and retry.

{
  "error": {
    "code": "revision_conflict",
    "message": "The number changed during the update. Read it again and retry."
  }
}

412If-Match isn't the number's current ETag.

{
  "error": {
    "code": "revision_conflict",
    "message": "The number changed since you read it. Read it again and retry with the new ETag."
  }
}

422A field or parameter isn't valid.

{
  "error": {
    "code": "invalid_field",
    "message": "`area_code` must be three digits, such as 415."
  }
}

DELETE/phone_numbers/numbers/{id}

Release a number

Releases an unused active number. Its status becomes releasing, then released once the carrier confirms. Release is permanent, and the carrier can reassign the number. Unused time isn't refunded.

ParameterLocationDescription
idpathThe number ID, such as pn_8Kq2vLxN4cT7.
If-MatchheaderThe number's ETag, such as "pn:pn_8Kq2vLxN4cT7:3". Optional; when sent, the change applies only if the number hasn't changed.
Motor-Modeheaderlive (default) or test. Test numbers are free and never connect to the phone network.
Responses

200The number with status releasing.

{
  "id": "pn_8Kq2vLxN4cT7",
  "phone_number": "+14155550123",
  "type": "local",
  "status": "releasing",
  "label": "Order updates",
  "locality": "San Francisco",
  "region": "CA",
  "capabilities": {
    "sms": true,
    "mms": true,
    "voice": false
  },
  "used_by": null,
  "monthly_price_usd": "1.60",
  "billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
  "paid_through": "2026-11-09T18:22:04Z",
  "failure_reason": null,
  "created_at": "2026-10-09T18:22:01Z",
  "activated_at": "2026-10-09T18:22:04Z",
  "released_at": null
}

401The key is missing or incorrect.

{
  "error": {
    "code": "unauthorized",
    "message": "This API key is invalid or has been revoked."
  }
}

403The key can't use phone numbers, or it's a publishable key.

{
  "error": {
    "code": "forbidden",
    "message": "This key cannot access this API. Update its permissions on the API keys page."
  }
}

404The number wasn't found for your team in this mode, or it was released or failed more than 90 days ago.

{
  "error": {
    "code": "not_found",
    "message": "The number doesn't exist."
  }
}

409A Motor resource uses the number (number_in_use, with resource_id), or the number isn't active (invalid_state).

{
  "error": {
    "code": "number_in_use",
    "message": "Delete SMS sender sms_snd_1 before releasing this number.",
    "resource_id": "sms_snd_1"
  }
}

412If-Match isn't the number's current ETag.

{
  "error": {
    "code": "revision_conflict",
    "message": "The number changed since you read it. Read it again and retry with the new ETag."
  }
}