HiThisIs your AI receptionist

Reseller API

Offer the front desk inside your own product. This API runs the whole lifecycle for your customers: create a client business, configure how the front desk answers, buy a phone number, set a plan, and pull every call and lead. Everything the dashboard does, you can do here.

Want to resell it, not just read the docs?

Get the white-label reseller license: put your own brand on the front desk, run it on your keys or ours, and sell it to local businesses at your own price. No code required to start.

See the reseller offer →

Introduction

The API is JSON over HTTPS. You authenticate as a reseller with a single bearer key; every request is automatically scoped to your own account, so you can only ever see or change your own clients. There is no SDK to install: the examples below are plain curl and map directly to any HTTP client.

Base URL & versioning

https://hithisis.com/api/reseller/v1

All paths below are relative to this base. The version is pinned in the path (v1); breaking changes ship under a new version, so your integration keeps working.

Authentication

Send your reseller key as a bearer token on every request. Generate and rotate it in your dashboard under API keys. Keep it server-side, never in a browser or mobile app: it can create clients and buy numbers.

Authorization: Bearer htir_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Rotating or revoking the key stops the old one immediately. A missing or bad key returns 401; a suspended reseller account returns 403.

Conventions

  • Send Content-Type: application/json on POST, PUT, and PATCH.
  • Timestamps are Unix epoch seconds (integers).
  • Phone numbers are E.164, e.g. +19735551234.
  • Money is either whole US dollars (plan prices) or integer cents (spend_cap_cents), as noted per field.
  • Booleans accept true/false.
  • PATCH is a partial update: send only the fields you want to change.

Rate limits

120 requests per minute per key. Over the limit returns 429. If you need a higher limit for a bulk import, contact us. Space out large batches and retry 429 responses after a short backoff.

Errors

Errors return the HTTP status plus a JSON body {"detail": "what went wrong"}.

StatusMeaning
200 / 201Success
400Malformed request (bad JSON)
401Missing or invalid API key
403Reseller account not active
404Client, call, or number not found (or not yours)
409Conflict (e.g. no Twilio connected, or client already has a number)
422Validation error (a field value was rejected)
429Rate limit exceeded
502Upstream (Twilio) error

Quickstart

Create a client, give it a number, and you're live:

# 1. Create a client business
curl -X POST https://hithisis.com/api/reseller/v1/clients \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name":"Ace Plumbing","industry":"plumbing","voice_persona":"sage","notify_email":"owner@ace.com"}'
# -> 201 { "id": "cust_...", ... }

# 2. Find an available number
curl "https://hithisis.com/api/reseller/v1/numbers/available?country=US&area_code=973" \
  -H "Authorization: Bearer $KEY"

# 3. Buy it for the client (the answering webhook is wired automatically)
curl -X POST https://hithisis.com/api/reseller/v1/clients/cust_.../number \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"phone_number":"+19735551234"}'

Reference data GET/meta

Returns the valid values for the fields below (voices, languages, industries, transfer triggers, minute policies, plans, packs), straight from the live system. Build your dropdowns from this so you never hard-code a value that changes.

{
  "voices": [{ "id": "sage", "label": "Sage", "desc": "Warm, steady, female" }, ...],
  "languages": [{ "name": "Hindi", "code": "hi", "spoken": true }, ...],
  "industries": [{ "slug": "plumbing", "label": "Plumbing" }, ...],
  "transfer_triggers": ["always_offer", "on_emergency", "on_request"],
  "minute_policies": ["hard_cap", "overage"],
  "plans": [{ "tier": "pro", "name": "Pro", "price_usd": 199, "minutes": 1200 }, ...],
  "minute_packs": [{ "id": "small", "minutes": 250, "price_usd": 35 }, ...],
  "number_providers": [
    { "id": "twilio", "name": "Twilio", "countries": ["US","CA","GB","AU"],
      "kyc_required": false, "provisioning_time": "instant", "connected": true },
    { "id": "plivo", "name": "Plivo", "countries": ["IN","US","CA","GB","AU"],
      "kyc_required": true, "provisioning_time": "1-2 days", "connected": false }
  ]
}

Configuration fields

These are the settings on a client, set at create time or via PATCH /clients/{id}.

