Quickstart — Create, Configure, Go Live
The fastest way to understand the platform is to build an assistant end-to-end. The script below creates an assistant for a fictional heating company, teaches it from the company website, and takes it live — the same five-step journey from the welcome page, in about a minute of API calls.
That's a live assistant with a web address. From here you'd give it a phone number (Go Live), connect a calendar (Bookings), and watch the conversations arrive (See What's Happening).
The one concept to internalise before you write real integration code: editing an app changes a draft; taking it live makes those changes real to your callers. Change the prompt, the voice, the branding — nothing your callers experience changes until you take the assistant live again. The rest of this chapter covers the app endpoints in detail.
TOKEN="your_api_token_here"
# 1. Create the app
APP=$(curl -s -X POST https://api.everycallhandled.com/api/v1/apps \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "HVAC Receptionist",
"attributes": {
"voiceChatPrompt": "You are the AI receptionist for ABC HVAC. Office hours: Mon-Fri 8am-5pm. Always confirm callback number.",
"voice": "shimmer",
"voiceChatEnabled": "true",
"primaryColor": "#1E3A5F"
}
}')
APP_ID=$(echo $APP | jq -r .id)
echo "Created app: $APP_ID"
# 2. Teach it from the company website
JOB=$(curl -s -X POST https://api.everycallhandled.com/api/v1/apps/$APP_ID/knowledge \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "url", "content": "https://abchvac.com/services" }')
JOB_ID=$(echo $JOB | jq -r .jobId)
# Poll until ingestion completes
while true; do
STATUS=$(curl -s https://api.everycallhandled.com/api/v1/apps/$APP_ID/knowledge/jobs/$JOB_ID \
-H "Authorization: Bearer $TOKEN" | jq -r .status)
echo "Knowledge ingest: $STATUS"
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && exit 1
sleep 3
done
# 3. Take it live
curl -X POST https://api.everycallhandled.com/api/v1/apps/$APP_ID/publish \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "subdomain": "abc-hvac", "userEmail": "owner@abchvac.com" }'
# 4. Verify the live app
curl https://api.everycallhandled.com/api/v1/apps/$APP_ID \
-H "Authorization: Bearer $TOKEN" | jq '.publishedAppUrl, .attributes.creditBalance'An app is one assistant: its instructions, voice, knowledge, branding and (once live) its phone number and web address. Most businesses start with one; you can run many.
Requires create_app.
Request:
All attributes are optional at creation — you can set everything later with an update. The full attribute reference is in Make It Yours.
Response (200):
Save the id — you'll need it for all subsequent
operations. (Published state and createdByUserId are
returned by GET /apps/{id}, not by create.)
Errors:
409 PHONE_NUMBER_IN_USEifattributes.twilioPhoneNumberis already used by another of your apps.
curl -X POST https://api.everycallhandled.com/api/v1/apps \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Customer Support Bot",
"description": "AI assistant for customer support",
"attributes": {
"assistantName": "Support Assistant",
"welcomeMessage": "Hello! How can I help you today?",
"primaryColor": "#4F46E5",
"textColor": "#FFFFFF",
"voice": "nova",
"voiceChatPrompt": "You are a helpful customer support assistant."
}
}'{
"id": "550e8400-e29b-41d4-a716-446655440000",
"organisationId": "org-abc123",
"name": "Customer Support Bot",
"description": "AI assistant for customer support",
"attributes": { /* echoed back, normalised */ },
"chatId": "...",
"createdAt": "2026-05-10T10:30:00Z",
"updatedAt": "2026-05-10T10:30:00Z"
}Requires view_settings.
Query params:
published—true,false, orall. If omitted, only published apps are returned (passallto include drafts).limit(default 50, max 200),offset(pagination)
Response (200):
The response is filtered to your account: you see your own apps and those of any customer accounts beneath yours (see For Partners). Credit balances shown for live assistants are always current.
{
"apps": [ /* array of app objects (same shape as GET /apps/{id}) */ ],
"isAdmin": false,
"pagination": { "total": 17, "limit": 50, "offset": 0, "hasMore": false, "nextOffset": null }
}Available to any signed-in member of the account that owns the app.
Response (200):
hasPendingPublish is true when the app has
draft changes that aren't live yet; pendingPublishAttrIds
lists which attributes are waiting. Use these to drive an "Apply your
changes" prompt in your own UI.
Errors:
404 APP_NOT_FOUND— the app doesn't exist OR your token can't access it (deliberately the same error to avoid existence leaks).
curl 'https://api.everycallhandled.com/api/v1/apps/{appId}' \
-H 'Authorization: Bearer YOUR_TOKEN'{
"id": "550e8400-...",
"name": "Customer Support Bot",
"description": "...",
"attributes": { /* full attribute set; balances and usage totals are always current live values */ },
"createdByUserId": "...",
"isPublished": true,
"publishedAppUrl": "https://support.app.everycallhandled.com",
"subdomain": "support",
"hasPendingPublish": false,
"pendingPublishAttrIds": [],
"createdAt": "...",
"updatedAt": "..."
}Requires edit_settings.
PATCH semantics — only fields you supply are changed. Updates are draft changes: take the assistant live again to apply them (see Go Live).
curl -X PATCH https://api.everycallhandled.com/api/v1/apps/$APP_ID \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Renamed app",
"attributes": {
"voiceChatPrompt": "Updated prompt...",
"voice": "ballad"
}
}'Requires edit_settings. Permanently removes the app
together with its knowledge, conversations and billing history. If the
app was live, its site is taken down too. This cannot be undone.
curl -X DELETE 'https://api.everycallhandled.com/api/v1/apps/{appId}' \
-H 'Authorization: Bearer YOUR_TOKEN'Requires clone_app.
The duplicate inherits the prompt, tools, voice configuration and billing mode. It does not inherit the phone number, live state or credit balance. Duplicating an app is the safe way to experiment with big changes without touching the assistant your callers are using.
curl -X POST https://api.everycallhandled.com/api/v1/apps/$APP_ID/clone \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{ "name": "Copy of My App" }'Operators only (create_app). Builds a
demonstration receptionist for a business from its
public website alone: the homepage and up to four service, price,
contact and about pages are read, a brief is drawn from what the site
states (services and prices only where published, opening hours, the
questions a prospective customer would ask), and the same build path as
a Builder intake runs from it: a published app, a phone number, a
booking page in the business's own logo and colours, web chat, and the
website queued into the app's knowledge. The result is stamped as a
demonstration (see Demonstration mode
below).
| Field | Required | Notes |
|---|---|---|
websiteUrl |
yes | http(s) URL of the business's site. |
sector |
no | Short trade label; overrides what the site suggests. |
businessName |
no | Overrides the name read from the site. |
ownerEmail |
no | The operator who owns the demonstration. Defaults to the caller's email. Nothing personal is ever taken from the website. |
creditSeconds |
no | The demonstration's allowance of call time, allocated from the operator's pool as a per-app cap. 300 to 7200, default 1800 (30 minutes). When it is spent the receptionist says so and ends the call. |
demoDays |
no | How long the demonstration lives before it is ended. 1 to 60, default 14. |
Response (201): appId,
businessName, phoneNumber,
publishedUrl, bookingPageUrl,
landingUrl, pagesRead, the brief's
services, hours and
callerQuestions, and the seeded bookingTypes.
A build takes 30 to 60 seconds, longer than the gateway allows, so call
it from a job rather than a browser;
backend/scripts/build_demos_from_list.js invokes the
function directly for a list of sites.
curl -X POST https://api.everycallhandled.com/api/v1/apps/build-from-website \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"websiteUrl": "https://www.example-clinic.co.uk",
"sector": "dental practice",
"businessName": "Example Dental",
"ownerEmail": "you@example.com"
}'Every built demonstration also gets a landing page
(landingUrl in the build response, a
/d/{token} link) that shows the number, the booking page
and the web chat with the demonstration notice and the platform's
healthcare proof. Resolving the token
(GET /api/v1/apps/prospect-demo/{token}) returns
mode: "built", a built block
(appId, business, websiteUrl,
phoneNumber, bookingPageUrl,
sector) and the demo notice strings.
Pages report a visit with a public beacon, nothing about the visitor is kept:
Operators read the engagement of every demonstration in one call:
Returns { demos: [...], count }, one row per
demonstration: business, sector,
phoneNumber, bookingPageUrl,
landingUrl, createdAt, visits,
returnVisits (visits more than 30 minutes apart),
firstVisitAt, lastVisitAt, calls,
callSeconds, longestCallSeconds,
chats, bookingAttempts,
lastActivityAt, stage
(no engagement, visited, chatted,
called, booking attempted),
replied, repliedAt, notes. Calls
and chats are the demonstration's own Conversations; booking attempts
are its booking rows.
PATCH /api/v1/demos/{appId} with
{ "replied": true } and/or { "notes": "..." }
records the operator's own marks on a row;
{ "extendDays": 7 } pushes a live demonstration's end date;
{ "end": true } ends it now.
POST /api/v1/public/demo-visit
{ "appId": "...", "path": "landing" }A CSV for instantly.ai, one row per live demonstration. Standard
columns: email, first_name,
last_name, company_name, phone,
website. Custom variables for the sequence:
demo_number, landing_url,
booking_url, sector, cohort
(dental, aesthetics, veterinary, consultant, general),
tailored_question (the question the email invites them to
ask, by cohort, or the row's own), company_type,
pecr_ok, pecr_reason,
opt_out_line, sender_identity,
app_id, demo_ends_at, stage.
PECR: a limited company is a corporate subscriber and may be mailed
with the sender identified and an opt-out offered; a sole trader or
ordinary partnership is an individual subscriber and needs recorded
consent; an unknown company type is not mailed until it is known. The
contact, company type, company number, consent and tailored question are
set at build (contact, companyType,
companyNumber, consent,
tailoredQuestion on
POST /api/v1/apps/build-from-website) or afterwards with
PATCH /api/v1/demos/{appId}. The sender identity and
opt-out line come from the deployment's
DEMO_SENDER_IDENTITY and DEMO_OPT_OUT_LINE;
until they are set, visible placeholders ride along.
GET /api/v1/demos/export.csv rows that may be mailed
GET /api/v1/demos/export.csv?all=1 every live demonstration, with the PECR column saying why a row may not be mailedEvery funnel row carries phase (live,
ended, quarantine, due-delete),
endsAt, endedAt, deleteAfter,
daysLeft, creditSeconds and
creditBalance. A daily sweep ends demonstrations whose time
is up: the number is parked on a short "this demonstration has ended"
message, the receptionist is paused (a caller who reaches it hears the
same), and the booking page shows the ended notice
(config.demo.ended, config.demo.endedText) and
takes no bookings. Nothing is deleted by the sweep. Two weeks after
ending, the operator is told which demonstrations can be deleted;
deleting an app (DELETE /api/v1/apps/{appId}) releases its
number. A demonstration that has spent its allowance answers with "this
demonstration has used up its allowance of call time" until an operator
tops up its cap (POST /api/v1/apps/{appId}/cap-topup).
A built demonstration carries booking.demo in its
booking settings:
While it is set: the receptionist opens every call and chat by saying
it is a demonstration built from public information and not connected to
the business, gives no clinical or treatment advice and no suitability
judgement, quotes no price the website does not state, and describes
bookings as demonstration bookings that reach nobody; no confirmation,
reminder or follow-up messages are sent to anyone; the public booking
page shows a banner (config.demo carries
title, text and confirmed) and
confirms bookings as demonstrations with no payment or manage link. The
flag is set at build time and survives dashboard saves; remove it by
clearing booking.demo through
PUT /api/v1/apps/{appId}/booking.
"demo": { "enabled": true, "business": "Example Dental", "createdAt": "2026-09-10T09:01:22Z" }