mcpField
P
phone-numbers

phone-numbers

github.com/zavudevOther1238 installsUpdated 2d ago
Repository

About phone-numbers

phone-numbers is a Zavu skill for searching, purchasing, and managing phone numbers through the API. It helps developers handle number types, carrier-specific regulatory requirements, and sender assignment.

Key features

  1. 01Search numbers by country and type
  2. 02Purchase and manage numbers
  3. 03Check carrier requirements
  4. 04Submit addresses and documents
  5. 05Assign numbers to senders

Use cases

  1. 01Find numbers matching a region or pattern
  2. 02Buy a number with required compliance details
  3. 03Track number regulatory approval
  4. 04Connect a number to a sender

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/zavudev/zavu-skills --skill phone-numbers

After installing, type/phone-numbersin Claude Code to confirm it loaded.

Details

From the project README

Phone Numbers

When to Use

Use this skill when building code to search for, purchase, or manage phone numbers. Covers available number search, purchasing, regulatory requirements, and sender assignment.

Search Available Numbers

const result = await zavu.phoneNumbers.searchAvailable({
  countryCode: "US",
  type: "local",
  contains: "555",
  limit: 10,
});

for (const number of result.items) {
  console.log(number.phoneNumber);       // +15551234567
  console.log(number.friendlyName);      // (555) 123-4567
  console.log(number.locality);          // San Francisco
  console.log(number.capabilities);      // { sms: true, voice: true, mms: true }
  console.log(number.pricing.monthlyPrice); // 1.25
  console.log(number.pricing.isFreeEligible); // true (paid plans include one US number, once per account)
}

Python:

result = zavu.phone_numbers.search_available(
    country_code="US",
    type="local",
    contains="555",
    limit=10,
)
for number in result.items:
    print(number.phone_number, number.pricing.monthly_price)

Go:

result, err := client.PhoneNumbers.SearchAvailable(context.TODO(), zavudev.PhoneNumberSearchAvailableParams{
    CountryCode: "US",
    Type:        zavudev.PhoneNumberTypeLocal,
    Contains:    zavudev.String("555"),
    Limit:       zavudev.Int(10),
})
for _, number := range result.Items {
    fmt.Println(number.PhoneNumber, number.Pricing.MonthlyPrice)
}

Ruby:

result = client.phone_numbers.search_available(country_code: "US", type: "local", contains: "555", limit: 10)
result.items.each { |number| puts "#{number.phone_number} #{number.pricing.monthly_price}" }

PHP:

$result = $client->phoneNumbers->searchAvailable(
    countryCode: 'US', type: 'local', contains: '555', limit: 10,
);
foreach ($result->items as $number) {
    echo $number->phoneNumber . ' ' . $number->pricing->monthlyPrice . "\n";
}

Purchase Phone Number

const result = await zavu.phoneNumbers.purchase({
  phoneNumber: "+15551234567",
  name: "Primary Line",
});
console.log(result.phoneNumber.id);     // pn_abc123
console.log(result.phoneNumber.status); // "active"

Buying numbers requires a paid plan. The Free plan cannot purchase phone numbers (the API returns 402 with code paid_plan_required). A paid plan includes one number at no charge, once per account: it must be a US or Canadian number (a +1 number) costing $20 a month or less. pricing.isFreeEligible in search results marks the numbers that qualify.

Phone Number Types

TypeDescription
localGeographic number, tied to a city or region
nationalNon-geographic number, valid country-wide
mobileMobile-prefix number. In several countries it is the only type in stock, and in some markets the only type that can receive SMS
tollFreeToll-free number

Manage Phone Numbers

// List owned numbers
const numbers = await zavu.phoneNumbers.list({ status: "active" });
for (const pn of numbers.items) {
  console.log(pn.id, pn.phoneNumber, pn.status);
}

// Get details
const pn = await zavu.phoneNumbers.retrieve("pn_abc123");

