Skip to content

Public API

Aelo’s public API is a call analytics API — conversation intelligence over REST. You send call recordings or chat transcripts in from your own systems — a CRM, a telephony platform, a custom pipeline — and read back records and their analysis (scores, summaries, objections) once processing finishes. It is org-scoped: every request acts on the organization that issued the API key, and every response is limited to that organization’s data.

Available on Starter and above. Free-plan organizations cannot issue or use public API keys.

  1. Go to Settings → API Keys. Only the organization owner (the client role) can see this section — admins, supervisors and agents cannot manage keys.
  2. Choose a name, pick one or both scopes (records:write, records:read), and optionally set an expiry date.
  3. The plaintext key (aelo_sk_…) is shown once, at creation. Store it yourself — Aelo only ever keeps its hash and cannot show it to you again. If you lose it, revoke it and issue a new one.

An organization may hold at most 10 active keys at a time; revoke unused ones before issuing more.

Every request carries the key as a bearer token:

Authorization: Bearer aelo_sk_YOUR_KEY

All endpoints live under https://api.aelo.cloud/api/public/v1.

A key only unlocks what its scopes allow:

ScopeGrants
records:writeCreate records (POST /records, POST /records/upload)
records:readList and read records and analyses (the three GET routes)

A request to an endpoint the key’s scopes don’t cover fails with insufficient_scope, even if the key itself is valid.

LimitValue
Requests per key200 / minute
Multipart upload size (POST /records/upload)25 MB
fileUrl download size (POST /records)100 MB, or 25 MB when the source sends no Content-Length
fileUrl download timeout30 seconds
Active API keys per organization10
Record list page size50 by default, capped at 100 (limit above 100 is silently reduced, not rejected)

The multipart limit is lower than the fileUrl limit because a multipart body has to be fully buffered in memory before Aelo can read it; a fileUrl download is streamed straight to storage — as long as the source declares its Content-Length. A source that declares none cannot be streamed and is held in memory like a multipart body, under the same 25 MB ceiling. If your audio is larger than 25 MB, upload it to storage you control and send fileUrl instead of the file itself — see Uploading calls and chats for both paths in detail.

Method & pathScopeWhat it does
POST /recordsrecords:writeCreate a record from a fileUrl (calls) or textContent (chats)
POST /records/uploadrecords:writeCreate a call record from an uploaded audio file (multipart)
GET /recordsrecords:readList your organization’s records
GET /records/{recordId}records:readGet one record
GET /records/{recordId}/analysisrecords:readGet the latest analysis for a record

The two upload endpoints, their request fields, idempotency behavior and the warnings they can return are covered in Uploading calls and chats. The rest are documented below.

Lists records newest-first. Archived records are excluded — and not marked as such, since the response has no archived field, so an archived record simply does not appear.

Query parameters:

