Ringkit docs

Build on the phone layer

Ringkit gives a business a phone number and an AI receptionist that knows it. The receptionist answers every call, transfers callers to the business's own team when the rules say so, and takes a message otherwise. Everything the dashboard does is an HTTP call, so a developer or an AI agent can set a business up end to end, then hand it to the owner. There's no plan: $1.99/month per number and $0.20 a minute while the receptionist is on a call.

There are no SDKs yet. Use curl or fetch, generate a client from the OpenAPI document, or connect an agent over MCP.

01 · Quickstart

Quickstart

Ten calls from nothing to a funded business with a live number. Most people finish in under an hour. You don't need an account or a key to start: step 1 returns one. The examples use a made-up business, Hidden Valley Builders, a remodeling contractor in Ventura County.

1. Create the business

No key needed. The response carries the business and its API key. The key is shown once: store it now.

Request
curl https://api.ringkit.com/v1/businesses \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hvb-create-1" \
  -d '{
    "name": "Hidden Valley Builders",
    "timezone": "America/Los_Angeles",
    "vertical": "remodeling",
    "website": "hiddenvalleybuilders.example",
    "ownerEmail": "[email protected]",
    "ownerPhone": "+18055550134"
  }'
Response · 201
{
  "business": {
    "id": "biz_4f8k2m9q7x1c3v5n",
    "name": "Hidden Valley Builders",
    "timezone": "America/Los_Angeles",
    "vertical": "remodeling",
    "website": "https://hiddenvalleybuilders.example",
    "ownerEmail": "[email protected]",
    "ownerPhone": "+18055550134",
    "notify": [],
    "status": "active",
    "knowledgeTier": "starter",
    "voice": "Ashley",
    "createdVia": "api",
    "createdAt": "2026-10-27T17:04:11.000Z",
    "updatedAt": "2026-10-27T17:04:11.000Z"
  },
  "apiKey": "rk_…"
}
export RINGKIT_API_KEY=rk_…
export BIZ=biz_4f8k2m9q7x1c3v5n

New businesses get a $5 trial credit for calls (one per owner email). It doesn't pay for the Pro plan, and pays for a number only during the 7-day trial on a business's first number once the owner has signed in.

2. Teach it the business

Pick any mix of four ways. The fastest is a background import of the website:

curl https://api.ringkit.com/v1/businesses/$BIZ/knowledge/imports \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hiddenvalleybuilders.example" }'
Response · 202
{
  "id": "kimp_9w2r5t8y1u4i7o3p",
  "url": "https://hiddenvalleybuilders.example",
  "status": "queued",
  "createdAt": "2026-10-27T17:04:20.000Z"
}

Poll GET /v1/businesses/$BIZ/onboarding until latestImport.status is done or failed. The import fills only fields that are still empty and never touches uploaded documents.

{
  "step": "number",
  "hasNumber": false,
  "knowledgeReady": true,
  "latestImport": {
    "id": "kimp_9w2r5t8y1u4i7o3p",
    "url": "https://hiddenvalleybuilders.example",
    "status": "done",
    "estimatedTokens": 3120,
    "found": { "hours": true, "services": 6, "faqs": 4, "policies": 2, "summary": true },
    "createdAt": "2026-10-27T17:04:20.000Z",
    "finishedAt": "2026-10-27T17:05:02.000Z"
  }
}

Or write the profile yourself with PUT /v1/businesses/$BIZ/knowledge (a full replace; PATCH merges):

