What it does
Your call centre, CRM or ERP sends AskWhiz the client, the equipment and the problem. AskWhiz turns it into a submitted request for your company, exactly like a request a restaurant sends from the app: your dispatchers are notified and accept it, on WhatsApp or on the web, by choosing the technician and the visit time.
- Only service companies can use the API: organizations with the service-provider role in AskWhiz. Requests always go to your own dispatchers.
- The API only creates requests. It does not list, read, change or cancel them; your team does that in AskWhiz.
- There is no field for a technician, an appointment or your own reference number: your dispatchers choose the technician and the time when they accept the request.
How clients and equipment are matched
AskWhiz never guesses. Each request uses the records it finds with certainty, creates the ones that do not exist yet, or asks a dispatcher to choose.
Clients
AskWhiz looks for the client among your company's clients without an AskWhiz account and the restaurants linked to your company in AskWhiz:
- the same phone number or email with a fitting name (the same name, or one containing the other) is a certain match; a linked restaurant is matched by its company phone and its name;
- the same name is a certain match too: capital letters, accents, punctuation and endings such as SRL, S.A. or GmbH are ignored;
- the same phone or email under another name (one owner with several restaurants, a manager's phone) is never used on its own: a dispatcher decides;
- a name that only contains the other (“Restaurant Centru” and “Restaurant Centru Cluj”), without the same phone or email, is only a possible match.
Exactly one certain match, and no same phone or email under another name: that client is used. A client without an AskWhiz account also gains any detail it lacks (address, phone, email, contact) from your request; nothing is overwritten. No match: AskWhiz adds the client to your client list. Otherwise (several certain matches, the same phone or email under another name, or only possible matches) a dispatcher chooses, and can also say the client is new.
Equipment
Each unit is then matched on its own among that client's registered equipment, in this order:
- the same serial number (unless both brands are known and differ);
- then the same brand and model (the same model when you send no brand);
- then the same type, for example “Combi oven”.
The first step that finds anything decides: one unit is used; several units are left to a dispatcher; nothing at any step adds a new unit to the client. A registered unit whose serial, brand or model contradicts yours is never matched by a weaker key, and two units of one request never silently become the same machine: a dispatcher is asked.
Several units in one request
Send every unit of one call-out in one request: 1 to 20 units.
- A client without an AskWhiz account gets one ticket with every unit.
- A restaurant linked to you in AskWhiz gets as many tickets as the units-per-ticket limit of your service agreement with it requires. If it allows one unit per ticket, three units become three tickets;
ticketslists them all.
What awaiting_dispatcher means
The client or a unit matched more than one record, so AskWhiz did not guess. Your dispatchers get a numbered WhatsApp message with the choices, and the request also waits at the top of the requests inbox in AskWhiz on the web. A dispatcher picks the right records, or says a record is new, and the ticket is created then.
If nobody chooses within 2 working hours, the dispatchers are reminded once; 2 working hours later your admins get an email. Your system does not need to do anything. The API does not report the ticket created later: replaying the same Idempotency-Key still answers awaiting_dispatcher. Keep request_id in case you contact AskWhiz support.
Get an API key
An organization admin creates keys in the AskWhiz web app under Settings → API keys (the tab is shown only to admins of service companies). Name each key after the system that will use it, and optionally let it expire in 30 days, 90 days or 1 year.
The key looks like awz_live_… and is shown once, right after it is created. AskWhiz stores only a hash of it, so a lost key cannot be shown again: revoke it and create a new one. An organization can hold up to 20 active keys.
Keep the key on your server, in a secret store or an environment variable. Never put it in a web page, a mobile app or a code repository.
When a key stops working
- an admin revokes it (immediately, answering
401 API_KEY_REVOKED); - its expiry date passes (
401 API_KEY_EXPIRED); - the admin who created it leaves the organization or is no longer an admin (
401 API_KEY_OWNER_INACTIVE): ask a current admin for a new key; - your organization is suspended (
403 ORG_SUSPENDED): the key works again once an admin restores billing.
A key acts for your organization with the authority of the admin who created it. Every ticket it creates is marked “via API” in the ticket history, with the key's name visible only inside your organization.
Send a request
One endpoint. The body is JSON with snake_case field names. Call it from your server only.
POST https://api.askwhiz.io/api/v1/ticketsHeaders
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer followed by your full API key. |
Content-Type | Yes | application/json |
Idempotency-Key | Yes | A value you choose, unique per request, for example your call or case number: 1 to 255 visible ASCII characters. See below. |
Authorization: Bearer awz_live_abcdefghijk2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: call-2026-09-28-0042Idempotency-Key: retries are safe
Every call needs an Idempotency-Key header: 1 to 255 visible ASCII characters (letters, digits and punctuation; no spaces, no accents). Use one value per real request, for example your call or case number.
- Sending the same key again with the same body never creates a second client, unit or ticket. It returns the original answer, still
202, with the headerIdempotent-Replayed: true. A request that first waited for a dispatcher is still answeredawaiting_dispatcher. - Reusing a key with a different body fails with
409 IDEMPOTENCY_KEY_REUSED. - A call that was answered with an error created nothing and did not use up its key: fix the request and send it again.
- Idempotency keys belong to one API key, so two of your systems cannot collide.
So when a call times out, or you get a 429 or a 5xx, send it again with the same key and the same body.
Body fields
| Field | Type | Required | Limit | Notes |
|---|---|---|---|---|
client | object | Yes | — | Who needs the service. |
client.name | string | Yes | up to 300 characters | Company or site name. Used to find the client. |
client.phone | string | New client * | up to 40 characters | International format, e.g. +40744123456. Used to find the client; a client without an AskWhiz account gets the visit details on WhatsApp at this number. |
client.email | string | New client * | up to 320 characters | Used to find the client. |
client.address | string | No | up to 500 characters | The site address. Gives the technician a route and lets AskWhiz confirm the arrival on site. |
client.contact_name | string | No | up to 300 characters | The person on site. |
equipment | array | Yes | 1 to 20 units | The units that need service. Each is matched on its own. |
equipment[].type | string | At least one † | up to 100 characters | What it is, e.g. “Combi oven”. |
equipment[].brand | string | At least one † | up to 200 characters | e.g. “Rational”. |
equipment[].model | string | At least one † | up to 200 characters | e.g. “iCombi Pro 6-1/1”. |
equipment[].serial | string | At least one † | up to 200 characters | The most reliable way to find a registered unit. |
problem | string | Yes | up to 8000 characters | What is wrong, in the caller's words. Line breaks are kept. |
priority | string | No | low, normal, high, urgent | Default normal. |
* When the client is new to your company, send a valid phone number or email (or both); otherwise the call fails with 422 CUSTOMER_CONTACT_REQUIRED.
† Each unit needs at least one of type, brand, model or serial.
Every value is a string: send phone numbers in quotes too. Spaces around a value are trimmed and repeated spaces collapsed, and a blank value counts as missing. Control characters (other than line breaks and tabs) and any field not listed here are refused with 400 VALIDATION_ERROR.
Phone numbers: international format
Send the country code with +, for example +40744123456 or +40 744 123 456 (spaces and dashes are fine). A national number such as 0744123456 is understood only when your organization has a country set in AskWhiz; otherwise it is refused with 422 CLIENT_PHONE_INVALID.
Responses
An accepted request always answers 202 Accepted with three fields:
| Field | Meaning |
|---|---|
request_id | Identifies this API request. Quote it if you contact AskWhiz support. |
status | ticket_created or awaiting_dispatcher. |
tickets | The tickets created. Empty while waiting for a dispatcher; usually one; several for a linked restaurant whose agreement limits the units per ticket. |
tickets[].id | The ticket's id. |
tickets[].number | The ticket number your team sees in AskWhiz, e.g. TKT-000042. |
tickets[].status | Always submitted: waiting for a dispatcher to accept it. |
status: ticket_created
The ticket exists and your dispatchers were notified.
{
"request_id": "0f6d3c1e-5a53-4f4c-9d0a-5e2b1c9e7a10",
"status": "ticket_created",
"tickets": [
{
"id": "7b1e2f9a-3c4d-4e5f-8a6b-9c0d1e2f3a4b",
"number": "TKT-000042",
"status": "submitted"
}
]
}Several units, several tickets
The three units of the “Several units” example, for a linked restaurant whose service agreement allows one unit per ticket:
{
"request_id": "5d2a8c4e-1b7f-4e3a-9c6d-2f8e0a1b3c5d",
"status": "ticket_created",
"tickets": [
{"id": "c0a8e5d1-2b3c-4d5e-8f6a-7b8c9d0e1f2a", "number": "TKT-000043", "status": "submitted"},
{"id": "d1b9f6e2-3c4d-4e5f-9a7b-8c9d0e1f2a3b", "number": "TKT-000044", "status": "submitted"},
{"id": "e2c0a7f3-4d5e-4f6a-8b8c-9d0e1f2a3b4c", "number": "TKT-000045", "status": "submitted"}
]
}status: awaiting_dispatcher
A dispatcher must first choose the client or a unit (see What awaiting_dispatcher means above). tickets is empty; the ticket is created when a dispatcher chooses.
{
"request_id": "9a4f1c2e-7d3b-4a5e-8f0c-1b2d3e4f5a6b",
"status": "awaiting_dispatcher",
"tickets": []
}The Idempotent-Replayed header
A repeated Idempotency-Key with the same body is answered with the original body and the header Idempotent-Replayed: true. A first answer does not carry the header.
Errors
Every error has the same JSON shape. Branch on code; message is for people and may change.
{
"error": {
"code": "CUSTOMER_CONTACT_REQUIRED",
"message": "This client is new to your company: send a valid phone number or email address.",
"details": {}
}
}VALIDATION_ERROR lists each problem in details.errors, with the field path:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": {
"errors": [
{"field": "body.equipment.0", "message": "Value error, Describe the equipment: type, brand, model or serial"},
{"field": "body.priority", "message": "Input should be 'low', 'normal', 'high' or 'urgent'"}
]
}
}
}| Status | code | Meaning | What to do |
|---|---|---|---|
400 | VALIDATION_ERROR | The body is not valid JSON, or a field is missing, blank, too long, of the wrong type, not an allowed value, or not part of the API. | Fix the fields listed in details.errors. |
400 | IDEMPOTENCY_KEY_REQUIRED | No Idempotency-Key header. | Send one, unique per request. |
400 | IDEMPOTENCY_KEY_INVALID | The Idempotency-Key is empty, longer than 255 characters, or contains spaces or non-ASCII characters. | Use only letters, digits and punctuation. |
401 | API_KEY_MISSING | No Authorization header, or it does not start with Bearer. | Send Authorization: Bearer awz_live_…. |
401 | API_KEY_INVALID | The key is incomplete, mistyped or does not exist. | Copy the full key again. If it was lost, ask an admin for a new one. |
401 | API_KEY_REVOKED | An admin revoked the key. | Ask an admin for a new key. |
401 | API_KEY_EXPIRED | The key's expiry date has passed. | Ask an admin for a new key. |
401 | API_KEY_ORG_INACTIVE | The organization that owns the key was deleted. | Contact AskWhiz. |
401 | API_KEY_OWNER_INACTIVE | The admin who created the key left the organization or is no longer an admin. | Ask a current admin to create a new key. |
403 | ORG_SUSPENDED | Your organization is suspended. | An admin restores billing in AskWhiz; the same key then works again. |
403 | LEGAL_CONSENT_REQUIRED | The admin who created the key has not accepted the current AskWhiz terms. | That admin signs in to AskWhiz and accepts them. |
403 | PHONE_VERIFICATION_REQUIRED | The admin who created the key has not verified their phone number. | That admin verifies it in AskWhiz. |
403 | SERVICE_PROVIDER_REQUIRED | Your organization is not a service company. | Only service companies can send requests. Contact AskWhiz. |
404 | NOT_FOUND | The URL is wrong, or the API is temporarily switched off. | Check the URL. If it is right, retry later with the same Idempotency-Key. |
409 | IDEMPOTENCY_KEY_REUSED | This Idempotency-Key was already used with a different body. | Use a new key for a new request. To get the original answer, resend the original body. |
422 | CLIENT_PHONE_INVALID | client.phone is not a valid phone number. A national number needs a country set on your organization. | Send it in international format, e.g. +40744123456. |
422 | CLIENT_EMAIL_INVALID | client.email is not a valid email address. | Correct it or leave it out. |
422 | CUSTOMER_CONTACT_REQUIRED | The client is new to your company and the request has no valid phone or email. | Send client.phone or client.email. |
429 | RATE_LIMITED | More than 60 requests in a minute with this key. | Wait the seconds in the Retry-After header, then retry with the same Idempotency-Key. |
500 | INTERNAL_ERROR | Something failed on the AskWhiz side. | Retry with the same Idempotency-Key and a growing delay: it never creates a duplicate. |
Retry only 429, 5xx and network errors, with the same Idempotency-Key and a growing delay. Any other 4xx fails the same way until the request or the account is fixed.
A 502, 503 or 504 without this JSON body comes from the network in front of AskWhiz: treat it like a 5xx.
Examples
A new client with one unit
The client is not in AskWhiz yet, so a phone number or an email is required. AskWhiz adds the client and the oven, and creates the ticket.
curl -sS -X POST https://api.askwhiz.io/api/v1/tickets \
-H "Authorization: Bearer $ASKWHIZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: call-2026-09-28-0042" \
-d '{
"client": {
"name": "Bistro Central",
"phone": "+40744123456",
"email": "office@bistro-central.example",
"address": "Str. Memorandumului 1, Cluj-Napoca",
"contact_name": "Ion Pop"
},
"equipment": [
{
"type": "Combi oven",
"brand": "Rational",
"model": "iCombi Pro 6-1/1",
"serial": "E11SI22051234"
}
],
"problem": "Shows a heating error and does not reach temperature.",
"priority": "high"
}'Several units
Three units of one call-out. “Restaurant Centru” is a restaurant linked to you whose agreement allows one unit per ticket, so the answer lists three tickets (see Responses).
curl -sS -X POST https://api.askwhiz.io/api/v1/tickets \
-H "Authorization: Bearer $ASKWHIZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: call-2026-09-28-0043" \
-d '{
"client": {"name": "Restaurant Centru", "phone": "+40744111222"},
"equipment": [
{"brand": "Pitco", "model": "SE14", "serial": "G1234"},
{"type": "Salamander grill"},
{"type": "Ice machine", "brand": "Scotsman"}
],
"problem": "Fryer trips the breaker; the grill and the ice machine are due for service.",
"priority": "normal"
}'A retry with the same Idempotency-Key
The first call timed out and you do not know whether it arrived. Send the same body (here saved in request.json) with the same key: you get the original answer, marked Idempotent-Replayed, and no duplicate.
# The first call timed out: send the SAME body with the SAME key.
curl -sS -i -X POST https://api.askwhiz.io/api/v1/tickets \
-H "Authorization: Bearer $ASKWHIZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: call-2026-09-28-0042" \
-d @request.json
HTTP/2 202
content-type: application/json
idempotent-replayed: true
{"request_id": "0f6d3c1e-5a53-4f4c-9d0a-5e2b1c9e7a10", "status": "ticket_created", "tickets": [{"id": "7b1e2f9a-3c4d-4e5f-8a6b-9c0d1e2f3a4b", "number": "TKT-000042", "status": "submitted"}]}Python (requests)
Retries 429, 5xx and network errors with a growing delay that honours Retry-After, always with the same Idempotency-Key.
import os
import time
import requests
API_URL = "https://api.askwhiz.io/api/v1/tickets"
API_KEY = os.environ["ASKWHIZ_API_KEY"]
def send_ticket(payload: dict, idempotency_key: str, attempts: int = 5) -> dict:
"""Send one request. Retries 429, 5xx and network errors with the SAME key."""
headers = {"Authorization": f"Bearer {API_KEY}", "Idempotency-Key": idempotency_key}
for attempt in range(attempts):
try:
r = requests.post(API_URL, json=payload, headers=headers, timeout=30)
except requests.RequestException:
r = None # network error: the call may or may not have arrived
if r is not None and r.status_code == 202:
return r.json() # {"request_id", "status", "tickets"}
if r is not None and r.status_code != 429 and r.status_code < 500:
error = r.json()["error"] # fix the request; retrying it unchanged fails again
raise RuntimeError(f"{r.status_code} {error['code']}: {error['message']}")
if attempt == attempts - 1:
break
wait = 2 ** attempt # 1, 2, 4, 8 s
if r is not None and r.headers.get("Retry-After", "").isdigit():
wait = max(wait, int(r.headers["Retry-After"]))
time.sleep(wait)
raise RuntimeError("AskWhiz is unavailable: retry later with the same Idempotency-Key")
result = send_ticket(
{
"client": {"name": "Bistro Central", "phone": "+40744123456"},
"equipment": [{"type": "Combi oven", "brand": "Rational"}],
"problem": "Does not reach temperature.",
},
idempotency_key="call-2026-09-28-0042", # e.g. your call or case number
)
print(result["status"], [t["number"] for t in result["tickets"]])JavaScript (fetch)
The same logic for Node.js 18 or newer, on your server.
// Node 18+ (built-in fetch). Server side only: never ship the key to a browser.
const API_URL = "https://api.askwhiz.io/api/v1/tickets";
export async function sendTicket(payload, idempotencyKey, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
let res = null;
try {
res = await fetch(API_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ASKWHIZ_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
} catch {
// network error: the call may or may not have arrived; retry with the same key
}
if (res?.status === 202) return res.json(); // { request_id, status, tickets }
if (res && res.status !== 429 && res.status < 500) {
const { error } = await res.json(); // fix the request; retrying it unchanged fails again
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
if (attempt === attempts - 1) break;
const retryAfter = Number(res?.headers.get("Retry-After")) || 0;
const wait = Math.max(retryAfter, 2 ** attempt); // 1, 2, 4, 8 s
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
throw new Error("AskWhiz is unavailable: retry later with the same Idempotency-Key");
}
const result = await sendTicket(
{
client: { name: "Bistro Central", phone: "+40744123456" },
equipment: [{ type: "Combi oven", brand: "Rational" }],
problem: "Does not reach temperature.",
},
"call-2026-09-28-0042", // e.g. your call or case number
);
console.log(result.status, result.tickets.map((t) => t.number));Limits
- 60 requests per minute per API key. Above that the answer is
429 RATE_LIMITEDwith aRetry-Afterheader in seconds. Retries count too. - 1 to 20 units per request.
problem: up to 8000 characters.- Other text fields: see the limits in the field table.
Idempotency-Key: 1 to 255 visible ASCII characters.- Up to 20 active API keys per organization.
HTTP/2 429
retry-after: 17
{"error": {"code": "RATE_LIMITED", "message": "Too many requests for this API key. Retry after the indicated delay.", "details": {"retryAfterSeconds": 17}}}What happens next
- 1Your dispatchers are notified of the new request, like for any request from a restaurant.
- 2A dispatcher accepts it, on WhatsApp or on the web, choosing the technician and the visit time.
- 3The client is told. A client without an AskWhiz account gets a WhatsApp message with the technician, the visit time and the address when you sent its phone number, and again if the time or the technician changes. A linked restaurant follows the job in its own AskWhiz account.
- 4The job runs in AskWhiz like any other: the visit, the work report and the sign-off.
The API is create-only for now: it does not send status changes back to your system. Your team follows the job in AskWhiz.
Questions? Write to contact@askwhiz.io and include the request_id.