API Reference
Sprio's REST API lets you place outbound calls, check call status, run post-call analysis, and add knowledge to an assistant — authenticated with an API key.
Base URL: https://api.voice-up.sprio.ai/v1
All requests require:
Authorization: Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Errors follow the shape:
{"error": "error_code", "message": "human readable description"}
There is no list/collection endpoint yet (no GET /v1/calls, no
GET /v1/assistants) and no pagination — every response below is a single
object. If you need to enumerate calls or assistants today, do it from your
Sprio dashboard rather than the API.
Place an outbound call
POST /v1/calls/outbound
Permission required: calls:write
| Field | Required | Notes |
|---|---|---|
phone_number | Yes | E.164 format, e.g. +919876543210 |
assistant_id | Yes | Must belong to your organization |
metadata | No | Arbitrary object, stored on the call |
idempotency_key | No | Replaying the same key returns the original call instead of creating a new one |
callback_url | No | Sprio will notify this URL as the call progresses |
Before dialing, Sprio checks your organization's credit balance — a call that would exceed it is rejected rather than started.
- curl
- PowerShell
- Python
- Node.js
curl -X POST https://api.voice-up.sprio.ai/v1/calls/outbound \
-H "Authorization: Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+919876543210",
"assistant_id": "your-assistant-id",
"metadata": {"source": "custom-integration"},
"idempotency_key": "order-12345"
}'
$body = @{
phone_number = "+919876543210"
assistant_id = "your-assistant-id"
metadata = @{ source = "custom-integration" }
idempotency_key = "order-12345"
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/v1/calls/outbound" `
-Method Post `
-Headers @{ Authorization = "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } `
-ContentType "application/json" `
-Body $body
import requests
response = requests.post(
"https://api.voice-up.sprio.ai/v1/calls/outbound",
headers={"Authorization": "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
json={
"phone_number": "+919876543210",
"assistant_id": "your-assistant-id",
"metadata": {"source": "custom-integration"},
"idempotency_key": "order-12345",
},
)
print(response.status_code, response.json())
const response = await fetch("https://api.voice-up.sprio.ai/v1/calls/outbound", {
method: "POST",
headers: {
Authorization: "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
phone_number: "+919876543210",
assistant_id: "your-assistant-id",
metadata: { source: "custom-integration" },
idempotency_key: "order-12345",
}),
});
console.log(response.status, await response.json());
Response 202:
{
"request_id": "...",
"call_id": "...",
"status": "queued",
"phone_number": "+919876543210",
"assistant_id": "...",
"callback_url": "...",
"message": "Call queued"
}
A replayed idempotency_key returns the same shape with
"idempotent_replay": true instead of creating a second call.
Errors:
| Status | Error code | Meaning |
|---|---|---|
| 400 | invalid_phone_number | Not valid E.164 |
| 400 | assistant_id_required | Missing assistant_id |
| 404 | assistant_not_found | No such assistant |
| 403 | assistant_wrong_org | Assistant belongs to a different organization |
| 403 | insufficient_credits | Includes required and available amounts in the response |
| 429 | rate_limit_exceeded | See Rate limits — this endpoint has its own limit on top of the global one |
Get call status
GET /v1/calls/{id}
Permission required: calls:read (a key with calls:write also
satisfies this — calls:write implies calls:read)
Scoped to your organization — a call ID belonging to another org returns
404 call_not_found, not a permission error.
- curl
- PowerShell
- Python
- Node.js
curl https://api.voice-up.sprio.ai/v1/calls/your-call-id \
-H "Authorization: Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/v1/calls/your-call-id" `
-Headers @{ Authorization = "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
import requests
response = requests.get(
"https://api.voice-up.sprio.ai/v1/calls/your-call-id",
headers={"Authorization": "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
)
print(response.json())
const response = await fetch("https://api.voice-up.sprio.ai/v1/calls/your-call-id", {
headers: { Authorization: "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" },
});
console.log(await response.json());
Response 200:
{
"id": "...",
"status": "completed",
"duration": 47,
"cost": 0.0,
"sentiment": "positive",
"summary": "...",
"started_at": "2026-07-31T10:15:00Z",
"ended_at": "2026-07-31T10:15:47Z",
"phone_number": "+919876543210",
"assistant_id": "...",
"assistant_name": "...",
"idempotency_key": null,
"callback_url": null,
"callback_status": null,
"request_metadata": {}
}
Analyze a call
POST /v1/calls/{id}/analyze
Runs summary, success evaluation, and structured-data extraction against the call's transcript, and stores the result on the call.
- curl
- PowerShell
- Python
- Node.js
curl -X POST https://api.voice-up.sprio.ai/v1/calls/your-call-id/analyze \
-H "Authorization: Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/v1/calls/your-call-id/analyze" `
-Method Post `
-Headers @{ Authorization = "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
import requests
response = requests.post(
"https://api.voice-up.sprio.ai/v1/calls/your-call-id/analyze",
headers={"Authorization": "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
)
print(response.json())
const response = await fetch("https://api.voice-up.sprio.ai/v1/calls/your-call-id/analyze", {
method: "POST",
headers: { Authorization: "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" },
});
console.log(await response.json());
Response 200:
{
"call_id": "...",
"analysis": {
"summary": "...",
"success_eval": "...",
"structured_data": {}
}
}
Add knowledge to an assistant
POST /v1/assistants/{id}/knowledge-base
Multipart upload, field name files. Accepts .txt, .md, .pdf,
.docx — extracted text is truncated to 50,000 characters per file and
appended to the assistant's knowledge base.
- curl
- PowerShell
- Python
- Node.js
curl -X POST https://api.voice-up.sprio.ai/v1/assistants/your-assistant-id/knowledge-base \
-H "Authorization: Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-F "files=@brochure.pdf"
$form = @{ files = Get-Item -Path "brochure.pdf" }
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/v1/assistants/your-assistant-id/knowledge-base" `
-Method Post `
-Headers @{ Authorization = "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } `
-Form $form
import requests
with open("brochure.pdf", "rb") as f:
response = requests.post(
"https://api.voice-up.sprio.ai/v1/assistants/your-assistant-id/knowledge-base",
headers={"Authorization": "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
files={"files": f},
)
print(response.json())
import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("files", new Blob([await readFile("brochure.pdf")]), "brochure.pdf");
const response = await fetch(
"https://api.voice-up.sprio.ai/v1/assistants/your-assistant-id/knowledge-base",
{
method: "POST",
headers: { Authorization: "Bearer sprio_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" },
body: form,
}
);
console.log(await response.json());
Response 200: the assistant's full updated knowledge base array:
[
{
"id": "...",
"filename": "brochure.pdf",
"path": "...",
"textPreview": "...",
"uploadedAt": "2026-07-31T10:00:00Z"
}
]
Errors: 404 if the assistant doesn't exist, 400 (No files uploaded) if the request has no files.
Permission scopes
An API key's permissions is a list of scope strings, set at creation.
Currently enforced scopes:
| Scope | Applies to |
|---|---|
calls:write | POST /v1/calls/outbound (also satisfies calls:read) |
calls:read | GET /v1/calls/{id} |
If you don't specify permissions when creating a key, it defaults to
["calls:write", "assistants:read"].
POST /v1/calls/{id}/analyze and POST /v1/assistants/{id}/knowledge-base
currently only require a valid API key — no specific permission scope is
checked on them yet. Don't rely on scoping a key away from these two
endpoints as an access-control boundary.