API reference

Send a recording and get back who spoke, what they said and when: every speaker separated, overlapping speech kept, every word timed. One model, calico-1-flash, at $0.006 per minute, billed by the second.

Base URL https://app.maybloc.com/api/v1. Requests and responses are JSON.

Quickstart

Four requests: create an upload, send the file, transcribe it, read the result. Recordings run 20 seconds to 3 hours, in MP3, M4A, AAC, WAV, FLAC, OGG, Opus or WebM audio, or MP4, MOV, MKV or WebM video (only the sound is kept), up to 2 GB. For video, only the audio track is kept; the video file is deleted when the transcript finishes.

export BASE=https://app.maybloc.com/api/v1

# 1. Create an upload. The response has upload_url, headers and id.
curl -s $BASE/uploads -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"file_name":"call.m4a","content_type":"audio/mp4","byte_size":4812001}'

# 2. PUT the file to upload_url (valid 15 minutes), then mark the upload complete.
curl -X PUT "$UPLOAD_URL" -H "Content-Type: audio/mp4" --data-binary @call.m4a
curl -s -X POST $BASE/uploads/$UPLOAD_ID/complete -H "Authorization: Bearer $API_KEY"

# 3. Transcribe. duration_ms is the recording's length; the price is based on it.
curl -s $BASE/transcripts -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{"upload_id":"'$UPLOAD_ID'","duration_ms":155000}'

# 4. When status is "completed", read the result.
curl -s $BASE/transcripts/$TRANSCRIPT_ID/result -H "Authorization: Bearer $API_KEY"

A transcript moves through queued, processing and then completed, failed or canceled. Poll GET /transcripts/{id} every few seconds, or use a webhook.

Authentication

Create a key in the console under API keys and send it as a bearer token. Keys act for the whole workspace. Keep them on your server.

Authorization: Bearer mbk_live_...

Read-only keys can list and read transcripts but not create, rename, cancel or delete them.

Language and add-ons

Send config with POST /transcripts to set the language and turn on add-ons. Leave it out for automatic language detection and no add-ons. The playground's Export config writes the same object.

{
  "upload_id": "…",
  "duration_ms": 155000,
  "config": {
    "model": "calico-1-flash",
    "language": "en",
    "addons": [
      "smart_format",
      "insights",
      "translation",
      "custom_fields"
    ],
    "translate_to": [
      "es"
    ],
    "custom_fields": [
      {
        "key": "next_step",
        "question": "What was agreed as the next step?",
        "type": "text"
      }
    ]
  }
}

language is auto or an ISO 639-1 code such as en, ja or es. Add-ons are made from the transcript and added to the per-minute price:

redact_piiNames, phone numbers, emails, addresses, card and account numbers and other personal details replaced with typed tags such as [PERSON_NAME], in the text and every export, with the times they were said.+$0.0005/min
smart_formatPunctuation and capitals added, hesitations and stutters removed, every word kept on its timing. Applies to the transcript and every export.+$0.0005/min
subtitlesBroadcast-style captions: punctuated sentences, at most two lines and 7 seconds, on screen long enough to read, with crosstalk shown as two speakers. SRT and WebVTT.Free
insightsA summary, chapters, key points, action items and decisions with who owns them, and highlights worth clipping.+$0.001/min
identify_speakersSpeakers named from the conversation itself, when someone is addressed or introduced by name or states their role.+$0.0005/min
sentimentThe tone overall and per speaker, with the moments where it is strongest or shifts.+$0.0005/min
topicsWhat the conversation is about, and every person, company, place, product, date and amount named, with where.+$0.0005/min
keywordsThe terms each conversation keeps coming back to, ranked, with where they were said.+$0.0005/min
custom_fieldsYour own questions answered from every recording, each in the type you choose: text, number, yes or no, a list, a speaker or one of your options.+$0.0005/min
translationThe transcript and subtitles in up to 5 other languages, line by line in sync with the audio.+$0.0015/min per language

translation takes translate_to, up to 5 language codes, each charged per minute. custom_fields takes up to 20 questions, each with a key, a question and an answer type: text, number, boolean, list, speaker or choice with options.

Their output is in the result under addons, keyed by the same names. Most are generated shortly after the transcript: their status is pending until the transcript.addons.completed webhook, and if they can't be generated it is failed and their price is refunded. With redact_pii, the transcript stays processing until names and other details have been looked for, so nothing unredacted is ever served; its text, words and exports are redacted with typed tags such as [PERSON_NAME], and audio lists where each was said. With smart_format the text is punctuated and readable. Speakers in generated text appear as {{speaker_1}}. Names found by identify_speakers are applied to speakers you haven't named; change or clear them with PATCH and speaker_names. To add add-ons to a finished transcript without transcribing it again, call POST /transcripts/{id}/addons.

The result

GET /transcripts/{id}/result returns the conversation. Speaker ids are stable within one transcript. Names you set in the console or with PATCH are on the transcript as speaker_names.

