# RingKit: full reference for AI agents > RingKit is a pay-per-minute AI phone receptionist for small businesses. The receptionist knows the business (hours, services, prices, FAQs, policies, uploaded documents), answers every call, takes messages and, when a caller needs a person, transfers them to the business's own team number or takes a message. An AI agent can set a business up end to end over REST or MCP: create it, teach it, pick a voice and routing, try it in text, fund it, get a number, test it and, optionally, hand it to the human owner. The only step a human must do is pay (opening the top-up checkoutUrl); no human has to sign in. There is no plan and no subscription beyond the number: pay only for what you use, from a prepaid wallet charged per call minute. ## Base URLs - REST: https://api.ringkit.com/v1 - MCP (Streamable HTTP, stateless): https://api.ringkit.com/mcp - OpenAPI 3.1 (generated from the same zod contract as the server): https://api.ringkit.com/openapi.json - MCP server card: https://api.ringkit.com/.well-known/mcp.json - Short guide: https://api.ringkit.com/llms.txt - Dashboard for the human owner: https://app.ringkit.com - Human docs: https://ringkit.com/docs.html ## Authentication 1. `POST /v1/businesses` needs no credential. Its response carries `apiKey` exactly once: store it, it is never shown again. 2. Send it on every other call: `Authorization: Bearer rk_live_...`. 3. Keys have a scope. `manage` (the default, and the key createBusiness returns) acts as the business owner: every business route, including owner-only ones. `read` may only GET; any other method answers 403 `insufficient_scope`. Create more keys with `createApiKey` and revoke them with `revokeApiKey`. 4. The human owner never uses the key. When they want to, they sign in to the dashboard by magic link: `inviteOwner` emails them one bound to this business. Setup never waits for it. Routes that take no credential: `createBusiness`, `requestMagicLink`, `consumeMagicLink`, `passwordLogin`, `enrollAgent`, `createAgent`, `submitFeedback`. ## Conventions - JSON in and out (`Content-Type: application/json`). GET inputs are query parameters; `:id` in a path is the business id (`biz_...`). Money is integer cents; times are ISO 8601; phone numbers are E.164 (`+14155550123`). - **Idempotency.** Every POST accepts `Idempotency-Key` (any unique string, at most 255 characters). A retry with the same key within 24 hours returns the stored response with the header `Idempotent-Replayed: true` instead of running again, so retrying after a timeout is safe. Send a new key to repeat an action on purpose (a second top-up of the same amount). 409 `idempotency_in_progress` means the first request is still running: retry with the same key. - **Pagination.** `listCalls` and `listLedger` return `{items, nextCursor}`. Pass `?cursor=&limit=<1-100>` for the next page; `nextCursor: null` is the last page. - **Errors.** Every error is `{"error": {"code", "message", "hint"?, "docs", "details"?}}`. `hint` is one actionable next step; `docs` is https://ringkit.com/docs.html#errors; `details` carries the validation issues or the numbers behind a limit. Branch on `code`, not on `message`. - **Rate limits.** Unauthenticated POSTs: 10 a minute per IP. Each API key: 600 requests a minute (REST and MCP share the bucket). Setup chat: 20 messages a minute per business. `inviteOwner`: 5 an hour per business. Over a limit you get 429 `rate_limited` with a `Retry-After` header (seconds) and `details.retryAfterSeconds`: wait that long, then retry. ### Error codes | Code | HTTP | Hint (the `hint` field) | |---|---|---| | `validation_error` | 400 | Fix the listed fields; the schema is at /openapi.json. | | `invalid_code` | 400 | Send the 6-digit code the verification call read, exactly. | | `unauthorized` | 401 | Send Authorization: Bearer rk_... (the apiKey returned by POST /v1/businesses or the ringkit_create_business MCP tool). | | `invalid_credentials` | 401 | Check the email and password; password sign-in exists only for operator-designated accounts, everyone else signs in with an emailed link (POST /v1/auth/magic-link). | | `funding_required` | 402 | Top up the wallet: POST /v1/businesses/:id/wallet/top-ups returns a checkoutUrl for the human to pay; trial credit covers calls only. | | `insufficient_balance` | 402 | Top up the wallet: POST /v1/businesses/:id/wallet/top-ups returns a checkoutUrl for the human to pay; trial credit covers calls only. | | `forbidden` | 403 | This credential can't act on this resource; use an API key or session that belongs to this business. | | `owner_required` | 403 | Use an owner's session or a manage API key (keys act as owner), or ask an owner to do this. | | `insufficient_scope` | 403 | This key is read-only; use a manage key (POST /v1/businesses/:id/api-keys with scope manage). | | `claim_required` | 403 | This is a signup draft: save it with an email first (POST /v1/drafts/me/claim {email}), then retry with the session it sets. | | `verification_required` | 403 | Open the sign-in link emailed to this address in this browser (POST /v1/auth/magic-link {email} sends a new one), then retry. | | `draft_expired` | 401 | The draft expired or was already saved; start again with POST /v1/drafts, or sign in with the email you gave. | | `draft_import_limit` | 429 | This draft used all its website imports; save it with an email (POST /v1/drafts/me/claim) to keep editing the profile by hand. | | `drafts_disabled` | 503 | Anonymous drafts are switched off on this server; create the business with POST /v1/businesses instead. | | `gateway_disabled` | 503 | (no hint sent) | | `webhook_not_configured` | 503 | (no hint sent) | | `unknown_call` | 404 | (no hint sent) | | `voice_stack_disabled` | 409 | The operator switched the ElevenLabs stack off; the business stays on livekit. Try again later. | | `voice_stack_unsupported_mode` | 409 | Set routing mode, afterHoursMode and every schedule rule to ai_first or ai_only (PUT /v1/businesses/:id/routing), then switch the voice stack again. | | `voice_stack_failed` | 502 | A step of the switch failed; details carries the voice-stack status with the failed step. Send the same PUT again to resume. | | `elevenlabs_unavailable` | 503 | ElevenLabs is not configured on this server; stay on the livekit stack. | | `demo_limit_reached` | 429 | This business used its browser demo calls (or this network its daily ones); save the draft and get a number to keep calling, or try the text simulator (POST /v1/businesses/:id/simulate). | | `demo_unavailable` | 503 | Browser demo calls are unavailable right now; try again later, or use the text simulator (POST /v1/businesses/:id/simulate). | | `unverified_draft_exists` | 409 | This email already started a signup; the sign-in link was re-sent: open it to continue that setup. | | `destination_not_allowed` | 403 | Configure the number on the business first (ownerPhone, a routing transfer target or an SMS notify target), then verify it. | | `destination_unverified` | 403 | Verify the number first: POST .../destinations/verify, then .../destinations/confirm with the code the call read. | | `csrf_required` | 403 | Send the header X-Requested-With: ringkit with cookie-authenticated requests. | | `not_found` | 404 | Check the id; list endpoints return current ids. | | `unknown_voice` | 404 | Pick a voice id from GET /v1/voices. | | `conflict` | 409 | Read the resource again (GET) and retry against its current state. | | `idempotency_in_progress` | 409 | A request with this Idempotency-Key is still running; retry the same request in a few seconds. | | `lost_claim` | 409 | Another request with this Idempotency-Key finished first; retry with the same key to read its stored response. | | `already_verified` | 409 | (no hint sent) The number is already verified: go straight to testCall. | | `challenge_superseded` | 409 | A newer verification call replaced this code; use the code from the latest call. | | `conversation_busy` | 409 | The previous setup-chat message is still being answered; wait for it, then send again. | | `document_limit_reached` | 409 | Delete documents you no longer need, or move to the pro tier (POST /v1/businesses/:id/knowledge/tier). | | `token_limit_reached` | 409 | Delete documents you no longer need, or move to the pro tier (POST /v1/businesses/:id/knowledge/tier). | | `over_tier_limits` | 409 | Delete documents until usage fits the starter limits, then change the tier. | | `number_limit_reached` | 409 | Release a number you no longer need (DELETE /v1/businesses/:id/numbers/:numberId) before ordering another. | | `number_required` | 409 | Provision a number first: GET /v1/numbers/search, then POST /v1/businesses/:id/numbers. | | `test_call_limit` | 409 | The 24-hour outbound call limit for this business or number was reached; try again after it rolls over. | | `forward_check_limit` | 409 | A business may run 3 forwarding checks in 24 hours; try again after the window rolls over. | | `forwarding_phone_required` | 409 | Save the business phone that forwards first: PUT /v1/businesses/:id/numbers/:numberId/forwarding {businessPhone}. | | `gone` | 410 | The resource expired or was removed; request a new one. | | `code_expired` | 410 | Request a new code: POST /v1/businesses/:id/destinations/verify. | | `file_too_large` | 413 | Upload a file under the limit in details (10 MB), or split it. | | `unsupported_file_type` | 415 | Upload PDF, DOCX, TXT, Markdown or HTML. | | `import_failed` | 422 | The website couldn't be read; check the URL, or set the knowledge profile directly with PUT /v1/businesses/:id/knowledge. | | `extraction_failed` | 422 | No text could be read from the file; upload a text-based version, not a scan. | | `empty_document` | 422 | The file has no readable text; upload one that does. | | `rate_limited` | 429 | Wait for Retry-After seconds, then retry; spread bursts over a minute (each API key gets RATE_LIMIT_API_KEY_PER_MIN requests a minute, default 600). | | `setup_budget_exceeded` | 429 | Today's setup-chat budget is spent; continue tomorrow, or edit knowledge and routing directly (PUT .../knowledge, PUT .../routing). | | `too_many_attempts` | 429 | Request a new code: POST /v1/businesses/:id/destinations/verify. | | `internal` | 500 | (no hint sent) Retry with the same Idempotency-Key; if it keeps failing, report it with POST /v1/feedback. | | `llm_unavailable` | 503 | The AI model isn't configured on this server; fill the knowledge profile directly with PUT /v1/businesses/:id/knowledge, or retry later. | | `llm_failed` | 503 | The AI model call failed; retry in a few seconds. | | `telephony_unavailable` | 503 | No telephony host is connected right now; retry in a minute. | | `payments_unavailable` | 503 | In-page payments aren't configured here; use POST /v1/businesses/:id/wallet/top-ups (hosted checkout), or retry later. | | `billing_test_unavailable` | 403 | Stripe test billing is not enabled on this server; sign up without billing=test. | | `billing_test_forbidden` | 403 | The billing test key is wrong; sign up without billing=test, or ask for the current key. | | `voice_preview_unavailable` | 503 | (no hint sent) Previews need a voice host; pick a voice from GET /v1/voices without one. | ## Pricing - AI receptionist: $0.20 a minute of talk time. - Billed in 6-second increments; the first 10 seconds of every call are free. - Phone number: $1.99 a month, accrued daily from the wallet (a business's first number can run on a 7-day trial). - Knowledge: Starter (5 documents / 50k tokens) is free; Pro (100 documents / 500k tokens) is 500¢ a month. - Prepaid wallet, minimum top-up 1000¢ ($10.00). A new business gets 500¢ of free trial credit (one per owner email) that pays for **calls only**: ordering a number or the Pro plan needs paid credit. ## The lifecycle Do these in order. Each step lists its routes with a request and the response it returns. Replace `$RINGKIT_API_KEY` with the key from step 1 and the business id with yours. ### 1. Create the business No auth needed. Keep the returned apiKey: every later call sends Authorization: Bearer rk_... and may send Idempotency-Key on POSTs. #### `createBusiness`: POST /v1/businesses Create a business and its first API key (returned once); no auth needed, so this is how an agent starts. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Harbor Wellness", "timezone": "America/Los_Angeles", "vertical": "wellness", "website": "harborwellness.com", "ownerEmail": "dana@harborwellness.com", "ownerPhone": "+14155550142" }' ``` Response `201`: ```json { "business": { "id": "biz_7k2m9q4x8w3n5p1r", "name": "Harbor Wellness", "timezone": "America/Los_Angeles", "vertical": "wellness", "website": "https://harborwellness.com", "ownerEmail": "dana@harborwellness.com", "ownerPhone": "+14155550142", "notify": [], "status": "active", "knowledgeTier": "starter", "voice": "Ashley", "createdVia": "api", "voiceStack": "livekit", "createdAt": "2026-10-27T16:02:11.000Z", "updatedAt": "2026-10-27T16:02:11.000Z" }, "apiKey": "rk_live_3f9a1c7e5b2d8f4a6c0e9b1d7f3a5c8e" } ``` #### `whoami`: GET /v1/me The business behind the presented API key or session, with how the caller authenticated and the key's scope. ```bash curl -sS -X GET "https://api.ringkit.com/v1/me" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "id": "biz_7k2m9q4x8w3n5p1r", "name": "Harbor Wellness", "timezone": "America/Los_Angeles", "vertical": "wellness", "website": "https://harborwellness.com", "ownerEmail": "dana@harborwellness.com", "ownerPhone": "+14155550142", "notify": [ { "type": "email", "to": "dana@harborwellness.com" } ], "status": "active", "knowledgeTier": "starter", "voice": "Ashley", "createdVia": "api", "voiceStack": "livekit", "createdAt": "2026-10-27T16:02:11.000Z", "updatedAt": "2026-10-27T16:02:11.000Z", "authKind": "api_key", "role": "owner", "keyScope": "manage" } ``` ### 2. Teach it the business Import the website (now, or in the background and poll), talk it through with the setup chat, or write the profile directly; uploads add documents on top. #### `importKnowledge`: POST /v1/businesses/:id/knowledge/import Draft a knowledge profile from a website now (synchronous), optionally applying it. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge/import" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://harborwellness.com", "apply": false }' ``` Response `200`: ```json { "draft": { "summary": "Harbor Wellness is a massage and bodywork studio in the Mission, San Francisco, with four licensed therapists.", "hours": { "regular": { "mon": [], "tue": [ { "open": "09:00", "close": "19:00" } ], "wed": [ { "open": "09:00", "close": "19:00" } ], "thu": [ { "open": "09:00", "close": "19:00" } ], "fri": [ { "open": "09:00", "close": "19:00" } ], "sat": [ { "open": "10:00", "close": "16:00" } ], "sun": [] }, "exceptions": [], "note": "Last appointment starts one hour before close." }, "phone": "+14155550100", "bookingUrl": "https://harborwellness.com/book", "services": [ { "name": "Swedish massage", "description": "Relaxing full-body massage.", "priceUsd": 95, "durationMin": 60 }, { "name": "Deep tissue massage", "description": "Focused work on chronic tension.", "priceUsd": 125, "durationMin": 75 } ], "staff": [], "faqs": [ { "q": "Do you take insurance?", "a": "We don't bill insurance directly, but we give you a receipt you can submit for reimbursement." }, { "q": "Is there parking?", "a": "Street parking on Valencia, and a public garage on 21st Street two blocks away." } ], "policies": [ { "topic": "Cancellations", "text": "Cancel at least 24 hours ahead; late cancellations and no-shows are charged 50% of the service." } ], "doNotSay": [], "escalationContacts": [], "documents": [] }, "applied": false, "estimatedTokens": 1840 } ``` #### `startKnowledgeImport`: POST /v1/businesses/:id/knowledge/imports Queue a background website import that fills only the profile fields not set yet. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge/imports" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "harborwellness.com" }' ``` Response `202`: ```json { "id": "kimp_d2f6h0j4l8n1p5r9", "url": "https://harborwellness.com", "status": "queued", "createdAt": "2026-10-27T16:03:00.000Z" } ``` #### `getLatestKnowledgeImport`: GET /v1/businesses/:id/knowledge/imports/latest The newest website import and its status (poll after startKnowledgeImport). ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge/imports/latest" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "import": { "id": "kimp_d2f6h0j4l8n1p5r9", "url": "https://harborwellness.com", "status": "done", "createdAt": "2026-10-27T16:03:00.000Z", "estimatedTokens": 1840, "found": { "hours": true, "services": 6, "faqs": 4, "policies": 2, "summary": true }, "finishedAt": "2026-10-27T16:03:41.000Z" } } ``` #### `startSetupChat`: POST /v1/businesses/:id/setup-chat (owner only) Start a setup-agent chat that fills knowledge and routing through conversation. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/setup-chat" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "context": "onboarding" }' ``` Response `201`: ```json { "id": "schat_h6j0l4n8p2r5t9v3", "status": "open", "context": "onboarding", "messages": [ { "role": "assistant", "text": "Hi! I'll set up your receptionist. What does Harbor Wellness do, and what hours are you open?", "at": "2026-10-27T16:05:00.000Z", "writes": [] } ], "budget": { "usedCents": 1, "limitCents": 200 }, "createdAt": "2026-10-27T16:05:00.000Z", "updatedAt": "2026-10-27T16:05:00.000Z" } ``` #### `sendSetupChat`: POST /v1/businesses/:id/setup-chat/:conversationId/messages (owner only) Send a message to the setup agent and get its reply. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/setup-chat/schat_h6j0l4n8p2r5t9v3/messages" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "text": "Massage studio. Tuesday to Friday 9 to 7, Saturday 10 to 4, closed Sunday and Monday." }' ``` Response `200`: ```json { "id": "schat_h6j0l4n8p2r5t9v3", "status": "open", "context": "onboarding", "messages": [ { "role": "assistant", "text": "Hi! I'll set up your receptionist. What does Harbor Wellness do, and what hours are you open?", "at": "2026-10-27T16:05:00.000Z", "writes": [] }, { "role": "user", "text": "Massage studio. Tuesday to Friday 9 to 7, Saturday 10 to 4, closed Sunday and Monday.", "at": "2026-10-27T16:05:30.000Z", "writes": [] }, { "role": "assistant", "text": "Saved your hours. Which services do you offer, and what do they cost?", "at": "2026-10-27T16:05:34.000Z", "writes": [ { "tool": "set_hours", "summary": "Hours: Tue–Fri 9:00–19:00, Sat 10:00–16:00; closed Sun, Mon" } ] } ], "budget": { "usedCents": 3, "limitCents": 200 }, "createdAt": "2026-10-27T16:05:00.000Z", "updatedAt": "2026-10-27T16:05:34.000Z" } ``` #### `putKnowledge`: PUT /v1/businesses/:id/knowledge Replace the whole knowledge profile. ```bash curl -sS -X PUT "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "summary": "Harbor Wellness is a massage and bodywork studio in the Mission, San Francisco, with four licensed therapists.", "hours": { "regular": { "mon": [], "tue": [ { "open": "09:00", "close": "19:00" } ], "wed": [ { "open": "09:00", "close": "19:00" } ], "thu": [ { "open": "09:00", "close": "19:00" } ], "fri": [ { "open": "09:00", "close": "19:00" } ], "sat": [ { "open": "10:00", "close": "16:00" } ], "sun": [] }, "note": "Last appointment starts one hour before close." }, "phone": "+14155550100", "bookingUrl": "https://harborwellness.com/book", "services": [ { "name": "Swedish massage", "description": "Relaxing full-body massage.", "priceUsd": 95, "durationMin": 60 }, { "name": "Deep tissue massage", "description": "Focused work on chronic tension.", "priceUsd": 125, "durationMin": 75 } ], "faqs": [ { "q": "Do you take insurance?", "a": "We don'\''t bill insurance directly, but we give you a receipt you can submit for reimbursement." }, { "q": "Is there parking?", "a": "Street parking on Valencia, and a public garage on 21st Street two blocks away." } ], "policies": [ { "topic": "Cancellations", "text": "Cancel at least 24 hours ahead; late cancellations and no-shows are charged 50% of the service." } ] }' ``` Response `200`: ```json { "summary": "Harbor Wellness is a massage and bodywork studio in the Mission, San Francisco, with four licensed therapists.", "hours": { "regular": { "mon": [], "tue": [ { "open": "09:00", "close": "19:00" } ], "wed": [ { "open": "09:00", "close": "19:00" } ], "thu": [ { "open": "09:00", "close": "19:00" } ], "fri": [ { "open": "09:00", "close": "19:00" } ], "sat": [ { "open": "10:00", "close": "16:00" } ], "sun": [] }, "exceptions": [], "note": "Last appointment starts one hour before close." }, "phone": "+14155550100", "bookingUrl": "https://harborwellness.com/book", "services": [ { "name": "Swedish massage", "description": "Relaxing full-body massage.", "priceUsd": 95, "durationMin": 60 }, { "name": "Deep tissue massage", "description": "Focused work on chronic tension.", "priceUsd": 125, "durationMin": 75 } ], "staff": [], "faqs": [ { "q": "Do you take insurance?", "a": "We don't bill insurance directly, but we give you a receipt you can submit for reimbursement." }, { "q": "Is there parking?", "a": "Street parking on Valencia, and a public garage on 21st Street two blocks away." } ], "policies": [ { "topic": "Cancellations", "text": "Cancel at least 24 hours ahead; late cancellations and no-shows are charged 50% of the service." } ], "doNotSay": [], "escalationContacts": [], "documents": [] } ``` #### `patchKnowledge`: PATCH /v1/businesses/:id/knowledge Change parts of the knowledge profile; omitted fields are kept. ```bash curl -sS -X PATCH "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "greeting": "Thanks for calling Harbor Wellness, this is the front desk. How can I help?" }' ``` Response `200`: ```json { "summary": "Harbor Wellness is a massage and bodywork studio in the Mission, San Francisco, with four licensed therapists.", "hours": { "regular": { "mon": [], "tue": [ { "open": "09:00", "close": "19:00" } ], "wed": [ { "open": "09:00", "close": "19:00" } ], "thu": [ { "open": "09:00", "close": "19:00" } ], "fri": [ { "open": "09:00", "close": "19:00" } ], "sat": [ { "open": "10:00", "close": "16:00" } ], "sun": [] }, "exceptions": [], "note": "Last appointment starts one hour before close." }, "phone": "+14155550100", "bookingUrl": "https://harborwellness.com/book", "services": [ { "name": "Swedish massage", "description": "Relaxing full-body massage.", "priceUsd": 95, "durationMin": 60 }, { "name": "Deep tissue massage", "description": "Focused work on chronic tension.", "priceUsd": 125, "durationMin": 75 } ], "staff": [], "faqs": [ { "q": "Do you take insurance?", "a": "We don't bill insurance directly, but we give you a receipt you can submit for reimbursement." }, { "q": "Is there parking?", "a": "Street parking on Valencia, and a public garage on 21st Street two blocks away." } ], "policies": [ { "topic": "Cancellations", "text": "Cancel at least 24 hours ahead; late cancellations and no-shows are charged 50% of the service." } ], "doNotSay": [], "escalationContacts": [], "documents": [], "greeting": "Thanks for calling Harbor Wellness, this is the front desk. How can I help?" } ``` #### `uploadKnowledgeDocument`: POST /v1/businesses/:id/knowledge/documents Upload a PDF, DOCX, TXT, Markdown or HTML file (base64 JSON); its text joins the knowledge for every call. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/knowledge/documents" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "fileName": "price-list.txt", "contentType": "text/plain", "contentBase64": "U3dlZGlzaCBtYXNzYWdlIDYwIG1pbjogJDk1CkRlZXAgdGlzc3VlIDc1IG1pbjogJDEyNQo=" }' ``` Response `201`: ```json { "document": { "id": "doc_n4r8v2z6c0f3j7m1", "fileName": "price-list.txt", "fileType": "txt", "contentType": "text/plain", "byteSize": 52, "sha256": "9c1f4e7a2b5d8c0e3f6a9b2c5d8e1f4a7b0c3d6e9f2a5b8c1d4e7f0a3b6c9d2e", "sectionCount": 1, "estimatedTokens": 15, "originalStored": true, "uploadedAt": "2026-10-27T16:10:00.000Z" }, "usage": { "tier": "starter", "limits": { "maxDocuments": 5, "maxTokens": 50000, "maxFileBytes": 10485760, "monthlyCents": 0 }, "used": { "documents": 1, "tokens": 412 }, "originalsStored": true } } ``` ### 3. Pick a voice Set `voice` on the business to one of the listed ids. #### `listVoices`: GET /v1/voices List the receptionist voices; set one with updateBusiness `voice`. ```bash curl -sS -X GET "https://api.ringkit.com/v1/voices" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "voices": [ { "id": "Ashley", "label": "Ashley", "gender": "female", "accent": "American", "description": "Warm, natural (the default)" }, { "id": "Sarah", "label": "Sarah", "gender": "female", "accent": "American", "description": "Calm, professional" }, { "id": "Mark", "label": "Mark", "gender": "male", "accent": "American", "description": "Steady, reassuring" } ], "default": "Ashley" } ``` #### `updateBusiness`: PATCH /v1/businesses/:id Patch the business (name, timezone, website, voice, notify targets, ...); owner identity, status and notify are owner-only. ```bash curl -sS -X PATCH "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "voice": "Sarah", "notify": [ { "type": "email", "to": "dana@harborwellness.com" } ] }' ``` Response `200`: ```json { "id": "biz_7k2m9q4x8w3n5p1r", "name": "Harbor Wellness", "timezone": "America/Los_Angeles", "vertical": "wellness", "website": "https://harborwellness.com", "ownerEmail": "dana@harborwellness.com", "ownerPhone": "+14155550142", "notify": [ { "type": "email", "to": "dana@harborwellness.com" } ], "status": "active", "knowledgeTier": "starter", "voice": "Sarah", "createdVia": "api", "voiceStack": "livekit", "createdAt": "2026-10-27T16:02:11.000Z", "updatedAt": "2026-10-27T16:15:00.000Z" } ``` ### 4. Decide who answers The AI answers every call; set when it transfers to the business's own team number or takes a message. Transfer targets must be verified before test calls ring them. #### `getRouting`: GET /v1/businesses/:id/routing Read the routing policy: when the AI answers, transfers or takes a message, and the spend cap. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/routing" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "mode": "ai_first", "schedule": [], "afterHoursMode": "ai_only", "humanRingTimeoutS": 8, "maxHumanOffers": 3, "escalateOn": [ "caller_requests_human", "always_human_intent", "repeated_failure" ], "alwaysHumanIntents": [ "complaint" ], "neverHumanIntents": [ "hours", "location" ], "transferTargets": [ { "id": "front-desk", "label": "Front desk", "number": "+14155550142", "whenToUse": "Billing questions and complaints" } ], "dailySpendCapUsd": 25, "recordCalls": true, "discloseAi": true, "requiredAgentSkills": [], "vettedAgentsOnly": false } ``` #### `putRouting`: PUT /v1/businesses/:id/routing (owner only) Change the routing policy (transfer targets on the business's own team, handoff preferences, recording, spend cap). Every call is answered by the AI: RingKit's live-rep pool is not offered, so ai_first/human_first modes act as ai_only. ```bash curl -sS -X PUT "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/routing" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "ai_first", "alwaysHumanIntents": [ "complaint" ], "neverHumanIntents": [ "hours", "location" ], "transferTargets": [ { "id": "front-desk", "label": "Front desk", "number": "+14155550142", "whenToUse": "Billing questions and complaints" } ], "dailySpendCapUsd": 25 }' ``` Response `200`: ```json { "mode": "ai_first", "schedule": [], "afterHoursMode": "ai_only", "humanRingTimeoutS": 8, "maxHumanOffers": 3, "escalateOn": [ "caller_requests_human", "always_human_intent", "repeated_failure" ], "alwaysHumanIntents": [ "complaint" ], "neverHumanIntents": [ "hours", "location" ], "transferTargets": [ { "id": "front-desk", "label": "Front desk", "number": "+14155550142", "whenToUse": "Billing questions and complaints" } ], "dailySpendCapUsd": 25, "recordCalls": true, "discloseAi": true, "requiredAgentSkills": [], "vettedAgentsOnly": false } ``` ### 5. Try it in text No number needed; billed like a call at the AI rate from the wallet (the free trial credit covers it). Drives the real receptionist with typed caller turns: fix knowledge and routing until the answers are right. #### `simulateCall`: POST /v1/businesses/:id/simulate Run a text-mode call against the real receptionist to test knowledge and routing; no number needed; billed like a call at the AI rate from the wallet (the free trial credit covers it). ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/simulate" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "callerText": "Hi, are you open on Saturday?" }' ``` Response `200`: ```json { "callId": "call_s9d3f7h1k5m8p2r6", "aiText": "Yes, we're open Saturday from 10 to 4. Would you like to book a massage?", "ended": false, "intent": "hours", "escalated": false } ``` ### 6. Fund the wallet The one step only a human can do: topUp returns a checkoutUrl; hand it to the human to open and pay, then read getWallet. Ask early (any time after step 1): a number (step 7) needs $2 of paid credit, and trial credit covers calls only. #### `getWallet`: GET /v1/businesses/:id/wallet (owner only) Wallet balance, unspent trial credit and auto-refill settings. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/wallet" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "balanceCents": 2500, "trialCreditCents": 500, "autoRefill": null } ``` #### `topUp`: POST /v1/businesses/:id/wallet/top-ups (owner only) Start a wallet top-up: returns a Stripe checkoutUrl for the human to pay. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/wallet/top-ups" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "amountCents": 2000 }' ``` Response `200`: ```json { "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_a1b2c3d4e5f6", "balanceCents": 500 } ``` Stop and ask the human when: it returns a checkoutUrl: paying is the one setup step only a human can do. Give them the link, wait until they say they paid, then read getWallet to see the credit (checkoutUrl is null when payments are mocked and the wallet was credited at once). #### `putAutoRefill`: PUT /v1/businesses/:id/wallet/auto-refill (owner only) Turn auto-refill on or off and set its threshold and amount (needs a saved card to run). ```bash curl -sS -X PUT "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/wallet/auto-refill" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "thresholdCents": 500, "amountCents": 2000 }' ``` Response `200`: ```json { "enabled": true, "thresholdCents": 500, "amountCents": 2000, "hasPaymentMethod": false, "paymentMethod": null } ``` ### 7. Get a phone number Needs step 6: $2 of paid wallet credit, not trial credit (else 402 funding_required with availableCents, requiredCents and trialCreditCents). Exception: the first number of a business whose owner signed in may run 7 days on trial credit (trialUntil; a top-up keeps it). To keep the owner's existing number, save it with putForwarding, have the owner dial their carrier's forwarding code, then run startForwardingCheck. #### `searchNumbers`: GET /v1/numbers/search Search phone numbers available to order, by area code or region ($1.99 a month each). ```bash curl -sS -X GET "https://api.ringkit.com/v1/numbers/search?areaCode=415&limit=2" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "numbers": [ { "number": "+14155550187", "region": "San Francisco, CA", "monthlyCents": 199, "provider": "telnyx" }, { "number": "+14155550163", "region": "San Francisco, CA", "monthlyCents": 199, "provider": "telnyx" } ] } ``` #### `provisionNumber`: POST /v1/businesses/:id/numbers (owner only) Order a phone number for the business (needs $2 of paid wallet credit, or, for the first number of a business whose owner signed in, trial credit with a 7-day trial). ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/numbers" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "number": "+14155550187" }' ``` Response `201`: ```json { "id": "num_g5k9m3q7s1u4w8y2", "businessId": "biz_7k2m9q4x8w3n5p1r", "number": "+14155550187", "provider": "telnyx", "providerNumberId": "2563718291736154", "monthlyCents": 199, "status": "active", "createdAt": "2026-10-27T16:20:40.000Z" } ``` Stop and ask the human when: it answers 402 funding_required: the wallet needs $2 of paid credit (trial credit only covers a first number once the owner signed in; getOnboarding.canOrderOnTrial says so). Call topUp, give the human the checkoutUrl to pay, then retry. A number with trialUntil is released then unless the wallet is topped up first: tell the human. #### `listNumbers`: GET /v1/businesses/:id/numbers List the business's phone numbers. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/numbers" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "numbers": [ { "id": "num_g5k9m3q7s1u4w8y2", "businessId": "biz_7k2m9q4x8w3n5p1r", "number": "+14155550187", "provider": "telnyx", "providerNumberId": "2563718291736154", "monthlyCents": 199, "status": "active", "createdAt": "2026-10-27T16:20:40.000Z" } ] } ``` #### `putForwarding`: PUT /v1/businesses/:id/numbers/:numberId/forwarding (owner only) Save the owner's existing business phone that forwards (on no-answer) to this RingKit number. ```bash curl -sS -X PUT "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/numbers/num_g5k9m3q7s1u4w8y2/forwarding" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "businessPhone": "+14155550142" }' ``` Response `200`: ```json { "businessPhone": "+14155550142" } ``` Stop and ask the human when: the owner turns forwarding on with their carrier's dial code (e.g. **61*#), which only they can do on their phone: give them the code, then run startForwardingCheck. #### `startForwardingCheck`: POST /v1/businesses/:id/numbers/:numberId/forwarding/check (owner only) Ring the business phone from the RingKit number and wait up to 60 s for it to forward back; poll getForwarding for the outcome. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/numbers/num_g5k9m3q7s1u4w8y2/forwarding/check" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` Response `202`: ```json { "checkId": "fwd_Zt3qWm8KpX2rVn6YbJ4cHA", "status": "started" } ``` Stop and ask the human when: the business phone really rings: tell the human NOT to answer it (an answered call means not_forwarded), then poll getForwarding every few seconds for lastCheck.outcome. #### `getForwarding`: GET /v1/businesses/:id/numbers/:numberId/forwarding (owner only) Forwarding to a RingKit number: the owner's business phone, when forwarding was last proven, and the last forwarding check. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/numbers/num_g5k9m3q7s1u4w8y2/forwarding" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "businessPhone": "+14155550142", "verifiedAt": "2026-10-27T16:36:31.000Z", "lastCheck": { "checkId": "fwd_Zt3qWm8KpX2rVn6YbJ4cHA", "at": "2026-10-27T16:36:02.000Z", "status": "done", "outcome": "forwarded", "detail": "Your phone forwarded the test call to your RingKit number.", "finishedAt": "2026-10-27T16:36:31.000Z" } } ``` ### 8. Place a test call Needs the number from step 7. Verify the destination first: the verify call reads a 6-digit code to whoever answers, they read it back to you, you confirm it, then ring that number with the AI. This is the consent of the person being called, so it stays a human step. #### `verifyDestination`: POST /v1/businesses/:id/destinations/verify (owner only) Call a number and read it a 6-digit code, to prove the business controls it. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/destinations/verify" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "number": "+14155550142" }' ``` Response `202`: ```json { "queued": true, "expiresAt": "2026-10-27T16:40:00.000Z", "consentId": "obc_b7d1f5h9k3m6p0s4" } ``` Stop and ask the human when: the call rings `number` and reads a 6-digit code twice (valid 10 minutes): ask the person who answered for the code, then send it with confirmDestination. #### `confirmDestination`: POST /v1/businesses/:id/destinations/confirm (owner only) Confirm a number with the code its verification call read. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/destinations/confirm" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "number": "+14155550142", "code": "481516" }' ``` Response `200`: ```json { "number": "+14155550142", "verifiedAt": "2026-10-27T16:32:10.000Z", "method": "voice_otp" } ``` Stop and ask the human when: you need the 6-digit code the verification call read; only the person who answered that call has it. #### `testCall`: POST /v1/businesses/:id/test-call (owner only) Ring one of the business's verified numbers from its RingKit number with the AI (a real, billed call). ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/test-call" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "to": "+14155550142" }' ``` Response `202`: ```json { "queued": true, "consentId": "obc_e3g7j1l5n9q2s6u0" } ``` Stop and ask the human when: the phone at `to` rings with the AI receptionist: ask the human to answer and talk to it like a caller, then read the result with listCalls / getCall. ### 9. Watch the calls Outcomes, summaries, transcripts and recordings. #### `listCalls`: GET /v1/businesses/:id/calls List calls, newest first (paginated), with outcome, intent, summary and cost. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/calls?limit=25" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "items": [ { "id": "call_x4n8q2w6e9r3t7y1", "businessId": "biz_7k2m9q4x8w3n5p1r", "direction": "inbound", "from": "+14155550199", "to": "+14155550187", "status": "completed", "provider": "livekit-telnyx", "providerCallId": "SCL_8hd73ks0", "startedAt": "2026-10-27T18:41:02.000Z", "answeredAt": "2026-10-27T18:41:03.000Z", "endedAt": "2026-10-27T18:42:31.000Z", "endReason": "caller_hangup", "segments": [ { "handler": "ai", "startedAt": "2026-10-27T18:41:03.000Z", "endedAt": "2026-10-27T18:42:31.000Z" } ], "summary": { "intent": "book_appointment", "outcome": "answered", "callerName": "Sam", "summary": "Sam asked about a deep tissue massage on Saturday; given the price and the booking link by SMS.", "followUpNeeded": false, "sentiment": "positive" }, "billedCents": 21 } ], "nextCursor": null } ``` #### `getCall`: GET /v1/businesses/:id/calls/:callId One call with its full transcript. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/calls/call_x4n8q2w6e9r3t7y1" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "call": { "id": "call_x4n8q2w6e9r3t7y1", "businessId": "biz_7k2m9q4x8w3n5p1r", "direction": "inbound", "from": "+14155550199", "to": "+14155550187", "status": "completed", "provider": "livekit-telnyx", "providerCallId": "SCL_8hd73ks0", "startedAt": "2026-10-27T18:41:02.000Z", "answeredAt": "2026-10-27T18:41:03.000Z", "endedAt": "2026-10-27T18:42:31.000Z", "endReason": "caller_hangup", "segments": [ { "handler": "ai", "startedAt": "2026-10-27T18:41:03.000Z", "endedAt": "2026-10-27T18:42:31.000Z" } ], "summary": { "intent": "book_appointment", "outcome": "answered", "callerName": "Sam", "summary": "Sam asked about a deep tissue massage on Saturday; given the price and the booking link by SMS.", "followUpNeeded": false, "sentiment": "positive" }, "billedCents": 21 }, "transcript": [ { "seq": 0, "speaker": "ai", "text": "Thanks for calling Harbor Wellness. This call may be answered by an AI assistant. How can I help?", "at": "2026-10-27T18:41:03.000Z" }, { "seq": 1, "speaker": "caller", "text": "Hi, how much is a deep tissue massage, and can I come Saturday?", "at": "2026-10-27T18:41:09.000Z" }, { "seq": 2, "speaker": "ai", "text": "Deep tissue is $125 for 75 minutes, and we're open Saturday 10 to 4. Want me to text you the booking link?", "at": "2026-10-27T18:41:14.000Z" } ] } ``` #### `getRecording`: GET /v1/businesses/:id/calls/:callId/recording A short-lived URL for a call's recording. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/calls/call_x4n8q2w6e9r3t7y1/recording" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "url": "https://storage.ringkit.com/recordings/call_x4n8q2w6e9r3t7y1.ogg?X-Amz-Signature=5e1c", "expiresAt": "2026-10-27T19:00:00.000Z" } ``` ### 10. Get told about calls call.completed, message.taken and balance.low, signed with the returned secret. #### `createWebhook`: POST /v1/businesses/:id/webhooks (owner only) Register a webhook for call.completed, message.taken or balance.low; the signing secret is returned once. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/webhooks" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.example.com/ringkit", "events": [ "call.completed", "message.taken", "balance.low" ] }' ``` Response `201`: ```json { "id": "whk_c8e2g6j0l4n7q1s5", "secret": "whsec_6b9f2d4a8c1e7f3b5d0a9c2e4f6b8d1a" } ``` #### `listWebhooks`: GET /v1/businesses/:id/webhooks List the business's webhook endpoints. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/webhooks" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "webhooks": [ { "id": "whk_c8e2g6j0l4n7q1s5", "url": "https://hooks.example.com/ringkit", "events": [ "call.completed", "message.taken", "balance.low" ], "createdAt": "2026-10-27T16:50:00.000Z" } ] } ``` ### 11. Manage API keys Give read-scope keys to anything that only reports; revoke keys you no longer use. Rotate a key by creating its replacement, then revoking it. #### `createApiKey`: POST /v1/businesses/:id/api-keys (owner only) Create an API key with scope manage (acts as owner) or read (GET only); the plaintext is returned once. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/api-keys" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "Reporting", "scope": "read" }' ``` Response `201`: ```json { "key": { "id": "key_q8s2u6w0y4a7c1e5", "name": "Reporting", "scope": "read", "prefix": "rk_live_9b2e", "createdAt": "2026-10-27T18:50:00.000Z" }, "apiKey": "rk_live_9b2e7d4f1a8c5e3b6d0f2a9c4e7b1d5f" } ``` #### `listApiKeys`: GET /v1/businesses/:id/api-keys (owner only) List the business's API keys (prefix, scope, last use, revoked or not); never the plaintext. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/api-keys" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "keys": [ { "id": "key_m3p7r1t5v9x2z6b4", "name": "Setup agent", "scope": "manage", "prefix": "rk_live_3f9a", "createdAt": "2026-10-27T16:02:11.000Z", "lastUsedAt": "2026-10-27T18:45:00.000Z" }, { "id": "key_q8s2u6w0y4a7c1e5", "name": "Reporting", "scope": "read", "prefix": "rk_live_9b2e", "createdAt": "2026-10-27T18:50:00.000Z" } ] } ``` #### `revokeApiKey`: DELETE /v1/businesses/:id/api-keys/:keyId (owner only) Revoke an API key; it stops authenticating at once. ```bash curl -sS -X DELETE "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/api-keys/key_q8s2u6w0y4a7c1e5" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "ok": true } ``` ### 12. Hand it to the owner Optional: the agent can keep running the line, and the human can take over any time. inviteOwner emails the owner a sign-in link for this business; getBusiness shows ownerSignedInAt once they used it (re-send it if it expired); listBusinessUsers shows who can sign in; completeOnboarding marks setup finished. #### `inviteOwner`: POST /v1/businesses/:id/owners/invite (owner only) Optional hand-over: email the human owner a sign-in link bound to this business (default: ownerEmail). ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/owners/invite" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` Response `200`: ```json { "ok": true, "email": "dana@harborwellness.com", "magicLinkSent": true } ``` #### `getBusiness`: GET /v1/businesses/:id Read the business: profile, voice, status, how it was created and whether a human owner has signed in. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "id": "biz_7k2m9q4x8w3n5p1r", "name": "Harbor Wellness", "timezone": "America/Los_Angeles", "vertical": "wellness", "website": "https://harborwellness.com", "ownerEmail": "dana@harborwellness.com", "ownerPhone": "+14155550142", "notify": [ { "type": "email", "to": "dana@harborwellness.com" } ], "status": "active", "knowledgeTier": "starter", "voice": "Ashley", "createdVia": "api", "voiceStack": "livekit", "createdAt": "2026-10-27T16:02:11.000Z", "updatedAt": "2026-10-27T16:02:11.000Z", "ownerSignedInAt": "2026-10-27T19:12:44.000Z" } ``` #### `listBusinessUsers`: GET /v1/businesses/:id/users (owner only) List the people who can sign in to the business dashboard, with their roles and last sign-in. ```bash curl -sS -X GET "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/users" \ -H "Authorization: Bearer $RINGKIT_API_KEY" ``` Response `200`: ```json { "users": [ { "id": "bu_t4v8x2z6b0d3f7h1", "email": "dana@harborwellness.com", "role": "owner", "createdAt": "2026-10-27T16:02:11.000Z", "lastSignInAt": "2026-10-27T19:12:44.000Z" } ] } ``` #### `completeOnboarding`: POST /v1/businesses/:id/onboarding/complete (owner only) Mark first-run setup finished so the dashboard stops nudging the owner. ```bash curl -sS -X POST "https://api.ringkit.com/v1/businesses/biz_7k2m9q4x8w3n5p1r/onboarding/complete" \ -H "Authorization: Bearer $RINGKIT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` Response `200`: ```json { "step": "done", "completedAt": "2026-10-27T19:20:05.000Z", "hasNumber": true, "knowledgeReady": true, "latestImport": { "id": "kimp_d2f6h0j4l8n1p5r9", "url": "https://harborwellness.com", "status": "done", "createdAt": "2026-10-27T16:03:00.000Z", "estimatedTokens": 1840, "found": { "hours": true, "services": 6, "faqs": 4, "policies": 2, "summary": true }, "finishedAt": "2026-10-27T16:03:41.000Z" } } ``` ## Hand-over (optional) The agent can keep running the line; the human can take over any time. - `inviteOwner` (`POST /v1/businesses/:id/owners/invite`, body `{email?}`, default the business's `ownerEmail`) emails the human owner a sign-in link bound to this business, adding them as an owner first when needed. 5 an hour. - `Business.createdVia` records how the business was made: `api` (bare REST), `mcp` or `dashboard`. `Business.ownerSignedInAt` is unset until a human owner has used a sign-in link; read it with `getBusiness` to know the hand-over happened. Nothing waits for it: ordering a phone number needs only paid wallet credit (402 `funding_required` without it). - In the dashboard (https://app.ringkit.com) the owner sees that the business was set up by an agent and can revoke or rotate the agent's key. - Anything that only reports (a CRM sync, a reporting job) should get its own read-only key: `createApiKey` with `{"scope": "read"}`. ## Voice stack (optional) Which platform answers the AI tier. `livekit` (the default) is RingKit's own stack; `elevenlabs` hands the call to the ElevenLabs Agents platform (their speech recognition, turn-taking and voices) running the same receptionist brain; a caller who needs a person is still transferred to your team number (or a message is taken). One call record, same billing either way. - `getVoiceStack` (`GET /v1/businesses/:id/voice-stack`) returns `{desired, applied, status: ok | pending | error, number?, elevenlabsAgentId?, steps[], lastError?, forcedByOperator?, disabled?}`. - `putVoiceStack` (`PUT /v1/businesses/:id/voice-stack`, body `{"stack": "elevenlabs"}`, owner only) applies the switch synchronously (ElevenLabs agent sync, number import or removal, carrier routing): it can take a few seconds. Needs an active number (409 `number_required`) and ai_only / ai_first routing (409 `voice_stack_unsupported_mode` for human_first). 409 `voice_stack_disabled` while the operator has the ElevenLabs stack switched off. A failed step answers 502 `voice_stack_failed` with the status in `details`; send the same PUT again to resume from where it stopped. ## Webhooks `createWebhook` `{url, events}` returns the signing `secret` once. RingKit POSTs `{type, createdAt, data}` to the URL: | Event | `data` | When | |---|---|---| | `call.completed` | `{call, transcript}` | After the call is settled and summarized | | `message.taken` | `{callId, message, summary}` | When the receptionist took a message on the call | | `balance.low` | `{balanceCents}` | When a call takes the balance below 200¢ | Every delivery carries `X-RingKit-Signature: t=,v1=.")>`. Verify it by recomputing the HMAC over the timestamp, a dot and the raw body, comparing in constant time, and rejecting old timestamps. Answer 2xx within 5 seconds. A 4xx other than 408/429 is permanent; anything else is retried with exponential backoff (10 attempts over about 1.5 days), so handle duplicates. ## Route index Every route, grouped by tag. Audience: `agent` = useful to an API-key or MCP caller; `dashboard` = browser only (cookies, Stripe.js, EventSource); `console` = the human-agent console. Owner-only routes answer 403 `owner_required` to a member session; API keys act as owner. ### businesses | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `createBusiness` | POST | `/v1/businesses` | | agent | Create a business and its first API key (returned once); no auth needed, so this is how an agent starts. | | `getBusiness` | GET | `/v1/businesses/:id` | | agent | Read the business: profile, voice, status, how it was created and whether a human owner has signed in. | | `whoami` | GET | `/v1/me` | | agent | The business behind the presented API key or session, with how the caller authenticated and the key's scope. | | `inviteOwner` | POST | `/v1/businesses/:id/owners/invite` | yes | agent | Optional hand-over: email the human owner a sign-in link bound to this business (default: ownerEmail). | | `listBusinessUsers` | GET | `/v1/businesses/:id/users` | yes | agent | List the people who can sign in to the business dashboard, with their roles and last sign-in. | | `updateBusiness` | PATCH | `/v1/businesses/:id` | | agent | Patch the business (name, timezone, website, voice, notify targets, ...); owner identity, status and notify are owner-only. | ### auth | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `requestMagicLink` | POST | `/v1/auth/magic-link` | | dashboard | Email a sign-in link to a business user or enrolled agent; always answers ok. | | `consumeMagicLink` | POST | `/v1/auth/magic-link/consume` | | dashboard | Exchange a single-use sign-in token for a session cookie. | | `passwordLogin` | POST | `/v1/auth/password` | | dashboard | Sign in an operator-designated account (shared reviewer logins) with email and password; sets the same session cookie as a sign-in link. | | `logout` | POST | `/v1/auth/logout` | | dashboard | End the browser session and clear its cookies. | | `authSession` | GET | `/v1/auth/session` | | dashboard | Who the session cookie belongs to, which businesses they can open, and whether the email is verified (false until the emailed link is opened). | ### onboarding | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `createDraft` | POST | `/v1/drafts` | | dashboard | Start an anonymous signup draft from a website or name: a business with no owner yet, imported in the background; sets the rk_draft cookie. | | `getDraft` | GET | `/v1/drafts/me` | | dashboard | The draft behind the rk_draft cookie: business, newest website import and the receptionist preview (greeting, three Q&A, voice). | | `markDraftPreviewSeen` | POST | `/v1/drafts/me/preview-seen` | | dashboard | Funnel: the visitor saw their receptionist preview. | | `claimDraft` | POST | `/v1/drafts/me/claim` | | dashboard | Bind the draft to an email: the business gets its owner and trial credit, the browser an unverified session limited to it, and the sign-in link is emailed. | | `getOnboarding` | GET | `/v1/businesses/:id/onboarding` | | agent | First-run setup progress: the next step, whether knowledge is ready, whether there is a number, the latest import. | | `completeOnboarding` | POST | `/v1/businesses/:id/onboarding/complete` | yes | agent | Mark first-run setup finished so the dashboard stops nudging the owner. | ### api-keys | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `listApiKeys` | GET | `/v1/businesses/:id/api-keys` | yes | agent | List the business's API keys (prefix, scope, last use, revoked or not); never the plaintext. | | `createApiKey` | POST | `/v1/businesses/:id/api-keys` | yes | agent | Create an API key with scope manage (acts as owner) or read (GET only); the plaintext is returned once. | | `revokeApiKey` | DELETE | `/v1/businesses/:id/api-keys/:keyId` | yes | agent | Revoke an API key; it stops authenticating at once. | ### knowledge | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `getKnowledge` | GET | `/v1/businesses/:id/knowledge` | | agent | Read the knowledge profile the receptionist answers from (services, hours, FAQs, policies). | | `putKnowledge` | PUT | `/v1/businesses/:id/knowledge` | | agent | Replace the whole knowledge profile. | | `patchKnowledge` | PATCH | `/v1/businesses/:id/knowledge` | | agent | Change parts of the knowledge profile; omitted fields are kept. | | `importKnowledge` | POST | `/v1/businesses/:id/knowledge/import` | | agent | Draft a knowledge profile from a website now (synchronous), optionally applying it. | | `startKnowledgeImport` | POST | `/v1/businesses/:id/knowledge/imports` | | agent | Queue a background website import that fills only the profile fields not set yet. | | `getLatestKnowledgeImport` | GET | `/v1/businesses/:id/knowledge/imports/latest` | | agent | The newest website import and its status (poll after startKnowledgeImport). | | `listKnowledgeDocuments` | GET | `/v1/businesses/:id/knowledge/documents` | | agent | List uploaded knowledge documents and the tier's usage limits. | | `uploadKnowledgeDocument` | POST | `/v1/businesses/:id/knowledge/documents` | | agent | Upload a PDF, DOCX, TXT, Markdown or HTML file (base64 JSON); its text joins the knowledge for every call. | | `getKnowledgeDocument` | GET | `/v1/businesses/:id/knowledge/documents/:documentId` | | agent | Read one uploaded document with its extracted sections. | | `deleteKnowledgeDocument` | DELETE | `/v1/businesses/:id/knowledge/documents/:documentId` | | agent | Delete an uploaded document and its extracted text. | | `getKnowledgeDocumentFile` | GET | `/v1/businesses/:id/knowledge/documents/:documentId/file` | | agent | A short-lived download URL for an uploaded document's original file. | | `getKnowledgeUsage` | GET | `/v1/businesses/:id/knowledge/usage` | | agent | Knowledge usage against the current tier's limits. | | `setKnowledgeTier` | POST | `/v1/businesses/:id/knowledge/tier` | yes | agent | Switch the knowledge tier (starter or pro, billed monthly from the wallet). | ### setup-chat | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `getSetupChat` | GET | `/v1/businesses/:id/setup-chat` | yes | agent | The open setup-agent conversation, or null. | | `startSetupChat` | POST | `/v1/businesses/:id/setup-chat` | yes | agent | Start a setup-agent chat that fills knowledge and routing through conversation. | | `sendSetupChat` | POST | `/v1/businesses/:id/setup-chat/:conversationId/messages` | yes | agent | Send a message to the setup agent and get its reply. | | `closeSetupChat` | POST | `/v1/businesses/:id/setup-chat/:conversationId/close` | yes | agent | Close a setup-agent conversation. | ### voices | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `listVoices` | GET | `/v1/voices` | | agent | List the receptionist voices; set one with updateBusiness `voice`. | ### routing | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `getRouting` | GET | `/v1/businesses/:id/routing` | | agent | Read the routing policy: when the AI answers, transfers or takes a message, and the spend cap. | | `putRouting` | PUT | `/v1/businesses/:id/routing` | yes | agent | Change the routing policy (transfer targets on the business's own team, handoff preferences, recording, spend cap). Every call is answered by the AI: RingKit's live-rep pool is not offered, so ai_first/human_first modes act as ai_only. | | `getVoiceStack` | GET | `/v1/businesses/:id/voice-stack` | | agent | Read which voice stack answers the AI tier (livekit, the default, or elevenlabs: the ElevenLabs Agents platform running the same receptionist brain), what is applied at the carrier now, and the steps of the last switch. | | `putVoiceStack` | PUT | `/v1/businesses/:id/voice-stack` | yes | agent | Switch the voice stack (livekit | elevenlabs). Applies it synchronously (ElevenLabs agent sync, number import or removal, carrier routing), so it can take a few seconds; needs an active number and ai_only/ai_first routing. A failed step answers 502 voice_stack_failed with the status in details; send the same PUT again to resume. | ### numbers | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `searchNumbers` | GET | `/v1/numbers/search` | | agent | Search phone numbers available to order, by area code or region ($1.99 a month each). | | `provisionNumber` | POST | `/v1/businesses/:id/numbers` | yes | agent | Order a phone number for the business (needs $2 of paid wallet credit, or, for the first number of a business whose owner signed in, trial credit with a 7-day trial). | | `listNumbers` | GET | `/v1/businesses/:id/numbers` | | agent | List the business's phone numbers. | | `releaseNumber` | DELETE | `/v1/businesses/:id/numbers/:numberId` | yes | agent | Release a phone number; its rental stops. | | `getForwarding` | GET | `/v1/businesses/:id/numbers/:numberId/forwarding` | yes | agent | Forwarding to a RingKit number: the owner's business phone, when forwarding was last proven, and the last forwarding check. | | `putForwarding` | PUT | `/v1/businesses/:id/numbers/:numberId/forwarding` | yes | agent | Save the owner's existing business phone that forwards (on no-answer) to this RingKit number. | | `startForwardingCheck` | POST | `/v1/businesses/:id/numbers/:numberId/forwarding/check` | yes | agent | Ring the business phone from the RingKit number and wait up to 60 s for it to forward back; poll getForwarding for the outcome. | ### calls | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `listCalls` | GET | `/v1/businesses/:id/calls` | | agent | List calls, newest first (paginated), with outcome, intent, summary and cost. | | `getRecording` | GET | `/v1/businesses/:id/calls/:callId/recording` | | agent | A short-lived URL for a call's recording. | | `callSseTicket` | POST | `/v1/businesses/:id/calls/:callId/sse-ticket` | | dashboard | A 60-second single-use ticket for a call's live transcript stream (EventSource). | | `getCall` | GET | `/v1/businesses/:id/calls/:callId` | | agent | One call with its full transcript. | | `testCall` | POST | `/v1/businesses/:id/test-call` | yes | agent | Ring one of the business's verified numbers from its RingKit number with the AI (a real, billed call). | | `startDemoCall` | POST | `/v1/businesses/:id/demo-call` | | dashboard | Talk to the receptionist from the browser over WebRTC (LiveKit room + join token): no number needed, not billed, not recorded, a few minutes at most, two per business. | | `simulateCall` | POST | `/v1/businesses/:id/simulate` | | agent | Run a text-mode call against the real receptionist to test knowledge and routing; no number needed; billed like a call at the AI rate from the wallet (the free trial credit covers it). | ### destinations | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `verifyDestination` | POST | `/v1/businesses/:id/destinations/verify` | yes | agent | Call a number and read it a 6-digit code, to prove the business controls it. | | `confirmDestination` | POST | `/v1/businesses/:id/destinations/confirm` | yes | agent | Confirm a number with the code its verification call read. | | `listDestinations` | GET | `/v1/businesses/:id/destinations` | yes | agent | Numbers the business proved it controls (allowed test-call targets). | | `listOutboundConsents` | GET | `/v1/businesses/:id/outbound-consents` | yes | agent | The audit trail of outbound calls RingKit placed for the business. | ### agents | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `getAgentControls` | GET | `/v1/businesses/:id/agent-controls` | | agent | Which human agents may take the business's calls (vetted only, blocked agents). Inactive while RingKit's live-rep pool is not offered. | | `putAgentControls` | PUT | `/v1/businesses/:id/agent-controls` | yes | agent | Change which human agents may take the business's calls. Inactive while RingKit's live-rep pool is not offered. | | `createBusinessAgentInvite` | POST | `/v1/businesses/:id/agent-invites` | yes | agent | An invite code for the business's own receptionist, who will take only its calls. | | `markOwnAgent` | POST | `/v1/businesses/:id/agents/own` | yes | agent | Mark an agent who enrolled with the business's invite as its own staff. | | `createAgent` | POST | `/v1/agents` | | console | Dev only: create a gig agent and get a dev bearer token. | | `enrollAgent` | POST | `/v1/agents/enroll` | | console | Enroll as a human agent with an invite code; emails a console sign-in link. | | `agentSseTicket` | POST | `/v1/agents/me/sse-ticket` | | console | A 60-second single-use ticket for the agent console event stream. | | `agentMe` | GET | `/v1/agents/me` | | console | The signed-in human agent. | | `agentEarnings` | GET | `/v1/agents/me/earnings` | | console | The agent's unpaid earnings and payouts. | | `agentCommand` | POST | `/v1/agents/me/calls/:callId/command` | | console | Act on the agent's active call: ask the copilot, speak (text mode), or take an action (take a message, transfer, SMS, flag spam, end). | | `getPresence` | GET | `/v1/agents/me/presence` | | console | The agent's presence (offline, available, wrap-up, ...). | | `setPresence` | PUT | `/v1/agents/me/presence` | | console | Go available, offline or into wrap-up. | | `acceptOffer` | POST | `/v1/agents/me/offers/:callId/accept` | | console | Accept a call offer and get the token to join it. | | `declineOffer` | POST | `/v1/agents/me/offers/:callId/decline` | | console | Decline a call offer. | ### wallet | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `getWallet` | GET | `/v1/businesses/:id/wallet` | yes | agent | Wallet balance, unspent trial credit and auto-refill settings. | | `topUp` | POST | `/v1/businesses/:id/wallet/top-ups` | yes | agent | Start a wallet top-up: returns a Stripe checkoutUrl for the human to pay. | | `confirmTopUp` | POST | `/v1/businesses/:id/wallet/top-ups/:sessionId/confirm` | yes | dashboard | After Stripe Checkout returns, credit a paid session (the webhook does it too). | | `createTopUpIntent` | POST | `/v1/businesses/:id/wallet/top-ups/intents` | yes | dashboard | An in-page top-up PaymentIntent for the Stripe Payment Element. | | `confirmTopUpIntent` | POST | `/v1/businesses/:id/wallet/top-ups/intents/:paymentIntentId/confirm` | yes | dashboard | After an in-page payment, credit a paid PaymentIntent (the webhook does it too). | | `getAutoRefill` | GET | `/v1/businesses/:id/wallet/auto-refill` | yes | agent | Read the auto-refill settings. | | `putAutoRefill` | PUT | `/v1/businesses/:id/wallet/auto-refill` | yes | agent | Turn auto-refill on or off and set its threshold and amount (needs a saved card to run). | | `createSetupIntent` | POST | `/v1/businesses/:id/wallet/setup-intent` | yes | dashboard | A Stripe SetupIntent to save a card for auto-refill. | | `listLedger` | GET | `/v1/businesses/:id/ledger` | yes | agent | Wallet ledger entries (top-ups, call charges, rentals), newest first. | ### webhooks | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `listWebhooks` | GET | `/v1/businesses/:id/webhooks` | | agent | List the business's webhook endpoints. | | `createWebhook` | POST | `/v1/businesses/:id/webhooks` | yes | agent | Register a webhook for call.completed, message.taken or balance.low; the signing secret is returned once. | ### feedback | Route | Method | Path | Owner only | Audience | Summary | |---|---|---|---|---|---| | `submitFeedback` | POST | `/v1/feedback` | | agent | Send product feedback to the RingKit team (any caller, including anonymous). | ## MCP - Endpoint: https://api.ringkit.com/mcp (Streamable HTTP, stateless, JSON responses). Server card: https://api.ringkit.com/.well-known/mcp.json. Short guide: https://api.ringkit.com/llms.txt. - Authenticate the MCP connection with the same `Authorization: Bearer rk_live_...` header. `ringkit_create_business` needs no key and returns one. - Tools are named `ringkit_` after the REST route they call: `createBusiness` is `ringkit_create_business`, `startKnowledgeImport` is `ringkit_start_knowledge_import`, `provisionNumber` is `ringkit_provision_number`. Arguments are the route's input, plus an optional `businessId` (defaults to the key's business) and any other path parameter (`callId`, `documentId`, ...); results are the route's output. The server's `tools/list` is the authoritative list. - Mutating tools take an optional `idempotencyKey` argument with the same 24-hour replay as the REST header.