---
title: >-
  Let your AI agent buy its own phone number: agentic provisioning with hard
  limits
description: >-
  Your AI agent can search, buy, and wire a Nairobi number over MCP — bounded by
  scoped keys, a prepaid wallet, confirmation gates, and idempotency.
summary: >-
  Agentic provisioning on Sautikit MCP: search_available_numbers, claim_number,
  update_number_routing — with scoped API keys, wallet spend limits,
  confirmations, and idempotent retries.
date: 2026-09-15T00:00:00.000Z
type: blog
---


## Summary

The next thing your AI agent will ask for is a phone number. Payment providers have already built the rails for agents that buy things — Stripe's agent-scoped payment tokens, Google's bounded payment mandates — and they all converged on the same principle: let the agent act, but enforce the limits in the API, not the prompt. This post walks through a real agentic provisioning flow on Sautikit's hosted MCP server — `search_available_numbers` → `claim_number` → `validate_voice_actions` → `update_number_routing` — and the four guardrails that make it safe: scoped API keys, a prepaid wallet as the hard spend limit, confirmation gates on money tools, and idempotency keys so a retried claim can never buy two numbers.

## Agents that buy things: how the industry converged

If "an AI agent spending money" sounds reckless, look at who is building for it.

In September 2025, Stripe and OpenAI announced the Agentic Commerce Protocol, the standard behind Instant Checkout in ChatGPT. The payment primitive underneath, Stripe's Shared Payment Token, is a precise piece of design: a token scoped to a single business, capped by time or amount, revocable at any moment, and monitored by webhooks. The agent never touches the buyer's card credentials, and at launch every purchase still required human review (per Stripe's announcements; details as of mid-2026). Stripe's newer Projects work reportedly goes further — agents provisioning and paying for services under hard, API-enforced spending limits, with allowlisted catalogs and human-approval thresholds above a set value; check Stripe's docs for current specifics. Google's Agent Payments Protocol (AP2) formalizes the same idea as a "mandate": you grant the agent a bounded authority — this much money, these categories, this week — and the network enforces the bound on the token itself, so the agent cannot exceed it even if it tries.

Three independent teams, one conclusion: don't ask the model to behave — make the infrastructure incapable of letting misbehaviour matter. The limit lives below the agent.

Phone numbers are a natural next target for this pattern. They are cheap, they are provisioned by API, and an agent that can buy and wire a number can stand up an entire voice workflow — an IVR, a hotline, a support line — without a human clicking through a console. Here is what that looks like on Sautikit, and how each payment-industry guardrail maps onto a voice platform.

## "Get me a Nairobi number and wire it to this IVR"

The whole flow is one instruction to Claude — through the claude.ai connector or Claude Code, against the hosted MCP server at `mcp.sautikit.com/mcp` ([setup guide](/developers/guides/connect-mcp)):

> "Get me a Nairobi number and wire it to the IVR at https://ivr.example.com/voice."

Four tools fire:

1. **`search_available_numbers`** — queries the Kenyan inventory. Each row carries its monthly price and per-minute rates in minor units, so the agent can reason about cost before committing ("this one is KES 100/month ex. VAT and inbound is free").
2. **`claim_number`** — the money move, and the moment the agent *stops*. The tool carries a money-spending MCP annotation, so your client asks you to confirm before anything is debited. You approve; Sautikit atomically debits the prorated first month from your prepaid wallet and returns the number with its renewal date. Sautikit pricing (as of 2026-06-30): KES 100/month ex. VAT — KES 116 incl. VAT — per local Nairobi number; see [/pricing](/pricing) for current rates. The agent has committed roughly the price of lunch, not a procurement cycle.
3. **`validate_voice_actions`** — before wiring anything, the agent checks the IVR handler's response document against the exact JSON schema the runtime enforces. This tool is local to the MCP server: no API call, no cost, no scope required. LLMs are enthusiastic inventors of plausible-looking fields; this catches them while you are still writing the handler, before any real call hits it.
4. **`update_number_routing`** — points the number's `voice_callback_url` at the handler and `events_url` at your event sink. Done. Calls to the new number now reach the IVR.

The document validated in step 3 is ordinary voice-actions JSON — say, a one-level menu:

```json
{
  "actions": [
    {
      "getDigits": {
        "timeout": 8,
        "numDigits": 1,
        "nested": [
          { "say": { "text": "Thanks for calling. Press 1 for sales, 2 for support.", "language": "en-KE" } }
        ]
      }
    },
    { "say": { "text": "We did not receive a selection. Goodbye.", "language": "en-KE" } },
    { "hangup": {} }
  ]
}
```

Four tool calls, one human confirmation, zero dashboard tabs. If the agent later decommissions the line, `release_number` is annotated destructive — so that stops for confirmation too.

## What the tools actually call

There is no private agent backdoor: every MCP tool wraps the public REST API — the same endpoints, auth, scopes, rate limits, and billing your own code uses. The three network calls underneath the conversation:

```js
// 1. Search inventory
const search = await fetch(
  "https://api.sautikit.com/v1/numbers/available?country=KE&limit=5",
  { headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` } },
);
const { available } = await search.json(); // rows carry monthly + per-minute rates in minor units