// Rename
await zavu.phoneNumbers.update("pn_abc123", { name: "Support Line" });

// Assign to sender
await zavu.phoneNumbers.update("pn_abc123", { senderId: "snd_abc123" });

// Unassign from sender
await zavu.phoneNumbers.update("pn_abc123", { senderId: null });

// Release number (must not be assigned to a sender)
await zavu.phoneNumbers.release("pn_abc123");

Regulatory Requirements

Whether a number needs regulatory information (an address, a document, or text) is decided per number by the carrier, not by a fixed country list. The purchase looks the requirements up for the exact number before charging anything. The whole flow works over the API:

  1. GET /v1/phone-numbers/requirements?phoneNumber=%2B4930123456&type=local (encode + as %2B; an unencoded + also works). The response is the list the purchase of that number validates against; when the number's own requirements cannot be resolved, it is the list for its country and type. Empty items means the number needs nothing: buy it normally. A 502 requirements_unavailable means the lookup failed: retry, never treat it as "no requirements" (US and Canadian numbers are sold as numbers without requirements even then).
  2. For each requirementTypes[] entry: address -> create one with POST /v1/addresses in the same project (firstName and lastName are required) and use its id; document -> upload one (POST /v1/documents, see below) and use its id; textual -> the text itself; action -> send nothing for it. One entry per id.
  3. Purchase with type (required when sending requirements) and regulatoryRequirements. These fields are not in the SDK yet, so call REST:
curl -X POST https://api.zavu.dev/v1/phone-numbers \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+4930123456",
    "type": "local",
    "regulatoryRequirements": [
      { "requirementType": "<requirementTypes[].id>", "fieldValue": "<address id>" },
      { "requirementType": "<requirementTypes[].id>", "fieldValue": "<document id>" }
    ]
  }'
  1. The number is bought and billed at once with regulatoryStatus: "pending_review". It cannot send messages or place calls until regulatoryStatus is "approved". The status is re-checked every 6 hours: poll GET /v1/phone-numbers/{phoneNumberId}.
  2. Assign it to a sender (PATCH /v1/phone-numbers/{phoneNumberId} with senderId), before or after approval. A number assigned while under review is connected to that sender when approved, retried until it succeeds; a sender created over the API is set up for SMS as part of the assignment. A rejected number cannot be assigned (400). A number that stays pending_review for long needs support.

Errors, none of which charge anything:

ResponseMeaning
400 regulatory_compliance_requiredThe number needs information and none was sent or can be reused. details.missingRequirements lists { id, name, type }.
400 invalid_requestA required id is missing, an id is unknown or repeated, an address/document is not from this project or was rejected, or it could not be registered for review. details names the requirement.
400 number_unavailableThe number is gone, or not listed under the type sent.
502 requirements_unavailableThe requirements could not be looked up. Retry. Not returned for US and Canadian numbers.

Reuse: what you submitted is kept for your project under the number's country and type. A later purchase there may omit regulatoryRequirements, but only if what is kept still covers every requirement of that number and every address and document in it belongs to the project; otherwise it returns 400 regulatory_compliance_required.

// Requirements for a country and type (the SDK has no per-number variant yet;
// the purchase checks the exact number, so prefer the REST call in step 1)
const requirements = await zavu.phoneNumbers.requirements({
  countryCode: "DE",
  type: "local",
});

for (const req of requirements.items) {
  for (const rt of req.requirementTypes) {
    console.log(`${rt.id} ${rt.name}: ${rt.type} - ${rt.description}`);
  }
}
Requirement Types
TypeDescription
textualText field (name, business name)
addressPhysical address
documentIdentity document (passport, ID, etc.)
actionAction to perform
Create Regulatory Address
const address = await zavu.addresses.create({
  firstName: "John",
  lastName: "Doe",
  streetAddress: "123 Main St",
  locality: "Berlin",
  postalCode: "10115",
  countryCode: "DE",
});
console.log(address.address.status); // "pending"

