Upload call recordings to Rulebase
Pushes audio recordings and their metadata into a Rulebase workspace over the v1 REST API, for phone systems with no native connection. Once received, uploads enter the normal evaluation pipeline.
This writes to a live workspace. It runs a dry run by default and requires an
explicit --apply against a reviewed plan file.
Reversibility
Uploads cannot be undone through the API. There is no delete endpoint for a conversation upload. A batch pushed by mistake becomes conversations in the workspace, is eligible for evaluation, and consumes evaluation volume. Removing them is a support request, not a rollback.
What is safe: re-running is safe. Each call carries a unique_id that
Rulebase uses to identify it, and the journal in this skill skips anything already
uploaded. An interrupted run resumes without duplicating.
Because the damage is "too much data, unremovable" rather than "data destroyed",
the guardrail that matters most is --max-changes. Backfilling a year of call
history should be a deliberate sequence of bounded batches, each one verified,
not a single unbounded run.
What the API actually accepts
Worth reading before planning a migration, because it is narrower than "push your conversations in" suggests.
Audio only. WAV, MP3 or M4A. This endpoint ingests call recordings; it is not a generic conversation-import API for helpdesk tickets.
sourceis a fixed enum. The API accepts a specific set of source identifiers, not a free-text system name. Check the current v1 reference for the accepted values and use one of them exactly — an unrecognised source is rejected.100 MB per file on the multipart endpoint. Larger files go through the presigned-URL path.
Metadata is mandatory and asymmetric by direction. All fields are required, and two of them mean different things for inbound and outbound calls:
Field Inbound Outbound agentAgent email address Agent email address callerCustomer phone number Agent email (often duplicates agent)calledTrunk or extension number Customer phone number Getting
caller/calledbackwards for outbound calls is the most common ingestion error, and it does not fail loudly — it produces conversations with the customer and agent transposed, which then get evaluated that way.agentis how the employee is identified. An email that does not match a person on the QA roster produces a conversation nobody is evaluated for. Reconcile the agent emails in your manifest against the roster before uploading; that is what the dry run does.
Two upload paths
- Multipart (
POST /conversations/upload) — one request per call, file in the body. Simplest, and correct for anything under the size limit. - Presign (
POST /conversations/upload/presign→ PUT to the returned URL) — request a URL for a filename and source, then upload the bytes directly. The presigned URL expires in 30 minutes, so request it immediately before the transfer, never in a batch upfront. A pre-generated list of URLs for a thousand files will be mostly expired before you use it.
Poll GET /conversations/upload/{id} for processing status. Uploads that are
accepted still take time to become conversations, so a 201 is receipt, not
completion.
Before you run
RULEBASE_API_KEYin the environment. Never as an argument. If you don't have a key yet, or you're unsure of the region, set up access first — the region is the thing that silently returns401when wrong.Region host.
api.rulebase.cofor US,eu.api.rulebase.cofor EU. A key from one region does not authenticate against the other, and the failure is an indistinguishable401.The agent roster. Have the list of evaluatable agent emails to reconcile against.
A manifest. One JSON object per line describing each call:
{"file":"/calls/2026-07-01/abc.mp3","unique_id":"xc-88213","type":"inbound","agent":"amara@example.com","caller":"+2348012345678","called":"+2341234567","recorded_at":"2026-07-01T09:14:22Z"}Build it from your phone system's own export. Do not hand-write it for more than a handful of calls.
Safety
The seven rules this skill runs under:
- Dry run is the default.
upload-calls.mjswith no--applyvalidates and plans. It never transfers a byte. - Deciding and doing are separate. The dry run writes a plan file;
--applyconsumes it. A human can read the plan — or hand it to someone who can approve it — before anything is uploaded. - Append-only audit log. One JSONL record per attempted upload as it happens,
with the target
unique_id, outcome, returned upload id, and the plan it came from. - Idempotent and resumable. Completed
unique_ids are journaled and skipped on re-run. Interruptions are expected on multi-hour backfills. - Bounded blast radius.
--max-changesdefaults to 100 and must be raised deliberately. Backfill in verified batches. - Reversibility stated above, not buried here.
- Verify after applying. The run re-reads each upload's status and reports anything that did not land.
There is no flag that combines validating and uploading, and no --force.
Usage
Dry run — validates the manifest, checks every file exists and is a supported format and size, reconciles agent emails against the roster you supply, flags inbound/outbound metadata that looks transposed, confirms auth, and writes a plan:
export RULEBASE_API_KEY=...
node scripts/upload-calls.mjs \
--manifest ./calls.jsonl \
--roster ./agents.txt \
--region us \
--plan ./out/upload-plan.json
Read the plan. Then apply it, bounded:
node scripts/upload-calls.mjs \
--plan ./out/upload-plan.json \
--apply \
--max-changes 50 \
--audit ./out/upload-audit.jsonl \
--journal ./out/upload-journal.jsonl
Re-run the same command after an interruption; journaled calls are skipped. Raise
--max-changes only once a smaller batch has been verified in the workspace.
Write outputs outside the repository. Manifests contain customer phone numbers and agent emails — they are production PII and must not be committed.
When uploads are accepted but nothing appears
Check in this order, because each one masks the next:
- Processing status —
GET /conversations/upload/{id}. Accepted is not processed. - The agent email matched a person on the roster. If not, the conversation exists with nobody to evaluate.
- Evaluation eligibility. Coverage sampling, the eligibility window, and eligibility instructions all gate whether a conversation gets scored. An eligibility window shorter than the age of a backfill will silently exclude the entire backfill — this is the usual reason a historical import produces no scores.
- A published scorecard exists whose scope covers the channel and the people. No published scorecard means no evaluations at all.
Report which of these you checked. "The upload worked" and "the calls are being evaluated" are different claims.
Present results to the user
- Region and organization you uploaded to, stated before anything else.
- Planned vs applied vs skipped, with the
--max-changesbound named. - Validation findings from the dry run — missing files, unsupported formats, oversized files, agent emails not on the roster, suspected transposed metadata. These are the findings worth acting on before uploading.
- Verification results — per-upload status after applying, and anything that did not land.
- What remains — how many calls are left in the manifest and the command to continue.
- Whether the calls will actually be evaluated, or which of the four gates above you could not confirm.