{
  "summary": "Licensed general contractor in Ventura County: kitchen and bath remodels, additions and repairs.",
  "hours": {
    "regular": {
      "mon": [{ "open": "07:00", "close": "17:00" }],
      "tue": [{ "open": "07:00", "close": "17:00" }],
      "wed": [{ "open": "07:00", "close": "17:00" }],
      "thu": [{ "open": "07:00", "close": "17:00" }],
      "fri": [{ "open": "07:00", "close": "16:00" }],
      "sat": [],
      "sun": []
    }
  },
  "services": [
    { "name": "Kitchen remodel", "description": "Design, permits and build", "priceNote": "free estimates" },
    { "name": "Repairs", "priceUsd": 95, "priceNote": "per hour labor" }
  ],
  "faqs": [
    { "q": "Do you give free estimates?", "a": "Yes, on-site estimates are free anywhere in Ventura County." }
  ],
  "policies": [
    { "topic": "Deposits", "text": "Remodels start with a 10% deposit once the estimate is signed." }
  ]
}

Or upload documents (PDF, DOCX, TXT, Markdown, HTML; 10 MB each) with POST /v1/businesses/$BIZ/knowledge/documents. Text is extracted at upload and used on every call.

{ "fileName": "price-list.pdf", "contentBase64": "JVBERi0xLjcK…" }

Or talk it through with the setup chat. POST /v1/businesses/$BIZ/setup-chat starts a conversation and returns its id (schat_…) with the assistant's opening message. Answer with POST /v1/businesses/$BIZ/setup-chat/:conversationId/messages; the assistant saves hours, services, policies and transfers as you go and lists each write in messages[].writes.

{ "text": "We're closed weekends. Emergencies like a burst pipe go straight to Mike's cell." }

3. Pick a voice

GET /v1/voices lists the voices and the default. Set one on the business:

curl -X PATCH https://api.ringkit.com/v1/businesses/$BIZ \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "voice": "Sarah" }'

4. Set the routing policy

Decide which calls the receptionist transfers to the business's own team, and to which number. PUT replaces the whole policy; fields you leave out take their defaults.

curl -X PUT https://api.ringkit.com/v1/businesses/$BIZ/routing \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "ai_only",
    "transferTargets": [
      { "id": "mike", "label": "Mike (owner)", "number": "+18055550134", "whenToUse": "Emergencies and new jobs over $10k" }
    ]
  }'

5. Fund the wallet

The wallet is prepaid. A top-up returns a Stripe Checkout link for a person to open and pay; the minimum is $10. Paying is the only step that needs a person, and the number in the next step needs it, so ask early.

curl https://api.ringkit.com/v1/businesses/$BIZ/wallet/top-ups \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hvb-topup-1" \
  -d '{ "amountCents": 2000 }'
Response · 200
{
  "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_…",
  "balanceCents": 500
}

Once they've paid, GET /v1/businesses/$BIZ/wallet shows balanceCents (trial credit included) and trialCreditCents (the unspent trial part).

6. Get a number

curl "https://api.ringkit.com/v1/numbers/search?areaCode=805" \
  -H "Authorization: Bearer $RINGKIT_API_KEY"
{
  "numbers": [
    { "number": "+18055550142", "region": "Ventura, CA", "monthlyCents": 199, "provider": "livekit-telnyx" }
  ]
}
curl https://api.ringkit.com/v1/businesses/$BIZ/numbers \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hvb-number-1" \
  -d '{ "number": "+18055550142" }'

A number is $1.99/month from the wallet and needs $2 of paid credit (trial credit doesn't count, except that a business's first number can run a 7-day trial on it once the owner has signed in): without it you get 402 funding_required, with availableCents, requiredCents and trialCreditCents in details. Nobody has to sign in first. Publish the new number, or keep the one customers already know and forward it to the Ringkit number with your carrier's call forwarding.

7. Try it

There is no sandbox. Test in text first: POST /v1/businesses/$BIZ/simulate runs the real receptionist with typed caller lines. An empty callerText gets the greeting; send the returned callId with each next line.

{ "callerText": "" }
{ "callId": "call_2h6j9k3l7z1x5c8v", "callerText": "Do you do free estimates in Thousand Oaks?" }
{
  "callId": "call_2h6j9k3l7z1x5c8v",
  "aiText": "We do. Estimates are free anywhere in Ventura County, Thousand Oaks included. Can I take your name and a good number to call you back?",
  "ended": false,
  "intent": "estimate_request",
  "escalated": false
}