FieldTypeNotes
receptionist_namestringName the front desk gives. Default "Sam".
industrystringAn industry slug (see Industries). Default "general".
voice_personastringOne of the voices.
languagestringA supported language name (see Languages). Default "English".
custom_greetingstring / nullOverride the opening line.
custom_instructionsstring / nullExtra guidance for the front desk.
record_callsbooleanDefault true.
recording_noticebooleanPlay a recording notice. Default false.
after_hours_onlybooleanOnly answer outside business hours.
blocked_numbersstring / nullComma-separated E.164 numbers to reject.
transfer_enabledbooleanNeeds transfer_number set, or it stays off.
transfer_numberstring / nullE.164 number to transfer to.
transfer_triggerstringon_request, on_emergency, or always_offer.
quote_pricesbooleanNeeds pricing_notes set, or it stays off.
pricing_notesstring / nullThe price list the front desk may quote from.
notify_emailstringWhere lead emails go.
notify_smsstring / nullE.164 number for lead texts.
minute_policystringoverage (keep answering, bill overage) or hard_cap (stop at zero).
spend_cap_centsintegerMonthly overage cap in cents. 0 = no cap.

Voices

ValueDescription
sageWarm, steady, female
ashWarm, easy, male
coralBright, friendly, female
verseCalm, even, neutral

Languages (42)

Set with the language field. Understanding the caller is broad across every language below; the spoken voice is strongest where "Spoken" says strong (Hindi and English are the most natural for India). Pilot others before offering them as native quality.

LanguageCodeSpoken
Englishenstrong
Spanishesstrong
Frenchfrstrong
Germandestrong
Italianitstrong
Portugueseptstrong
Dutchnlunderstood, varies
Polishplunderstood, varies
Russianruunderstood, varies
Ukrainianukunderstood, varies
Arabicarstrong
Hindihistrong
Urduurunderstood, varies
Punjabipaunderstood, varies
Bengalibnstrong
Tamiltastrong
Telugutestrong
Marathimrunderstood, varies
Gujaratiguunderstood, varies
Kannadaknunderstood, varies
Malayalammlunderstood, varies
Odiaorunderstood, varies
Assameseasunderstood, varies
Nepalineunderstood, varies
Tagalogtlunderstood, varies
Vietnameseviunderstood, varies
Thaithunderstood, varies
Indonesianidunderstood, varies
Malaymsunderstood, varies
Turkishtrunderstood, varies
Greekelunderstood, varies
Hebrewheunderstood, varies
Swedishsvunderstood, varies
Norwegiannounderstood, varies
Danishdaunderstood, varies
Finnishfiunderstood, varies
Czechcsunderstood, varies
Romanianrounderstood, varies
Hungarianhuunderstood, varies
Japanesejastrong
Koreankounderstood, varies
Chinesezhstrong

Industries (44)

Use the slug. The field also accepts a trade we don't list (it falls back to a general persona). Full list at GET /meta. Examples: hvac, plumbing, roofing, electrical, landscaping, painting, general-contracting, handyman, fencing, concrete, flooring, cleaning, pest-control, garage-door ...

Plans & packs

TierPriceMinutes/mo
starter$99500
pro$1991,200
business$3992,800
scale$6996,000

One-time minute packs (grant with POST /clients/{id}/minutes or your own billing): 250 min ($35), 600 min ($75), 1,500 min ($165).

Clients

POST/clients — create a client

Only name is required. email and any configuration field are optional. A client is created unmetered (you bill your own customer); set a platform plan later with PUT /clients/{id}/plan if you want us to meter minutes.

POST /clients
{ "name": "Ace Plumbing", "email": "owner@ace.com", "industry": "plumbing",
  "voice_persona": "sage", "language": "English", "notify_email": "owner@ace.com" }

-> 201
{ "id": "cust_...", "name": "Ace Plumbing", "status": "agency", "plan_tier": "agency",
  "phone_number": null, "config": { ... }, "usage": { ... } }

GET/clients — list your clients

GET/clients/{id} — one client, with usage

{ "id": "cust_...", "name": "Ace Plumbing", "email": "owner@ace.com",
  "industry": "plumbing", "status": "active", "plan_tier": "pro",
  "phone_number": "+19735551234", "created_at": 1751500000,
  "config": { "receptionist_name": "Sam", "voice_persona": "sage", "language": "English",
              "transfer_enabled": false, "minute_policy": "overage", "spend_cap_cents": 0, ... },
  "usage": { "metered": true, "included": 1200, "available": 1110, ... } }

PATCH/clients/{id} — update configuration

Partial update. Two coupled rules are enforced: enabling transfer requires a transfer_number, and enabling quoting requires pricing_notes.

PATCH /clients/cust_...
{ "voice_persona": "ash", "transfer_enabled": true,
  "transfer_number": "+19735550000", "transfer_trigger": "on_emergency" }

POST/clients/{id}/pause  ·  POST/clients/{id}/resume