// List and inspect
const addresses = await zavu.addresses.list();
const one = await zavu.addresses.retrieve("addr_abc123");
await zavu.addresses.delete("addr_abc123");
Upload Regulatory Document

Three steps: get a one-time upload URL, POST the file bytes to it, then create the document record with the storageId the upload returned. The API does not check the file's format or size itself; the dashboard uploader accepts JPEG, PNG or PDF up to 10MB, so stay within that. Creating the record forwards the file to the carrier for verification in the same request: if it is refused, the call returns 400 and no record is kept.

A created address comes back pending and a created document uploaded, and the API does not move either one to verified or rejected afterwards. A purchase does not need them to be: approval is tracked on the number's regulatoryStatus, so poll that instead.

// 1. Get upload URL (POST /v1/documents/upload-url)
const upload = await zavu.regulatoryDocuments.uploadURL();

// 2. POST the file to the presigned URL; the response carries the storageId
const uploaded = await fetch(upload.uploadUrl, {
  method: "POST",
  body: file, // a Blob or File
  headers: { "Content-Type": file.type },
});
const { storageId } = await uploaded.json();

// 3. Create document record (POST /v1/documents)
const doc = await zavu.regulatoryDocuments.create({
  name: "Passport Scan",
  documentType: "passport",
  storageId,
  mimeType: file.type,
  fileSize: file.size,
});
console.log(doc.document.status); // "uploaded"

// List, inspect, delete (verified documents cannot be deleted)
const docs = await zavu.regulatoryDocuments.list();
const detail = await zavu.regulatoryDocuments.retrieve("doc_xyz789");
await zavu.regulatoryDocuments.delete("doc_xyz789");
Document Types
TypeDescription
passportPassport
national_idNational ID card
drivers_licenseDriver's license
utility_billUtility bill
tax_idTax ID document
business_registrationBusiness registration
proof_of_addressProof of address
otherOther document

10DLC (US A2P Messaging)

For US application-to-person messaging at scale, you may need 10DLC registration:

  1. Brand Registration - Register your business identity
  2. Campaign Registration - Register your messaging use case
  3. Number Assignment - Assign registered numbers to campaigns

Registration charges The Campaign Registry's own fees from your balance, passed through at cost, as two separate charges:

ChargeOne-timeMonthly
Brand (POST /v1/10dlc/brands/{brandId}/submit)$4—
Campaign, standard use cases (POST /v1/10dlc/campaigns/{campaignId}/submit)$15$10 while active
Campaign, LOW_VOLUME use case$2$2 while active

One-time fees are charged at submission and refunded, once, if the carrier rejects the registration or it never reaches the carrier. The monthly campaign fee is the published one above, charged from the first day the campaign is approved.

Statuses move on their own: pending campaigns are re-read from the carrier every 30 minutes, so polling GET /v1/10dlc/campaigns/{campaignId} is enough. Phone number assignments are pending, active or failed (the carrier refused the number; it can be assigned again); a removed assignment is no longer listed. Brand ein must be 9 digits (XX-XXXXXXX) and website a full URL, or the request is refused with 400.

10DLC registration is managed through the Zavu dashboard. Contact support for high-volume US messaging requirements.

Constraints

  • Phone number purchase requires a paid plan (402 paid_plan_required on Free); a paid plan includes one number, once per account (Pro: a US number)
  • Phone number name: max 100 characters
  • Phone numbers must be unassigned from senders before release
  • Addresses are created pending and documents uploaded and stay that way; poll the number's regulatoryStatus, not these
  • Country code: 2-letter ISO format (e.g., US, DE, BR)
  • Search results: max 50 per request
  • A number with requirements is bought with type + regulatoryRequirements and starts regulatoryStatus: "pending_review"; it cannot send or call until approved, then assign it to a sender

Comments

Markdown supported