ParamDefaultNotes
limit50Capped at 100
offset0
createdAfterISO-8601 datetime. Inclusive (createdAt >= createdAfter)
createdBeforeISO-8601 datetime. Inclusive (createdAt <= createdBefore)
{
"data": [
{
"id": "rec_...",
"projectId": "your-project-id",
"type": "call",
"status": "completed",
"externalId": "crm-call-12345",
"createdAt": "2026-07-01T10:00:00.000Z",
"occurredAt": "2026-07-01T09:58:12.000Z",
"durationSeconds": 184,
"agentId": "42",
"agentName": "Ivan Petrov",
"callDirection": "outbound"
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}

Returns one record in the same shape as the list. 404s as record_not_found if the id doesn’t exist or belongs to another organization — the two cases are indistinguishable on purpose.

Returns the latest analysis for a record.

{
"recordId": "rec_...",
"analysisId": "an_...",
"createdAt": "2026-07-01T10:04:30.000Z",
"summary": "Customer asked about pricing tiers; agent...",
"qualityScore": 78,
"metrics": {
"greetingScore": 90,
"needsScore": 70,
"clarityScore": 80,
"objectionScore": 65,
"closingScore": 75,
"politenessScore": 95,
"saleProbability": 62
}
}

qualityScore is the canonical score — the same number your dashboard shows for this call, not a self-reported figure from the model. Every metric is null when the analysis produced no value for it (a scoreless call category, or a rubric item your project has switched off) — never a 0 standing in for “not scored”. saleProbability only applies to calls; it is null for chats.

The response is deliberately narrower than what Aelo stores internally: provider, model, prompt profile, token counts and cost are not part of the public contract.

Two 404-shaped situations are distinguished by their error code, because your next move differs:

  • record_not_found — the record id itself doesn’t exist (or isn’t yours). Fix the id.
  • analysis_not_found — the record exists but carries no analysis. details.analysisExpected says whether one is still coming: true means keep polling, false means the record finished without an analysis and never will have one — stop. Some calls end that way by design: a call too short to grade (details.reason: "short_call") or a record that failed in the pipeline ("record_failed"). details.recordStatus carries the record’s current status either way.

Every failure returns the same envelope:

{
"error": {
"code": "invalid_request",
"message": "Human-readable description.",
"details": { "field": "..." }
}
}

details is optional and, when present, describes only your own request — never internal state. An unrecognized field in a JSON body, form or query string answers with invalid_request and names every offending field, plus a didYouMean suggestion when one is a close match to a real one (e.g. projectIDprojectId).

CodeStatusMeaning
invalid_api_key401Missing/malformed Authorization header, or the key is unknown, revoked or expired (all three look identical, on purpose)
insufficient_scope403The key doesn’t carry the scope this endpoint requires
tier_not_eligible403The organization is on the free plan
feature_disabled403Your organization’s administrator has switched the public API off. The key is still valid and upgrading the plan won’t help — ask them to turn it back on
insufficient_balance402The plan’s included minutes for this cycle are spent and the credit balance is empty. Nothing was stored — add credits in Settings → Billing and send the request again
project_not_found404projectId doesn’t exist, or belongs to another organization
project_not_accepting_records409The project exists but is paused or archived — the caller’s own admin needs to reactivate it before this project can accept records again
record_not_found404The record id doesn’t exist, or belongs to another organization
analysis_not_found404The record exists but has no analysis — poll on while details.analysisExpected is true, stop when it is false
not_found404No such route under /api/public/v1
rate_limited429More than 200 requests/minute on this key
payload_too_large413See Limits above
unsupported_media_type415The uploaded/downloaded content isn’t audio or video
invalid_request400The request body, form or query string failed validation
fetch_failed400The file at fileUrl could not be downloaded
resolution_unavailable503Aelo couldn’t verify fileUrl’s destination right now — transient, retry (Retry-After header)
service_unavailable503The analysis store is temporarily unreachable — transient, retry (Retry-After header)
internal_error500Unexpected server-side failure

A 503 always carries a Retry-After header (in seconds); back off and retry rather than treating it as a permanent failure.

Yes. Aelo’s public API is a REST call analytics API at https://api.aelo.cloud/api/public/v1: org-scoped Bearer keys (aelo_sk_…), records:write / records:read scopes, 200 requests per minute per key. Available on Starter and above.

How do I upload call recordings for analysis?

Section titled “How do I upload call recordings for analysis?”

Two ways: POST /records/upload takes the audio file itself (multipart, up to 25 MB); POST /records takes a fileUrl Aelo downloads from you (up to 100 MB). Chat transcripts go through POST /records as textContent. Repeat submissions are deduplicated by externalId — send your own identifier and a retry returns the original record instead of creating a second one. Both endpoints are covered in Uploading calls and chats.

Bitrix24 usually doesn’t need it: the native integration analyses calls with no API code at all. The public API is for everything else — your own telephony, a data pipeline, or a CRM Aelo doesn’t integrate with yet.

  • Uploading calls and chats — request fields, size limits, idempotency and warnings for the two upload endpoints
  • Pricing — the public API is included on Starter and above