Pause stops the line answering (status paused). Resume sets it back to active.

DELETE/clients/{id}

Releases the client's number (if any) from your Twilio account and soft-deletes the client (status disabled; call history is preserved). Returns { "number_released": true|false }.

Plans, usage & minutes

PUT/clients/{id}/plan

Body: {"tier":"pro"} (a platform plan that meters minutes), or {"minutes_included":2000} for a custom allotment, or {"tier":"agency"} to keep it unmetered (you bill the customer yourself).

GET/clients/{id}/usage

{ "metered": true, "included": 1200, "rollover": 0, "addon": 250,
  "used": 340, "overage": 0, "available": 1110,
  "period_start": 1751500000, "reset_at": 1754092000,
  "policy": "overage", "spend_cap_cents": 0 }

POST/clients/{id}/minutes

Grant top-up minutes to the current period. Body: {"minutes":500}.

Phone numbers

Numbers are searched and bought in the Twilio account you connect under API keys. You own the number and its cost. Without a connected Twilio account these return 409.

Providers (multi-carrier)

The platform is provider-pluggable, so it isn't locked to one carrier. Every number call accepts an optional provider (default twilio); the providers, the countries each covers, and whether each is connected come from number_providers in GET /meta — build your picker from that.

  • twilio — live now. US, CA, GB, AU. Instant, no KYC.
  • plivo — registered for India (+91) plus US/CA/global, kyc_required: true, ~1-2 day provisioning. Currently connected: false: calls with provider=plivo return 503 until the Plivo account and audio bridge are wired. Integrate against the contract now; it goes live with no breaking changes.

Each client reports number_provider and number_status (active | kyc_pending | kyc_rejected | null) on GET /clients/{id}.

KYC (regulated countries like India)

With a KYC-gated provider, buying returns { "number_status": "kyc_pending", "kyc_url": "..." }; you complete verification via POST /clients/{id}/kyc (GST/PAN/ address docs). The number activates on approval and we fire a number.activated webhook (below) so your UI flips from pending to live without polling. Returns 503 until a KYC-capable provider is connected.

GET/numbers/available

Query: country (2-letter ISO, default US), area_code, contains, sms (true to require SMS), limit (max 50), provider (default twilio).

GET /numbers/available?country=US&area_code=973
-> { "country": "US", "numbers": [
     { "phone_number": "+19735550111", "locality": "Newark", "region": "NJ",
       "capabilities": { "voice": true, "sms": true, "mms": true } }, ... ] }

POST/clients/{id}/number

Body: optional provider (default twilio), and phone_number (exact, from the search) or area_code (we pick one). The answering webhook is wired to us automatically. Returns number_status: "active" for instant providers, or "kyc_pending" for KYC-gated ones.

India via Twilio (available now): search country=IN; Twilio requires a regulatory bundle and address you create in your Twilio account, then pass their SIDs. India via Plivo (recommended, pending connection): pass provider=plivo and use the KYC flow above instead of Twilio bundles.

POST /clients/cust_.../number
{ "phone_number": "+9122XXXXXXXX", "bundle_sid": "BUxxxx", "address_sid": "ADxxxx" }

DELETE/clients/{id}/number

Releases the number from your Twilio account and clears it from the client.

Calls & leads

GET/calls — across all your clients (limit, max 500)

GET/clients/{id}/calls  ·  GET/clients/{id}/calls/{call_id}

{ "id": "call_...", "client_id": "cust_...", "type": "inbound", "status": "captured",
  "caller": { "name": "Dana Reed", "phone": "(973) 555-0111",
              "phone_e164": "+19735550111", "email": null, "address": "12 Oak St" },
  "service": "water heater leak", "urgency": "emergency", "preferred_time": null,
  "duration_seconds": 96, "recording_url": "https://...", "transcript": "...",
  "started_at": 1751500000, "ended_at": 1751500096 }

status: in_progress, captured, abandoned, failed. urgency: emergency, same_day, scheduled, or null.

Outbound calls (AI verification)

Trigger an AI call that dials a number, states its purpose, and reports the outcome — the primary use case is COD (cash-on-delivery) order verification for ecommerce. The AI says it's calling from your client's business about the order, asks the customer to confirm or cancel, and handles Hindi / English / Hinglish. Outbound minutes are metered against the client's balance exactly like inbound.

Status: the API contract below is final — build against it now. Live dialing is enabled per account after a one-time certification test call, so the endpoint returns 503 until your account is switched on (validation still runs, so you can develop against it). Dialing +91 also requires your connected Twilio to allow India.

