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.
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 }, ...]
}
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.
GET/numbers/available
Query: country (2-letter ISO, default US), area_code, contains,
sms (true to require SMS), limit (max 50).
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: phone_number (exact, from the search) or area_code (we pick one).
The answering webhook is wired to us automatically.
India (+91) and other regulated countries. Search with country=IN.
Indian numbers require a Twilio-approved regulatory bundle and address (a one-time
KYC step you complete in your Twilio account). Create the Bundle + Address in Twilio, then pass
their SIDs:
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.
Lead webhook
Register one HTTPS endpoint and we push every client's captured lead to it in real time.
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://..." } }
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 |
| 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 |
| GET | /webhook | Get lead webhook + secret |
| PUT | /webhook | Set lead webhook |
| DELETE | /webhook | Remove lead webhook |