{
  "id": "6f1c…",
  "object": "transcript.result",
  "model": "calico-1-flash",
  "duration_ms": 155290,
  "language": "en",
  "speakers": [
    {
      "id": "speaker_1",
      "speaking_ms": 61200
    },
    {
      "id": "speaker_2",
      "speaking_ms": 48800
    }
  ],
  "turns": [
    {
      "id": "t00001",
      "speaker_id": "speaker_1",
      "start_ms": 480,
      "end_ms": 3920,
      "text": "So what did you tell the board?",
      "words": [
        {
          "text": "So",
          "start_ms": 480,
          "end_ms": 610
        },
        "…"
      ]
    },
    "…"
  ],
  "overlaps": [
    {
      "id": "o1",
      "start_ms": 11840,
      "end_ms": 12610,
      "turn_ids": [
        "t00003",
        "t00004"
      ]
    }
  ],
  "activity": [
    {
      "speaker_id": "speaker_1",
      "start_ms": 440,
      "end_ms": 3960
    },
    "…"
  ]
}
  • turns: one speaker talking without a break, with each word's time.
  • overlaps: where two or more people talk at once, and which turns.
  • activity: when each speaker is audible, including short sounds with no words.

For subtitles or plain text, call GET /transcripts/{id}/export?format=srt (or vtt, txt). Speaker names are applied, and the cues are caption-sized when the subtitles add-on is on. GET /account returns your balance, so you can check credit before submitting.

Endpoints

get/accountRetrieve the account

The workspace's balance, to check credit before submitting, and the calling key's scope.

post/uploadsCreate an upload

Returns a URL that accepts one PUT of the file for 15 minutes.

post/uploads/{id}/completeComplete an upload

Call after the PUT succeeds. The file's size and type are checked before it can be transcribed.

get/transcriptsList transcripts

Transcripts newest first, up to limit per page. To read the next page, pass the last id as starting_after while has_more is true.

post/transcriptsCreate a transcript

Queues a completed upload for transcription and holds its price from your balance. Repeating the request with the same Idempotency-Key returns the same transcript and charges once.

get/transcripts/{id}Retrieve a transcript
patch/transcripts/{id}Rename a transcript or its speakers
delete/transcripts/{id}Delete a transcript

Deletes the recording and its transcript now. Billing records stay. Cancel a running transcript first.

get/transcripts/{id}/resultRetrieve the result

Available once the transcript is completed.

get/transcripts/{id}/exportExport subtitles or text

The transcript as an SRT or WebVTT subtitle file, or plain text, with speaker names. Subtitles use caption-sized cues when the subtitles add-on is on, otherwise one cue per turn. With redact_pii the text is redacted.

post/transcripts/{id}/addonsAdd add-ons

Adds add-ons to a completed transcript without transcribing it again, or more translation languages and custom fields to the ones it has. The difference in per-minute price is charged for the recording's length. Generated add-ons are then produced, and the transcript.addons.completed webhook fires when they are ready; names found by identify_speakers are applied to speakers you haven't named.

post/transcripts/{id}/cancelCancel a transcript

Stops a queued or running transcript. Canceled transcripts are not charged.

Every field is in the OpenAPI document.

Webhooks

Add an endpoint under Webhooks to get a POST for each status change: transcript.queued, transcript.processing, transcript.completed, transcript.failed, transcript.canceled, transcript.addons.completed, transcript.addons.failed.

{
  "id": "evt_…",
  "type": "transcript.completed",
  "created_at": "2026-10-06T09:14:03Z",
  "data": {
    "id": "6f1c…",
    "object": "transcript",
    "model": "calico-1-flash",
    "status": "completed"
  }
}

Each request has Maybloc-Timestamp, Maybloc-Delivery and Maybloc-Signature. The signature is sha256= followed by the hex HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with your endpoint secret. Reject timestamps older than five minutes and skip delivery ids you have seen. Failed deliveries are retried for about ten hours.

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody is the request body exactly as received, before JSON parsing.
export function verify(rawBody, headers, secret) {
  const timestamp = headers["maybloc-timestamp"];
  const signature = headers["maybloc-signature"] ?? "";
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  return signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Errors and limits

Errors return a status code and a body with error.code, error.message and error.request_id. Quote the request id when you contact us.

400invalid_requestA field is missing or out of range.
401authentication_requiredMissing or invalid API key.
402insufficient_creditYour balance is too low for this recording.
403forbiddenRead-only key, or a request from another site.
409conflictUpload not complete, result not ready, or an Idempotency-Key reused for a different request.
429rate_limitedToo many requests. Wait for Retry-After seconds.

Send a new Idempotency-Key for each transcript you want, and the same one to retry a request safely: you get the same transcript and are charged once.

Each API key can make 300 requests a minute across all endpoints. Past that, requests return 429 with Retry-After. Poll a transcript every few seconds, or use a webhook instead of polling.

Your data

Recordings and results are private to your workspace. Recordings are kept for 7 days and results for 30 days, then deleted. DELETE /transcripts/{id} deletes both at once. Billing records stay. Your recordings are not used to train models.