Then a real call. Ringkit only rings numbers the business configured (ownerPhone, a transfer target or an SMS notify target) and has verified. Verify places a call that reads a 6-digit code to whoever answers (their consent to be called); confirm sends it back:

POST /v1/businesses/$BIZ/destinations/verify    { "number": "+18055550134" }
POST /v1/businesses/$BIZ/destinations/confirm   { "number": "+18055550134", "code": "482913" }
POST /v1/businesses/$BIZ/test-call              { "to": "+18055550134" }

Test calls ring from the business's Ringkit number, run on the AI and bill at the AI rate. They are capped at 5 per business per day (and 3 outbound calls per number per day).

8. Read calls and summaries

GET /v1/businesses/$BIZ/calls?limit=25 returns { items, nextCursor }; pass ?cursor= for the next page until nextCursor is null. One call, with its transcript and summary:

curl https://api.ringkit.com/v1/businesses/$BIZ/calls/call_7n2k9x4m1q8c5v3b \
  -H "Authorization: Bearer $RINGKIT_API_KEY"
{
  "call": {
    "id": "call_7n2k9x4m1q8c5v3b",
    "businessId": "biz_4f8k2m9q7x1c3v5n",
    "direction": "inbound",
    "from": "+18055550173",
    "to": "+18055550142",
    "status": "completed",
    "provider": "livekit-telnyx",
    "providerCallId": "SCL_4yQd9KpW2mVt",
    "startedAt": "2026-10-27T18:12:03.000Z",
    "answeredAt": "2026-10-27T18:12:04.000Z",
    "endedAt": "2026-10-27T18:15:37.000Z",
    "endReason": "caller_hangup",
    "segments": [
      { "handler": "ai", "startedAt": "2026-10-27T18:12:04.000Z", "endedAt": "2026-10-27T18:15:37.000Z" }
    ],
    "summary": {
      "intent": "estimate_request",
      "outcome": "message_taken",
      "callerName": "Dana Ruiz",
      "summary": "Dana wants an estimate to extend the Oakridge kitchen remodel into the dining room. Free after 3pm on weekdays.",
      "message": {
        "callerName": "Dana Ruiz",
        "callbackNumber": "+18055550173",
        "message": "Estimate for extending the Oakridge kitchen remodel into the dining room. Call after 3pm.",
        "urgency": "normal"
      },
      "followUpNeeded": true,
      "sentiment": "positive"
    },
    "billedCents": 51
  },
  "transcript": [
    { "seq": 0, "speaker": "ai", "text": "Thanks for calling Hidden Valley Builders. How can I help?", "at": "2026-10-27T18:12:04.000Z" },
    { "seq": 1, "speaker": "caller", "text": "Hi, I'd like to get an estimate on extending a kitchen remodel.", "at": "2026-10-27T18:12:09.000Z" }
  ]
}

9. Subscribe to webhooks

curl https://api.ringkit.com/v1/businesses/$BIZ/webhooks \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hiddenvalleybuilders.example/hooks/ringkit",
    "events": ["call.completed", "message.taken", "balance.low"]
  }'

The response is { id, secret }. The signing secret is shown once; see Webhooks to verify deliveries.

10. Hand it to the owner (optional)

The line already runs; nothing above needed the owner to sign in. When they want the dashboard, this emails them a sign-in link:

curl https://api.ringkit.com/v1/businesses/$BIZ/owners/invite \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]" }'
{ "ok": true, "email": "[email protected]", "magicLinkSent": true }

Leave out email to use the business's ownerEmail; 5 invites per business per hour. When the owner signs in at app.ringkit.com, the business gets ownerSignedInAt and the dashboard shows the business as set up by an agent and lets them rotate the agent's key. The agent can keep running the line either way.