// 2. Claim — the money move. The Idempotency-Key makes retries safe (more below).
const inventoryId = "..."; // id of the chosen row from the search response
const claim = await fetch(
  `https://api.sautikit.com/v1/numbers/${inventoryId}/claim`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
      "Idempotency-Key": "provision:support-line:2026-09",
    },
  },
);
// 201 → { number, prorated_minor, monthly_minor, currency, next_renewal_at }
// 402 → wallet.insufficient_balance (the wallet said no)
// 409 → another workspace claimed this inventory row first

// 3. Route it
await fetch(`https://api.sautikit.com/v1/numbers/${numberId}/routing`, {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    voice_callback_url: "https://ivr.example.com/voice",
    events_url: "https://ivr.example.com/events",
  }),
});
```

The routing endpoint is agent-friendly by virtue of being API-friendly: omitted fields are left untouched, an empty string clears a field, and a malformed URL is rejected with `400 numbers.invalid_routing_url` — a clean, machine-readable error the agent can read and correct on the next turn.

## The prepaid wallet is the natural spend limit

Stripe caps agent tokens by amount; AP2 bounds mandates at the network. Sautikit's equivalent has been in the product since day one: the prepaid, KES-denominated [wallet](/developers/concepts/wallet).

Every paid action — a claimed number, an outbound calling minute — debits the wallet, and the wallet cannot go below zero. Claims fail with `402 wallet.insufficient_balance`; calls are blocked at zero balance. Which means a misbehaving agent can never cost you more than the balance you funded. Fund the agent's workspace with KES 2,000 and the worst possible outcome of any bug, loop, or prompt injection is KES 2,000 — not a five-figure surprise invoice at month-end, which is the failure mode of a card-on-file postpaid platform.

Two design choices sharpen this:

- **Money-in is not a tool.** Top-ups, API-key minting, and secret rotation are deliberately absent from the MCP tool set. An agent can spend its wallet down; it can never refill it, mint itself broader credentials, or escalate. Replenishment stays with humans, over M-Pesa or Paystack.
- **The agent can see its own budget.** `get_wallet_balance` costs nothing, so a well-prompted agent checks the balance before proposing spend — and you can subscribe a `wallet.low_balance` webhook as an independent alarm.

A prompt that says "don't spend more than KES 500" is a suggestion. A wallet holding KES 500 is a law.

## Scoped keys for autonomous agents, OAuth for humans

There are two ways into the MCP server, and they map onto two kinds of user.

**Humans get OAuth.** Add Sautikit as a custom connector on claude.ai and you sign in through Sautikit's authorization server, pick a workspace, and approve scope groups — no API key ever leaves the dashboard. Revoke the grant any time under Settings → Connected apps.

**Autonomous agents get scoped API keys.** A headless agent — Claude Code in CI, a cron-driven provisioning job — authenticates with a key you mint, and the key should carry only the scopes the job needs. For a number-provisioning agent that is `numbers.read`, `numbers.claim`, and `wallet.read` — nothing else. The MCP server filters its tool list by the key's scopes, so an agent whose key lacks `broadcasts.manage` never even sees `start_broadcast`, let alone calls it; agent and broadcast scopes are additionally enforced server-side, with enforcement rolling out across the remaining scope groups. Revocation bites immediately: keys are checked against a deny-list on every request, so a revoked key dies on its next call.

This mirrors where the MCP specification itself went — the June 2025 revision made every MCP server an OAuth 2.1 resource server, and the November 2025 revision standardized baseline scope names — and it mirrors Stripe's split between what a human approves interactively and what a bounded token lets an agent do alone.

## Confirmation gates: the human stays in the loop

Scopes decide what an agent *may* do; confirmation decides what it does *right now*. Sautikit annotates every money-spending tool — `claim_number`, `place_call`, `start_broadcast`, `send_agent_call` — so MCP clients prompt the human before running them, and destructive tools like `release_number` the same way. Claude surfaces these as approval prompts with allow-once versus always-allow choices, and on enterprise plans an org owner can pin per-tool policy to Always allow, Needs approval, or Blocked (per Anthropic's support docs, as of mid-2026).

That is the posture Stripe launched agentic checkout with — human review on every purchase, relaxing toward user-set limits over time. Start every new agent at "confirm everything that spends," and promote individual tools to always-allow only after you have watched the agent behave.

## Idempotency: a retry must never buy twice

The quietest failure mode in agentic systems is not malice — it is retries. An agent calls `claim_number`, the response times out, and the agent, reasonably, tries again. Without protection you now own two phone numbers.

The claim endpoint takes an `Idempotency-Key` header (up to 64 characters). If you are building your own agent loop against the REST API, always send one, keyed on business identity rather than randomness — `provision:support-line:2026-09` — so any retry of the same intent carries the same key. The semantics:

- Deduplication is scoped to the tuple of workspace, HTTP method, path, and key, with a 24-hour TTL.
- First request wins; a repeat within the TTL returns the original status and body, flagged with `X-Idempotency-Replayed: true`. The agent gets the number it already bought, not a second one.
- Success and client-error responses (2xx–4xx) are cached; a 5xx is never stored, so retrying a genuine server failure re-executes the request instead of replaying the failure.
- Reusing a key with a different body returns `idempotency.conflict`: reuse the original body, or send a new key.

Note the separate 409 in the claim flow: that is the inventory race — another workspace claimed the same number first — not an idempotency replay. An agent should treat a 409 as "search again, pick another number" and a replayed 201 as "already done, move on."

## Where the rest of the field is

The direction is not unique to us; it is where the whole industry points. But as of this writing the implementations differ sharply — [we surveyed it in July](/blog/run-your-call-center-from-claude), and the same split still holds. Twilio's hosted MCP server only reads: two tools that search and retrieve API documentation, with no account actions (public beta since May 2026, per Twilio's docs). Telnyx ships an MCP server that does perform account actions, including number purchase, but it is a local install running on your machine with a raw API key (per Telnyx's docs, as of mid-2026). Infobip hosts remote MCP servers in beta with OAuth support, with its messaging channels leading the rollout (per Infobip's docs, as of mid-2026).

When we last surveyed the field (July 2026), Sautikit was the only voice-first CPaaS we could find with a hosted, OAuth-protected MCP server and live call-event tools — verify each vendor's current state before you plan around it. The provisioning flow above is not a roadmap slide — it works from a claude.ai chat window today, bounded by a wallet you control. The full tool reference lives on the [MCP page](/mcp).

## Get started

1. [Create a Sautikit workspace](/) — this time, don't claim the number yourself.
2. Top up over **M-Pesa**: KES billing, no card. The balance is your agent's spend limit.
3. Connect the MCP server ([setup guide](/developers/guides/connect-mcp)) and ask your agent for a Nairobi number.

**[Start with Sautikit →](/)** &nbsp;·&nbsp; **[See pricing →](/pricing)** &nbsp;·&nbsp; **[Need SMS, WhatsApp & an agent desk? Helloduty →](https://helloduty.com)**