POST/clients/{id}/calls/outbound

Body:

FieldTypeNotes
tostringRequired. E.164 number to call.
objectivestringverify_order (default) or custom.
contextobjectEchoed back on the webhook. For verify_order: order_number, amount, items, business.
scriptstringRequired when objective is custom — free-text instructions for other verification/reminder use cases.
languagestringDefault English. Use a supported language name (see Languages); Hindi/Hinglish supported.
max_attemptsinteger1–5, default 2. no_answer only fires after these are exhausted.
retry_after_minutesintegerDelay before re-dialing a no-answer. Default 60.
POST /clients/cust_.../calls/outbound
{ "to": "+919812345678", "objective": "verify_order",
  "context": { "order_number": "#4218", "amount": "₹1,798",
               "items": "2× Yoga Mat", "business": "StyleKart" },
  "language": "Hindi", "max_attempts": 2, "retry_after_minutes": 60 }

-> 201  { "id": "call_...", "status": "queued" }

A custom objective ignores the order fields and follows your script:

{ "to": "+919812345678", "objective": "custom", "language": "Hinglish",
  "script": "Remind them their appointment is tomorrow at 4pm and ask if that still works." }

The verification-completed webhook

When the call finishes (or all attempts are exhausted) we POST to your webhook, signed the same way as lead.captured:

{ "event": "call.verification_completed", "client_id": "cust_...",
  "call": {
    "id": "call_...", "to": "+919812345678",
    "outcome": "confirmed",        // confirmed | cancelled | no_answer | voicemail | invalid_number
    "attempts": 1,
    "recordingUrl": "https://...", "transcript": "...",
    "context": { "order_number": "#4218", "amount": "₹1,798", ... }  // echoed from your request
  } }
OutcomeMeaning
confirmedCustomer confirmed the order.
cancelledCustomer cancelled.
no_answerNo pickup after all max_attempts.
voicemailReached voicemail / answering machine.
invalid_numberNumber invalid or wrong person.

Outbound calls also appear in GET /clients/{id}/calls with "type": "outbound" and the outcome field.

Webhooks

Register one HTTPS endpoint and we push every client's captured lead to it in real time. The same endpoint also receives number.activated and call.verification_completed.

PUT/webhook  ·  GET/webhook  ·  DELETE/webhook

PUT body {"url":"https://your-crm.com/hooks/leads"} returns a signing secret. GET returns the current URL and secret. DELETE removes it.

Payload

POST (your URL)
X-HiThisIs-Signature: <hex hmac-sha256 of the raw body>

{ "event": "lead.captured", "client_id": "cust_...", "business": "Ace Plumbing",
  "lead": { "name": "Dana Reed", "phone": "+19735550111", "address": "12 Oak St",
            "company": null, "service": "water heater leak",
            "urgency": "emergency", "preferredTime": null },
  "call": { "id": "call_...", "durationSeconds": 96, "recordingUrl": "https://..." } }

Other events on the same webhook

The same signed endpoint also receives:

{ "event": "number.activated", "client_id": "cust_...", "phone_number": "+91..." }

Fired when a client's number goes live — instantly for Twilio, or on KYC approval for a KYC-gated provider — so you can flip the client from kyc_pending to live without polling. Switch on event to tell them apart.

Verify the signature

# Python
import hmac, hashlib
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers["X-HiThisIs-Signature"]):
    abort(400)

Respond 2xx quickly. We route by client_id, which is globally unique to your account, so a shared customer email across apps never collides.

Full endpoint list

MethodPathPurpose
GET/metaReference data
GET/accountYour account summary
POST/clientsCreate a client
GET/clientsList clients
GET/clients/{id}Get a client + usage
PATCH/clients/{id}Update configuration
POST/clients/{id}/pausePause answering
POST/clients/{id}/resumeResume answering
DELETE/clients/{id}Release number + soft-delete
PUT/clients/{id}/planSet plan or minutes
GET/clients/{id}/usageMinute balance
POST/clients/{id}/minutesGrant top-up minutes
GET/numbers/availableSearch numbers
POST/clients/{id}/numberBuy + assign a number (optional provider)
POST/clients/{id}/kycSubmit KYC docs (regulated countries)
DELETE/clients/{id}/numberRelease the number
GET/callsCalls across all clients
GET/clients/{id}/callsA client's calls
GET/clients/{id}/calls/{call_id}One call
POST/clients/{id}/calls/outboundTrigger an AI verification call
GET/webhookGet lead webhook + secret
PUT/webhookSet lead webhook
DELETE/webhookRemove lead webhook