The same thing in JavaScript

No SDK needed. A small fetch helper covers every route:

JavaScript
const API = "https://api.ringkit.com/v1";

async function ringkit(method, path, { key, body, idempotencyKey } = {}) {
  const res = await fetch(API + path, {
    method,
    headers: {
      "Content-Type": "application/json",
      ...(key ? { Authorization: `Bearer ${key}` } : {}),
      ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message} (${json.error.hint ?? json.error.docs})`);
  return json;
}

const { business, apiKey } = await ringkit("POST", "/businesses", {
  idempotencyKey: "hvb-create-1",
  body: {
    name: "Hidden Valley Builders",
    timezone: "America/Los_Angeles",
    ownerEmail: "[email protected]",
  },
});

await ringkit("POST", `/businesses/${business.id}/knowledge/imports`, {
  key: apiKey,
  body: { url: "https://hiddenvalleybuilders.example" },
});

const { checkoutUrl } = await ringkit("POST", `/businesses/${business.id}/wallet/top-ups`, {
  key: apiKey,
  idempotencyKey: "hvb-topup-1",
  body: { amountCents: 2000 },
});
console.log("Open to fund the wallet:", checkoutUrl);
02 · Agent quickstart

Agent quickstart

Written for an AI agent to follow as-is. Base URL https://api.ringkit.com/v1. Send Content-Type: application/json on every request, Authorization: Bearer <apiKey> from step 2 on, and a fresh Idempotency-Key on every POST (reuse it only when retrying that same request). On an error, read error.hint and do what it says. Each Stop means: wait for the human before going on.

1. Ask the human for: business name, timezone (IANA, e.g. America/Los_Angeles),
   owner email, owner mobile number (E.164), website (optional).

2. POST /businesses
   {"name": "...", "timezone": "...", "ownerEmail": "...", "website": "..."}
   Leave out ownerPhone: it must be a verified number (403 destination_unverified on create).
   Step 5 makes it a transfer target and step 9 verifies it.
   Save: business.id as BIZ, apiKey as KEY (shown once).
   Now ask the human to fund the wallet (step 7): it is the only thing they must do, and the number needs it.

3. POST /businesses/BIZ/knowledge/imports   {"url": "<website>"}
   Then GET /businesses/BIZ/onboarding every 10 s until latestImport.status is "done" or "failed".
   No website, or it failed: PUT /businesses/BIZ/knowledge with summary, hours, services, faqs, policies
   from what the human told you in step 1.

4. GET /voices. Pick one (or keep "default").
   PATCH /businesses/BIZ   {"voice": "<id>"}

5. PUT /businesses/BIZ/routing
   {"mode": "ai_only", "transferTargets": [{"id": "owner", "label": "Owner", "number": "<ownerPhone>",
    "whenToUse": "the caller asks for the owner or has an emergency"}]}

6. POST /businesses/BIZ/simulate   {"callerText": ""}
   Then {"callId": "<callId>", "callerText": "<a realistic caller question>"} two or three times.
   Billed at the AI rate; the trial credit covers it. Fix gaps with PATCH /businesses/BIZ/knowledge.

7. POST /businesses/BIZ/wallet/top-ups   {"amountCents": 2000}
   Save: checkoutUrl.
   Stop: ask the human to open checkoutUrl and pay. Then GET /businesses/BIZ/wallet and confirm
   balanceCents - trialCreditCents is at least 200.

8. GET /numbers/search?areaCode=<3 digits>. Pick one.
   POST /businesses/BIZ/numbers   {"number": "<picked>"}
   Save: number. No sign-in is needed. On 402 funding_required, the payment hasn't landed: back to step 7.

9. POST /businesses/BIZ/destinations/verify   {"number": "<ownerPhone>"}
   Stop: ask the human for the 6-digit code the call read (their consent to be called).
   POST /businesses/BIZ/destinations/confirm   {"number": "<ownerPhone>", "code": "<code>"}
   POST /businesses/BIZ/test-call   {"to": "<ownerPhone>"}
   Ask the human to answer and talk to the receptionist.

10. GET /businesses/BIZ/calls?limit=5. Show the human the newest call's summary.

11. Optional hand-over: POST /businesses/BIZ/owners/invite   {}
    Tell the human their Ringkit number and that the email signs them in to https://app.ringkit.com
    whenever they want to take over. You can keep running the line either way. Done.

The same steps are in llms.txt; llms-full.txt adds every route and field.

03 · Concepts

Concepts

Eight ideas cover everything the API does.

business

Business

The account everything hangs off: name, timezone, owner email and phone, voice, status. Creating one needs no key and returns its API key. Ids are prefixed (biz_, call_, num_, whk_).

knowledge

Knowledge profile

What the receptionist knows: summary, hours, address, services and prices, staff, FAQs, policies, things never to say, greeting. Plus uploaded documents:

  • Starter: 5 documents, 50k tokens, free
  • Pro: 100 documents, 500k tokens, $5/month from the wallet
routing

Routing policy

Where calls go after the receptionist answers. mode is ai_only: the AI receptionist answers every call. Then:

  • transferTargets: your own team's numbers, each with whenToUse, so the receptionist knows when to transfer
  • No one free, or no rule matches: it takes a message for you

Ringkit doesn't staff calls itself: the ai_first and human_first modes, which brought in Ringkit's own live reps, aren't offered right now and run as ai_only.

number

Numbers

US local numbers, searched by area code and ordered into the business for $1.99/month from the wallet (the first can run a 7-day trial). Calls to the number reach the receptionist; forward an existing line to it to keep the number customers know.

wallet

Wallet

Prepaid, $10 minimum top-up, no plan or platform fee:

  • AI receptionist $0.20/min of talk time
  • First 10 seconds of every call free, then 6-second increments
  • Number $1.99/month · $5 trial credit for calls on new businesses
call

Calls & summaries

Every call keeps its transcript, who handled each part (segments), the cost (billedCents) and a summary: intent, outcome, caller name, a short write-up, any message taken with its urgency, and whether follow-up is needed. Summaries are included.

webhook

Webhooks

Signed POSTs to your URL when a call completes, a message is taken, or the balance runs low. Included at no cost.

hand-over

Hand-over

An agent can do the whole setup; the owner only pays. The business still belongs to them: the optional owner invite emails them a sign-in link whenever they want it, and the dashboard shows the business was set up by an agent and lets the owner rotate or revoke the agent's key.

Calls are recorded and transcribed to make the summary. Ringkit plays a consent notice on every call and follows two-party-consent rules by state. The receptionist always says it is an AI; that setting can't be turned off.

04 · REST API

REST API reference

JSON over HTTPS. The full schema of every route is in the OpenAPI document, generated from the same contract the API runs on.

Base URLs

WhatURL
REST APIhttps://api.ringkit.com/v1
MCP serverhttps://api.ringkit.com/mcp
OpenAPI 3.1https://api.ringkit.com/openapi.json
Docs for LLMshttps://api.ringkit.com/llms.txt, https://api.ringkit.com/llms-full.txt
MCP discoveryhttps://api.ringkit.com/.well-known/mcp.json
Dashboardhttps://app.ringkit.com

Authentication and API keys

POST /v1/businesses is the only call that needs no key; it returns { business, apiKey } once. Send the key on everything else:

Authorization: Bearer rk_…

An API key acts as the business owner. Each key has a scope: manage (the default, full access) or read (GET requests only; anything else returns 403 insufficient_scope). Give a reporting tool a read key:

curl https://api.ringkit.com/v1/businesses/$BIZ/api-keys \
  -H "Authorization: Bearer $RINGKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Reporting", "scope": "read" }'

The response has the key's metadata (key.id, key.prefix) and the plaintext apiKey, once. GET …/api-keys lists keys; DELETE …/api-keys/:keyId revokes one.

Idempotency

Every POST accepts an Idempotency-Key header. Retrying with the same key within 24 hours returns the stored response with Idempotent-Replayed: true instead of doing the work twice. A retry that arrives while the first is still running gets 409 idempotency_in_progress. Always send one on top-ups and number orders.

SDKs

No SDKs yet. Use fetch or curl (see the quickstart), generate a typed client from /openapi.json, or connect an agent over MCP.

Routes

Paths are relative to https://api.ringkit.com; :id is the business id.

MethodPathDescription
POST/v1/businessesCreate a business; returns its API key once (no auth)
GET/v1/businesses/:idRead the business
PATCH/v1/businesses/:idUpdate name, timezone, owner phone, voice, notify targets
GET/v1/meThe business behind the key
POST/v1/businesses/:id/owners/inviteEmail the owner a sign-in link { email? }
GET POST/v1/businesses/:id/api-keysList keys; create one { name, scope }
DELETE/v1/businesses/:id/api-keys/:keyIdRevoke a key
GET PUT PATCH/v1/businesses/:id/knowledgeRead, replace or merge the knowledge profile
POST/v1/businesses/:id/knowledge/importsImport a website in the background { url }
POST/v1/businesses/:id/knowledge/importDraft a profile from a website now { url, apply }
GET/v1/businesses/:id/onboardingSetup progress and the latest import
POST/v1/businesses/:id/setup-chatStart the setup chat { context?, restart? }
POST/v1/businesses/:id/setup-chat/:conversationId/messagesSend a message { text }
GET POST/v1/businesses/:id/knowledge/documentsList documents; upload one { fileName, contentBase64 }
DELETE/v1/businesses/:id/knowledge/documents/:documentIdDelete a document
GET/v1/businesses/:id/knowledge/usageDocument and token usage against the tier
POST/v1/businesses/:id/knowledge/tierSwitch tier { tier: starter | pro }
GET/v1/voicesAvailable voices and the default
GET PUT/v1/businesses/:id/routingRead or replace the routing policy
GET/v1/numbers/searchFind numbers ?areaCode=&contains=&limit=
GET POST/v1/businesses/:id/numbersList numbers; order one { number } ($1.99/mo)
DELETE/v1/businesses/:id/numbers/:numberIdRelease a number
GET/v1/businesses/:id/walletBalance and unspent trial credit
POST/v1/businesses/:id/wallet/top-upsGet a checkout link { amountCents } (min 1000)
GET/v1/businesses/:id/ledgerWallet ledger, cursor-paginated
POST/v1/businesses/:id/simulateText-mode test call { callId?, callerText }
POST/v1/businesses/:id/destinations/verifyCall a number and read it a 6-digit code { number }
POST/v1/businesses/:id/destinations/confirmConfirm the code { number, code }
POST/v1/businesses/:id/test-callRing a verified number with the AI { to }
GET/v1/businesses/:id/callsList calls ?limit=&cursor=
GET/v1/businesses/:id/calls/:callIdOne call with transcript and summary
GET/v1/businesses/:id/calls/:callId/recordingShort-lived recording URL
GET POST/v1/businesses/:id/webhooksList webhooks; create one { url, events }

Pagination: list routes take limit and cursor and return { items, nextCursor }; nextCursor is null on the last page.

05 · Agents

Agent frameworks & MCP

The MCP server at https://api.ringkit.com/mcp exposes the setup lifecycle as tools. It speaks Streamable HTTP, is stateless and takes POST only. Put the API key in the Authorization: Bearer rk_… header of the connection.

Add the server

Claude Code
claude mcp add --transport http ringkit https://api.ringkit.com/mcp --header "Authorization: Bearer rk_…"
Claude Desktop, Cursor and other MCP clients
{
  "mcpServers": {
    "ringkit": {
      "url": "https://api.ringkit.com/mcp",
      "headers": { "Authorization": "Bearer rk_…" }
    }
  }
}

No key yet? Connect without the header. ringkit_create_business works without one and returns the key; reconnect with it for every other tool. Every tool result carries a next field naming the tool that usually follows. Errors are the same JSON as REST, {"error":{"code","message","hint","docs"}} (see errors): follow the hint. Mutating tools take an optional idempotencyKey argument; an identical retry returns the stored result, so pass a new key to repeat a call on purpose.

Tools by step

49 tools; /.well-known/mcp.json lists them with their step, the endpoint and how to authenticate.

StepTools
1. Createringkit_create_business (no key), ringkit_whoami, ringkit_get_onboarding
2. Teachringkit_start_knowledge_import, ringkit_get_latest_knowledge_import, ringkit_import_knowledge, ringkit_start_setup_chat, ringkit_send_setup_chat, ringkit_get_setup_chat, ringkit_close_setup_chat, ringkit_put_knowledge, ringkit_patch_knowledge, ringkit_get_knowledge, ringkit_upload_knowledge_document, ringkit_list_knowledge_documents, ringkit_get_knowledge_document, ringkit_delete_knowledge_document, ringkit_get_knowledge_usage, ringkit_set_knowledge_tier
3. Voiceringkit_list_voices, ringkit_update_business { voice }
4. Routingringkit_get_routing, ringkit_put_routing
5. Try itringkit_simulate_call (no number needed; billed like a call at the AI rate from the wallet, the free trial credit covers it)
6. Fundringkit_get_wallet, ringkit_top_up (the human pays the checkoutUrl), ringkit_get_auto_refill, ringkit_put_auto_refill, ringkit_list_ledger
7. Numberringkit_search_numbers, ringkit_provision_number, ringkit_list_numbers, ringkit_release_number (needs step 6: $2 of paid credit)
8. Test callringkit_verify_destination, ringkit_confirm_destination, ringkit_list_destinations, ringkit_test_call
9. Watchringkit_list_calls, ringkit_get_call, ringkit_get_recording
10. Webhooksringkit_create_webhook, ringkit_list_webhooks
11. API keysringkit_create_api_key, ringkit_list_api_keys, ringkit_revoke_api_key
12. Hand over (optional)ringkit_invite_owner, ringkit_get_business (ownerSignedInAt), ringkit_list_business_users, ringkit_complete_onboarding

The order is the order of tools/list and of the Step N: prefix on each tool description. Funding gates the number, so step 6 comes before it: a number needs $2 of paid credit (trial credit covers calls only), and ringkit_top_up returns a checkoutUrl the human pays. That is the only thing a person must do; nobody has to sign in. ringkit_verify_destination and ringkit_test_call ring a phone a person must answer and read back a code (their consent to be called). Step 12, ringkit_invite_owner, is an optional hand-over: the agent can keep running the line, and the owner can take over any time.

OpenAI Agents, LangChain, Vercel AI SDK

Frameworks that build tools from OpenAPI can load https://api.ringkit.com/openapi.json directly. For a plain tool-calling loop, put https://api.ringkit.com/llms-full.txt in the system prompt and give the model one HTTP tool:

JavaScript
const [spec, guide] = await Promise.all([
  fetch("https://api.ringkit.com/openapi.json").then((r) => r.json()),
  fetch("https://api.ringkit.com/llms-full.txt").then((r) => r.text()),
]);
// spec.paths: one tool per operation; guide: the system prompt

Calls that need a person still reach one: list the business's own team numbers in transferTargets with a whenToUse for each, and the receptionist transfers those callers. Anything else becomes a message.

06 · Webhooks

Webhooks

Create a webhook with POST /v1/businesses/:id/webhooks { url, events }. The response holds the signing secret, once. Ringkit then POSTs JSON to the URL: { type, createdAt, data }.

EventdataSent when
call.completed{ call, transcript }A call has ended and been billed, with its summary when ready
message.taken{ callId, message, summary }The receptionist took a message for the business
balance.low{ balanceCents }A call took the wallet below $2

message.taken

{
  "type": "message.taken",
  "createdAt": "2026-10-27T18:15:52.000Z",
  "data": {
    "callId": "call_7n2k9x4m1q8c5v3b",
    "message": {
      "callerName": "Dana Ruiz",
      "callbackNumber": "+18055550173",
      "message": "Estimate for extending the Oakridge kitchen remodel into the dining room. Call after 3pm.",
      "urgency": "normal"
    },
    "summary": {
      "intent": "estimate_request",
      "outcome": "message_taken",
      "callerName": "Dana Ruiz",
      "summary": "Dana wants an estimate to extend the Oakridge kitchen remodel into the dining room.",
      "followUpNeeded": true
    }
  }
}

call.completed carries the same call and transcript as GET /calls/:callId.

Delivery

Respond with any 2xx within 5 seconds. Other responses and timeouts are retried with exponential backoff, up to 10 attempts (about a day and a half). A 4xx other than 408 or 429 is treated as permanent and not retried.

Verify signatures

Every delivery carries X-RingKit-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" keyed with the webhook's secret. The signature is made at send time, so reject anything more than five minutes old.

Node
import crypto from "node:crypto";

export function verifyRingkit(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(v1, "hex");
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

// Express: app.post("/hooks/ringkit", express.raw({ type: "application/json" }), (req, res) =>
//   verifyRingkit(req.body.toString(), req.get("X-RingKit-Signature"), process.env.RINGKIT_WEBHOOK_SECRET)
//     ? res.sendStatus(204) : res.sendStatus(400));
07 · Limits

Rate limits & errors

Rate limits

  • Unauthenticated POSTs (creating a business): 10 a minute per IP
  • Authenticated requests: 600 a minute per API key
  • Owner invites: 5 per business per hour
  • Setup chat: 20 messages a minute per business
  • Outbound calls: 5 test calls per business per day, 3 outbound calls per number per day

Over a limit you get 429 rate_limited with a Retry-After header in seconds. Wait that long, then retry.

Errors

Every error has the same body. hint is one concrete next step, written so an agent can act on it; docs links here.

{
  "error": {
    "code": "funding_required",
    "message": "ordering a number needs $2.00 of paid credit available; free trial credit covers calls only; top up first",
    "hint": "Top up the wallet: POST /v1/businesses/:id/wallet/top-ups returns a checkoutUrl for the human to pay; trial credit covers calls only.",
    "docs": "https://ringkit.com/docs.html#errors",
    "details": { "availableCents": 0, "requiredCents": 200, "trialCreditCents": 500 }
  }
}
StatusCodeMeaning
400validation_errorA field is missing or malformed; details lists them
401unauthorizedMissing, revoked or unknown API key
402funding_requiredOrdering a number needs $2 of paid credit; trial credit doesn't count
402insufficient_balanceNot enough paid credit for this (for example the Pro plan)
403forbiddenThis key can't act on that business
403owner_requiredOwner-only route called by a team member's session
403insufficient_scopeA read key tried to change something
403destination_unverifiedVerify the number before calling it
404not_foundNo such id for this business
409conflictThe resource changed or already exists; read it and retry
409idempotency_in_progressA request with this Idempotency-Key is still running
429rate_limitedToo many requests; wait Retry-After seconds
503llm_unavailableWebsite import or setup chat is unavailable; write the profile with PUT …/knowledge
503payments_unavailableIn-page payments aren't configured; use the hosted checkout top-up

Other codes are specific to one route (for example file_too_large on uploads or invalid_code on destination confirm) and almost all come with a hint.