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.
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.
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/jsonon 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. PATCHis 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"}.
| Status | Meaning |
|---|---|
200 / 201 | Success |
400 | Malformed request (bad JSON) |
401 | Missing or invalid API key |
403 | Reseller account not active |
404 | Client, call, or number not found (or not yours) |
409 | Conflict (e.g. no Twilio connected, or client already has a number) |
422 | Validation error (a field value was rejected) |
429 | Rate limit exceeded |
502 | Upstream (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}.
| Field | Type | Notes |
|---|---|---|
receptionist_name | string | Name the front desk gives. Default "Sam". |
industry | string | An industry slug (see Industries). Default "general". |
voice_persona | string | One of the voices. |
language | string | A supported language name (see Languages). Default "English". |
custom_greeting | string / null | Override the opening line. |
custom_instructions | string / null | Extra guidance for the front desk. |
record_calls | boolean | Default true. |
recording_notice | boolean | Play a recording notice. Default false. |
after_hours_only | boolean | Only answer outside business hours. |
blocked_numbers | string / null | Comma-separated E.164 numbers to reject. |
transfer_enabled | boolean | Needs transfer_number set, or it stays off. |
transfer_number | string / null | E.164 number to transfer to. |
transfer_trigger | string | on_request, on_emergency, or always_offer. |
quote_prices | boolean | Needs pricing_notes set, or it stays off. |
pricing_notes | string / null | The price list the front desk may quote from. |
notify_email | string | Where lead emails go. |
notify_sms | string / null | E.164 number for lead texts. |
minute_policy | string | overage (keep answering, bill overage) or hard_cap (stop at zero). |
spend_cap_cents | integer | Monthly overage cap in cents. 0 = no cap. |
Voices
| Value | Description |
|---|---|
sage | Warm, steady, female |
ash | Warm, easy, male |
coral | Bright, friendly, female |
verse | Calm, 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.
| Language | Code | Spoken |
|---|---|---|
| English | en | strong |
| Spanish | es | strong |
| French | fr | strong |
| German | de | strong |
| Italian | it | strong |
| Portuguese | pt | strong |
| Dutch | nl | understood, varies |
| Polish | pl | understood, varies |
| Russian | ru | understood, varies |
| Ukrainian | uk | understood, varies |
| Arabic | ar | strong |
| Hindi | hi | strong |
| Urdu | ur | understood, varies |
| Punjabi | pa | understood, varies |
| Bengali | bn | strong |
| Tamil | ta | strong |
| Telugu | te | strong |
| Marathi | mr | understood, varies |
| Gujarati | gu | understood, varies |
| Kannada | kn | understood, varies |
| Malayalam | ml | understood, varies |
| Odia | or | understood, varies |
| Assamese | as | understood, varies |
| Nepali | ne | understood, varies |
| Tagalog | tl | understood, varies |
| Vietnamese | vi | understood, varies |
| Thai | th | understood, varies |
| Indonesian | id | understood, varies |
| Malay | ms | understood, varies |
| Turkish | tr | understood, varies |
| Greek | el | understood, varies |
| Hebrew | he | understood, varies |
| Swedish | sv | understood, varies |
| Norwegian | no | understood, varies |
| Danish | da | understood, varies |
| Finnish | fi | understood, varies |
| Czech | cs | understood, varies |
| Romanian | ro | understood, varies |
| Hungarian | hu | understood, varies |
| Japanese | ja | strong |
| Korean | ko | understood, varies |
| Chinese | zh | strong |
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
| Tier | Price | Minutes/mo |
|---|---|---|
starter | $99 | 500 |
pro | $199 | 1,200 |
business | $399 | 2,800 |
scale | $699 | 6,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. Currentlyconnected: false: calls withprovider=plivoreturn503until 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:
| Field | Type | Notes |
|---|---|---|
to | string | Required. E.164 number to call. |
objective | string | verify_order (default) or custom. |
context | object | Echoed back on the webhook. For verify_order: order_number, amount, items, business. |
script | string | Required when objective is custom — free-text instructions for other verification/reminder use cases. |
language | string | Default English. Use a supported language name (see Languages); Hindi/Hinglish supported. |
max_attempts | integer | 1–5, default 2. no_answer only fires after these are exhausted. |
retry_after_minutes | integer | Delay 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
} }
| Outcome | Meaning |
|---|---|
confirmed | Customer confirmed the order. |
cancelled | Customer cancelled. |
no_answer | No pickup after all max_attempts. |
voicemail | Reached voicemail / answering machine. |
invalid_number | Number 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
| Method | Path | Purpose |
|---|---|---|
| GET | /meta | Reference data |
| GET | /account | Your account summary |
| POST | /clients | Create a client |
| GET | /clients | List clients |
| GET | /clients/{id} | Get a client + usage |
| PATCH | /clients/{id} | Update configuration |
| POST | /clients/{id}/pause | Pause answering |
| POST | /clients/{id}/resume | Resume answering |
| DELETE | /clients/{id} | Release number + soft-delete |
| PUT | /clients/{id}/plan | Set plan or minutes |
| GET | /clients/{id}/usage | Minute balance |
| POST | /clients/{id}/minutes | Grant top-up minutes |
| GET | /numbers/available | Search numbers |
| POST | /clients/{id}/number | Buy + assign a number (optional provider) |
| POST | /clients/{id}/kyc | Submit KYC docs (regulated countries) |
| DELETE | /clients/{id}/number | Release the number |
| GET | /calls | Calls across all clients |
| GET | /clients/{id}/calls | A client's calls |
| GET | /clients/{id}/calls/{call_id} | One call |
| POST | /clients/{id}/calls/outbound | Trigger an AI verification call |
| GET | /webhook | Get lead webhook + secret |
| PUT | /webhook | Set lead webhook |
| DELETE | /webhook | Remove lead webhook |