One key.
Two clean APIs.
Automate the platform your agency runs, or give a client direct model access. Every key has explicit scopes and optional spend caps from day one.
from openai import OpenAI
client = OpenAI(
base_url="https://models.bytepilot.ai/v1",
api_key="bp_live_..."
)
response = client.chat.completions.create(
model="bytepilot",
messages=[{
"role": "user",
"content": "Hello from my app"
}]
)
print(response.choices[0].message)
Everything needed to ship.
Both APIs authenticate with keys created in the BytePilot dashboard. Give each key only the scopes it needs, then add a spend cap if the integration should never cross a fixed boundary.
Platform API
Your agents, clients, calls, transcripts, credit balance and usage reports — the data behind the dashboard, in JSON. This is what you automate against: pull last month's usage per client and raise your own invoices, sync clients from your CRM, pause an agent from your own admin panel.
https://bytepilot.ai/api/v1Every endpoint below is this prefix plus its own path.
curl https://bytepilot.ai/api/v1/usage \
-H 'X-API-Key: bp_live_your_key_here'
Endpoints
28 public methods
GET
https://bytepilot.ai/api/v1/agents
List your agents
⌄
Every agent on your account with its status, template, assigned phone number, and client grouping (external_ref round-trips your own CRM/accounting ids).
agents{
"agents": [
{
"agent_id": 12,
"name": "Sophie",
"template": "generic_reception",
"status": "ACTIVE",
"phone_number": "+441256222333",
"client": {
"client_id": 3,
"name": "Harrison & Co",
"external_ref": "CRM-1042"
},
"created_at": "2026-07-28 09:00:00"
}
]
}
POST
https://bytepilot.ai/api/v1/agents/:id/status
Pause or resume an agent
⌄
Paused agents answer with a polite unavailable message. The change applies from the next call.
agentsParameters
idpath · requiredAgent id
{
"status": "PAUSED"
}
{
"agent_id": 12,
"status": "PAUSED"
}
GET
https://bytepilot.ai/api/v1/clients
List your clients
⌄
The client groupings your agents roll up to for billing.
agents{
"clients": [
{
"client_id": 3,
"name": "Harrison & Co",
"external_ref": "CRM-1042",
"created_at": "2026-07-01 10:00:00"
}
]
}
POST
https://bytepilot.ai/api/v1/clients
Create a client (idempotent by name)
⌄
Creates a client grouping, or returns the existing one with that name (updating external_ref if supplied) — safe to call from sync jobs.
agents{
"name": "Harrison & Co",
"external_ref": "CRM-1042"
}
{
"client_id": 3,
"name": "Harrison & Co",
"external_ref": "CRM-1042",
"created": true
}
GET
https://bytepilot.ai/api/v1/visibility/targets
List tracked websites
⌄
Every website you track for AI visibility, with its latest score and the client it belongs to. AI visibility measures how often leading AI assistants (ChatGPT, Claude, Gemini, Perplexity) recommend a business when people ask the buying questions its customers actually ask.
visibility{
"targets": [
{
"target_id": 5,
"url": "https://millerplumbing.co.uk",
"domain": "millerplumbing.co.uk",
"display_name": "Miller Plumbing",
"category": "emergency plumber",
"location": "Leeds",
"client": {
"client_id": 3,
"name": "Miller Plumbing",
"external_ref": "CRM-1042"
},
"latest_report_id": 41,
"latest_score": 43,
"created_at": "2026-08-01 09:00:00"
}
]
}
POST
https://bytepilot.ai/api/v1/visibility/targets
Track a website (idempotent by domain)
⌄
Reads the website once to derive the business name, category, location and the set of buyer-style questions every report will measure against — so scores stay comparable over time. Returns the existing target if the domain is already tracked. Creating a target is free; running reports costs credit.
visibility{
"url": "https://millerplumbing.co.uk",
"client_name": "Miller Plumbing"
}
{
"target_id": 5,
"domain": "millerplumbing.co.uk",
"display_name": "Miller Plumbing",
"category": "emergency plumber",
"location": "Leeds",
"created": true
}
POST
https://bytepilot.ai/api/v1/visibility/targets/:id/run
Run a visibility check
⌄
Queues a full report: every measured assistant is asked every buyer question, the website gets technical AI-readiness checks, and recommendations are drafted. Reports take a few minutes and you are only charged (flat rate per report, shown as price_pence) when one completes — a failed run is free. Optional send_to / copy_to email the finished report automatically under your branding with the PDF attached, exactly like the dashboard send.
visibilityParameters
idpath · requiredTarget id
{
"send_to": "owner@millerplumbing.co.uk",
"copy_to": "you@youragency.co.uk"
}
{
"report_id": 42,
"status": "QUEUED",
"price_pence": 200,
"note": "Reports take a few minutes. Poll GET /api/v1/visibility/reports/{report_id}. You are only charged when it completes."
}
GET
https://bytepilot.ai/api/v1/visibility/targets/:id/reports
List a website's reports
⌄
Report history for one tracked website, newest first (up to 50) — the score trend an agency charts for its client.
visibilityParameters
idpath · requiredTarget id
{
"reports": [
{
"report_id": 41,
"status": "COMPLETE",
"score": 43,
"charged_pence": 200,
"scheduled": false,
"created_at": "2026-08-18 08:00:00",
"completed_at": "2026-08-18 08:07:00"
}
]
}
GET
https://bytepilot.ai/api/v1/visibility/reports/:id
Get a report
⌄
The full report: overall 0–100 score, per-assistant breakdown, who the assistants recommend instead, the website's AI-readiness checks and the drafted recommendations. Completed reports also include agent_prompt — a ready-to-paste brief for an AI coding agent working on the measured website, containing the baseline, the buyer questions being lost and a prioritised task list; hand it to your own tooling or to the client's developer. Pass include_answers=1 for the question-by-question evidence. Poll this after queuing a run — status moves QUEUED → RUNNING → COMPLETE (or FAILED, uncharged).
visibilityParameters
idpath · requiredReport id
include_answersquerySet 1 to include every assistant answer excerpt
{
"report_id": 41,
"target_id": 5,
"status": "COMPLETE",
"score": 43,
"assistants": [
{
"assistant": "ChatGPT",
"available": true,
"questions_answered": 10,
"times_recommended": 4,
"score": 40
}
],
"competitors": [
{
"name": "Aqua Flow Plumbers",
"mentions": 5
}
],
"site_checks": {
"schema_org": false,
"llms_txt": false,
"blocked_crawlers": [
"GPTBot"
]
},
"recommendations": {
"summary": "Strong on emergency questions, invisible on boiler servicing.",
"gaps": [
"Blocked GPTBot means ChatGPT cannot read the site"
],
"actions": [
"Unblock GPTBot in robots.txt"
]
},
"agent_prompt": "You are working on the website for Miller Plumbing (https://millerplumbing.co.uk)\u2026\nTASKS, in priority order:\n1. robots.txt currently blocks GPTBot\u2026",
"charged_pence": 200,
"scheduled": false,
"created_at": "2026-08-18 08:00:00",
"completed_at": "2026-08-18 08:07:00"
}
GET
https://bytepilot.ai/api/v1/visibility/reports/:id/pdf
Download a report as PDF
⌄
The client-ready PDF, carrying YOUR branding (name, logo, colour from your dashboard branding page) and nothing of ours — the same document the dashboard sends to clients. Binary application/pdf response.
visibilityParameters
idpath · requiredReport id (must be COMPLETE)
{
"note": "Binary PDF body, Content-Type: application/pdf"
}
GET
https://bytepilot.ai/api/v1/leads/campaigns
List lead campaigns
⌄
Your lead-intelligence campaigns, newest first. A campaign finds businesses matching an industry and location, researches each one's website and web presence, scores how strong a prospect it is for the services you sell, and drafts the outreach — you are billed a flat rate per prospect scored, never for discovery or failed research.
leads{
"campaigns": [
{
"campaign_id": 7,
"label": "Dentists in Manchester",
"industry": "dentists",
"location": "Manchester",
"services": [
"Websites",
"AI receptionist"
],
"status": "COMPLETE",
"target": 25,
"discovered": 25,
"scored": 23,
"failed": 2,
"created_at": "2026-08-27 09:00:00",
"completed_at": "2026-08-27 09:41:00"
}
]
}
POST
https://bytepilot.ai/api/v1/leads/campaigns
Start a lead campaign
⌄
Queues discovery and research. Your balance must cover the campaign's maximum cost (count × per-prospect rate) up front, but charges land per prospect actually scored. Optional send_to / copy_to email the finished white-label prospect report (your branding, PDF attached) when the campaign completes. exclude_previous (default true) skips businesses already found for the SAME client (campaigns with no client form their own scope) — so a monthly re-run stays fresh, while the same lead can still surface for a different client it also suits. Optional seller_context (a sentence or two on what the seller is and offers) tailors the scoring, pitches and drafts to what is genuinely on offer — the dashboard derives it automatically from the seller's website. Better still, pass profile_id (a seller profile created in the dashboard): the campaign then also carries the seller's confirmed proof points, ideal customer, sign-off name and booking link, and drafts come out ready to send. Profiles are read fresh at scoring time, so profile edits apply to every later run.
leads{
"industry": "dentists",
"location": "Manchester",
"count": 25,
"services": [
"Websites",
"AI receptionist"
],
"client_name": "Smile Group",
"seller_context": "Smile Group: a dental marketing agency offering websites and AI reception for practices.",
"send_to": "owner@smilegroup.co.uk",
"copy_to": "you@youragency.co.uk"
}
{
"campaign_id": 7,
"status": "QUEUED",
"price_per_prospect_pence": 25,
"max_cost_pence": 625,
"note": "Campaigns take several minutes. Poll GET /api/v1/leads/campaigns/{campaign_id}. You are only charged per prospect scored."
}
GET
https://bytepilot.ai/api/v1/leads/campaigns/:id
Get a campaign
⌄
Progress and totals for one campaign. Poll this after starting one — status moves QUEUED → DISCOVERING → RESEARCHING → COMPLETE (or FAILED, uncharged).
leadsParameters
idpath · requiredCampaign id
{
"campaign_id": 7,
"label": "Dentists in Manchester",
"industry": "dentists",
"location": "Manchester",
"services": [
"Websites",
"AI receptionist"
],
"status": "RESEARCHING",
"target": 25,
"discovered": 25,
"scored": 11,
"failed": 1,
"charged_pence": 275,
"created_at": "2026-08-27 09:00:00",
"completed_at": null
}
GET
https://bytepilot.ai/api/v1/leads/campaigns/:id/prospects
List a campaign's scored prospects
⌄
Scored prospects, best first, with per-service fit scores and the recommended pitch. Filter with min_score to feed only strong prospects into your own tooling.
leadsParameters
idpath · requiredCampaign id
min_scorequeryOnly prospects scoring at least this (0-100)
{
"prospects": [
{
"prospect_id": 91,
"name": "Smile Dental",
"website": "https://smiledental.example",
"domain": "smiledental.example",
"phone": "0161 000 0000",
"lead_score": 91,
"fit_scores": {
"Websites": 41,
"AI receptionist": 96
},
"pitch": "Lead with the AI receptionist \u2014 no out-of-hours cover and no online booking."
}
]
}
GET
https://bytepilot.ai/api/v1/leads/prospects/:id
Get a prospect in full
⌄
Everything the research produced: the observed website signals (deterministic), best-effort external notes, per-service fit scores, the personalised angle, and the outreach drafts (cold email and call script) ready to send from YOUR OWN tools — the platform never sends outreach itself.
leadsParameters
idpath · requiredProspect id
{
"prospect_id": 91,
"campaign_id": 7,
"name": "Smile Dental",
"website": "https://smiledental.example",
"domain": "smiledental.example",
"contact_email": "hello@smiledental.example",
"phone": "0161 000 0000",
"status": "SCORED",
"lead_score": 91,
"fit_scores": {
"Websites": 41,
"AI receptionist": 96
},
"signals": [
{
"signal": "Online booking",
"level": "warn",
"detail": "No visible way to book online \u2014 customers must call during opening hours."
}
],
"external_notes": {
"reviews": "around 23 Google reviews",
"competitors": [
"Brighter Smiles"
]
},
"pitch": "Lead with the AI receptionist.",
"angle": "Their site asks patients to phone for appointments, with no way to book or ask questions outside opening hours.",
"email_subject": "Out-of-hours enquiries at Smile Dental",
"email_draft": "Hi, I was looking at smiledental.example and noticed\u2026",
"call_script": "OPENER: \u2026",
"charged_pence": 25
}
GET
https://bytepilot.ai/api/v1/leads/campaigns/:id/pdf
Download a campaign report as PDF
⌄
The client-ready prospect report, carrying YOUR branding and nothing of ours — the same document the dashboard emails to clients. Binary application/pdf response.
leadsParameters
idpath · requiredCampaign id (must be COMPLETE)
{
"note": "Binary PDF body, Content-Type: application/pdf"
}
GET
https://bytepilot.ai/api/v1/calls
List calls in a date range
⌄
Filterable by agent_id and client_id; defaults to the current month. Latest 200.
callsParameters
fromqueryYYYY-MM-DD (default: first of this month)
toqueryYYYY-MM-DD (default: today)
agent_idqueryFilter to one agent
client_idqueryFilter to one client
{
"from": "2026-07-01",
"to": "2026-07-28",
"calls": [
{
"call_id": 88,
"agent_id": 12,
"channel_id": 15,
"client_id": 3,
"caller_number": "+447712345678",
"started_at": "2026-07-28 14:03:11",
"duration_seconds": 151,
"status": "COMPLETED",
"outcome": "message_taken",
"summary": "Quote request",
"charged_pence": 26
}
]
}
GET
https://bytepilot.ai/api/v1/calls/:id
One call with transcript and message
⌄
The full record: timings, outcome, the structured message taken (if any) and the conversation transcript (subject to your transcript retention window).
callsParameters
idpath · requiredCall id
{
"call_id": 88,
"agent_id": 12,
"channel_id": 15,
"client_id": 3,
"caller_number": "+447712345678",
"to_number": "+441256222333",
"started_at": "2026-07-28 14:03:11",
"ended_at": "2026-07-28 14:05:42",
"duration_seconds": 151,
"status": "COMPLETED",
"outcome": "message_taken",
"summary": "Quote request",
"message": {
"caller_name": "John Peters",
"reason": "Quote request",
"details": "Ltd company, ~40 invoices/month"
},
"transcript": [
"[0:01] Agent: Hi, this is Sophie\u2026"
],
"charged_pence": 26,
"rate_ppu_used": 10
}
GET
https://bytepilot.ai/api/v1/balance
Current credit balance
⌄
Balance in pence, billing mode, and account status.
billing{
"balance_pence": 892,
"billing_mode": "CREDITS",
"account_status": "ACTIVE"
}
GET
https://bytepilot.ai/api/v1/ledger
Credit ledger entries in a date range
⌄
Every balance movement: grants, top-ups, usage debits (with the API key that incurred them, where applicable), adjustments. Latest 500 in range.
billingParameters
fromqueryYYYY-MM-DD (default: first of this month)
toqueryYYYY-MM-DD (default: today)
{
"from": "2026-07-01",
"to": "2026-07-28",
"entries": [
{
"entry_id": 14,
"type": "USAGE",
"amount_pence": -13,
"service": "voice",
"reference": "CA3f84\u2026",
"balance_after_pence": 892,
"api_key_id": null,
"created_at": "2026-07-28 10:46:20"
}
]
}
GET
https://bytepilot.ai/api/v1/usage
Usage report: units + cost per client, agent and channel
⌄
The endpoint your billing automation calls monthly to invoice your clients: usage rolled up channel → agent → client with your external_ref on every row, filtered by date range and optionally client_id / agent_id / channel_id.
billingParameters
fromqueryYYYY-MM-DD (default: first of this month)
toqueryYYYY-MM-DD (default: today)
client_idqueryFilter to one client
agent_idqueryFilter to one agent
channel_idqueryFilter to one channel
{
"from": "2026-07-01",
"to": "2026-07-28",
"usage": [
{
"service": "voice",
"client_id": 3,
"client_name": "Harrison & Co",
"external_ref": "CRM-1042",
"agent_id": 12,
"agent_name": "Sophie",
"channel_id": 15,
"calls": 42,
"seconds": 6510,
"charged_pence": 1110
}
]
}
POST
https://bytepilot.ai/api/v1/outbound/calls
Ring someone now
⌄
Places a call immediately — the endpoint your CRM hits when a customer asks to be called back. The permission that makes the call lawful is recorded from the `consent` object in the same request, so there is no way to schedule a call without saying why you may make it. Every call, from here or the scheduler, passes the same gate at the moment of dialling: do-not-call list, permission on file, calling hours, and the per-number and per-agent limits. A refused call answers **409 with the reason in plain words** — the request was fine, we simply must not make that call — and is still recorded so it shows in your history.
outbound{
"agent_id": 12,
"to_number": "+447712345678",
"reason": "Quote request from the website",
"consent": {
"basis": "requested",
"source": "Website callback form, 29 July",
"tps_attested": false
}
}
{
"outbound_id": 84,
"status": "placed",
"reason": ""
}
GET
https://bytepilot.ai/api/v1/outbound/calls
Outbound call history, including refused ones
⌄
Every attempt in the range with what became of it. Refused attempts carry `block_reason`, which is the answer to "why didn't it ring them" — the commonest question this feature produces.
outboundParameters
fromqueryYYYY-MM-DD (default: first of this month)
toqueryYYYY-MM-DD (default: today)
{
"from": "2026-07-01",
"to": "2026-07-29",
"calls": [
{
"outbound_id": 84,
"agent_id": 12,
"agent_name": "Ellie",
"client_id": 3,
"schedule_id": null,
"to_number": "+447712345678",
"purpose": "callback",
"status": "COMPLETED",
"block_reason": null,
"call_sid": "CA9f2\u2026",
"created_at": "2026-07-29 10:14:02"
}
]
}
POST
https://bytepilot.ai/api/v1/outbound/schedules
Schedule recurring calls
⌄
Sets up a repeating call — a morning check-in, a weekly follow-up. `time_of_day` is in the RECIPIENT's timezone, so nine in the morning stays nine in the morning when the clocks change. Cadence is once, daily, weekdays or weekly (`day_of_week` 1–7, Monday first). As with an immediate call, the permission is recorded from `consent` in the same request, and re-checked every time the schedule fires rather than trusted from when it was created.
outbound{
"agent_id": 12,
"to_number": "+447712345678",
"label": "Morning check-in with Doris",
"cadence": "daily",
"time_of_day": "09:00",
"timezone": "Europe/London",
"consent": {
"basis": "requested",
"source": "Arranged with her daughter, 12 July"
}
}
{
"schedule_id": 7,
"next_run_at": "2026-07-30 08:00:00",
"status": "ACTIVE"
}
GET
https://bytepilot.ai/api/v1/outbound/schedules
List your schedules
⌄
Every schedule on the account with its next call time in UTC.
outbound{
"schedules": [
{
"schedule_id": 7,
"agent_id": 12,
"label": "Morning check-in with Doris",
"to_number": "+447712345678",
"cadence": "daily",
"time_of_day": "09:00",
"day_of_week": null,
"timezone": "Europe/London",
"next_run_at": "2026-07-30 08:00:00",
"status": "ACTIVE"
}
]
}
POST
https://bytepilot.ai/api/v1/outbound/schedules/:id/cancel
Stop a schedule
⌄
No further calls are placed. Attempts already made stay in your history.
outboundParameters
idpath · requiredSchedule id
{
"schedule_id": 7,
"status": "CANCELLED"
}
POST
https://bytepilot.ai/api/v1/outbound/do-not-call
Add a number to the do-not-call list
⌄
Nothing overrides this: it is checked at the moment of dialling, and recording fresh permission afterwards does not undo it. `scope` is `account` (every client you run — the default) or `client` with a `client_id` (that one business only). Your agents add entries here themselves whenever somebody asks not to be called again, so you rarely need this.
outbound{
"phone_number": "+447712345678",
"scope": "account",
"note": "Asked by email"
}
{
"phone_number": "+447712345678",
"scope": "account",
"status": "suppressed"
}
GET
https://bytepilot.ai/api/v1/outbound/do-not-call
List do-not-call entries
⌄
Who your agents will never ring, and why each one is on the list.
outbound{
"entries": [
{
"phone_number": "+447712345678",
"scope": "client",
"client_id": 3,
"reason": "asked_on_call",
"note": "Asked during a call",
"created_at": "2026-07-29 10:22:41"
}
]
}
Model API
A drop-in replacement for the OpenAI API. Change two lines in any existing OpenAI-compatible app — the base URL and the key — and it runs on your BytePilot balance instead. Same request and response shapes, streaming included; we forward your request untouched and hand back exactly what came out.
It exists so the technical end of your book has somewhere to go. A client who wants to build their own thing gets a key from you, spends against your balance, and appears as a line on your usage report — so you can bill them for it at whatever margin you set. Give each client their own key, cap it, and read its spend separately.
Billing is a multiple of what the request actually costs us, not a per-token rate — model prices move monthly and a fixed rate would be wrong within weeks. Fractions of a penny accumulate rather than rounding up, so a thousand tiny requests cost what a thousand tiny requests should.
https://models.bytepilot.ai/v1Include the /v1 — this is the exact string that goes in an OpenAI client's base_url, and the client appends /chat/completions itself.
from openai import OpenAI
client = OpenAI(
base_url="https://models.bytepilot.ai/v1",
api_key="bp_live_your_key_here",
)
reply = client.chat.completions.create(
model="google/gemini-3.1-flash-lite",
messages=[{"role": "user", "content": "Summarise this enquiry in one line."}],
)
print(reply.choices[0].message.content)
curl https://models.bytepilot.ai/v1/chat/completions \
-H 'Authorization: Bearer bp_live_your_key_here' \
-H 'Content-Type: application/json' \
-d '{
"model": "google/gemini-3.1-flash-lite",
"messages": [{"role": "user", "content": "Hello"}]
}'
Endpoints
2 public methods
POST
https://models.bytepilot.ai/v1/chat/completions
Create a chat completion
⌄
The OpenAI `/v1/chat/completions` contract, unchanged: same request body, same response body, same SSE frames when you set `stream: true`. We do not rewrite your prompt, substitute your model or inject anything — an API that surprises you is worse than one that does less. Every request is attributed to the key that made it, so per-client cost reporting comes free, and a key over its spend cap gets a 402 rather than a surprise bill.
models{
"model": "google/gemini-3.1-flash-lite",
"messages": [
{
"role": "user",
"content": "Summarise this enquiry in one line."
}
],
"stream": false
}
{
"id": "chatcmpl-\u2026",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Customer wants a quote for year-end accounts."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 24,
"completion_tokens": 11,
"total_tokens": 35
}
}
GET
https://models.bytepilot.ai/v1/models
List the models you can call
⌄
The catalogue available through the Model API, in the same shape OpenAI clients expect — so a model picker built against OpenAI populates itself. Use the `id` from here as the `model` field in a completion.
models{
"object": "list",
"data": [
{
"id": "google/gemini-3.1-flash-lite",
"object": "model"
}
]
}
Errors
Both APIs use the same status codes. The Model API returns them in the error shape familiar model clients already understand.
The key is missing, malformed or revoked.
The key is valid but lacks the scope this endpoint needs.
Out of credit, or this key hit its own daily/monthly spend cap. Top up, or raise the cap on the Developers page.
No such record — or it belongs to another account. We never distinguish the two.
Too many requests. Back off and retry.
Machine-readable
Every format is generated from the same source as this page, so a human, client generator and coding assistant receive the same contract.
Using an AI coding assistant? Point it at https://bytepilot.ai/llms.txt and ask it to build the integration.
Create a scoped key and make the first call.
Your free credit works across the platform and both APIs.