1. Purpose of this page
This page tells a large language model (LLM) or AI coding agent how to write
code that uses QuickTranscript, either through the REST API or through the hosted
MCP (Model Context Protocol) server. It explains how to transcribe and summarize
audio or video files, and what the service does not do.
The machine-readable schema at /openapi.json describes the
same REST contract. It does not describe the MCP tools: MCP clients discover tool schemas with
tools/list (see section 9). This page adds workflow rules, gotchas, and complete examples.
REST base URL: https://quicktranscript.app. MCP endpoint: https://quicktranscript.app/mcp/.
2. Rules for the LLM
- Authenticate every REST and MCP request with the
X-API-Keyheader. Keys start withvdz_. Users create keys at /api-keys. Never invent a key. - Read the key from an environment variable (for example
QUICKTRANSCRIPT_API_KEY). Never hard-code it, log it, or include it in commits. - Transcription runs as an asynchronous job. With REST, submit with
POST /api/transcriptions, then pollGET /api/transcriptions/{task_id}untilstatusiscompleteorfailed. With MCP, calltranscribe_url, thenget_transcription_status(see section 9). - For REST submission, send the request body as form fields:
multipart/form-data(required for file uploads) orapplication/x-www-form-urlencoded. Do not send a JSON body. A JSON{"file_url": ...}body is ignored and returns400 No file or file_url supplied.This rule does not apply to MCP: the MCP SDK sends JSON-RPC for you, andtools/listreturns the tool schemas. - A file upload must declare an
audio/*orvideo/*content type. Otherwise the API returns400 Invalid file type. Set it explicitly: tools such as curl sendapplication/octet-streamfor extensions they don't recognize, such as.m4a. - Poll every 5–10 seconds and stop after a timeout you choose. Processing time grows with media length.
- Use only the REST endpoints in the table below or the MCP endpoint in section 9. Do not call browser routes such as
/upload,/process-url,/task-status/…, or/history. Those return HTML, not JSON. summary_htmlis HTML. Render it as HTML or convert it to text. Do not show it to users as raw markup.
3. Endpoint reference
| Method & path | Purpose | Input | Success |
|---|---|---|---|
GET /api/auth/test |
Verify an API key. | Header only. | 200 {"authenticated": true, "user_email": "…" | null} |
GET /api/credits |
Read the credit balance. Doesn't spend credits. | Header only. | 200 {"credits": 12} |
POST /api/transcriptions |
Submit a job. |
Multipart form with exactly one of:
summarize (bool, default true). Use false for a transcript without a summary.
|
202 {"task_id": "<uuid>", "status": "pending"} |
GET /api/transcriptions/{task_id} |
Poll one job. | Path task_id. |
200 status object |
GET /api/transcriptions/completed |
Fetch completed jobs that haven't been returned before (built for Zapier-style polling triggers). | Query limit 1–50, default 10. |
200 array of result objects |
/api/transcriptions/completed is destructive. Each result is returned only once: after a
call returns a job, later calls skip it. Use this endpoint in a single polling consumer. To track a specific job, poll
/api/transcriptions/{task_id}, which can be read any number of times.
4. Job lifecycle
status moves through these values:
pending → processing → transcribing → summarizing (only if summarize=true) → complete
↘ failed (from any step)
- Stop polling at
completeorfailed. Treat every other value as still running. status_messageis a short human-readable progress message, for example"Transcribing audio...".- A failed job includes
error_message(a user-facing reason such as"There was an issue reading the uploaded file.") anderror_type(file_error,download_error,prohibited_content,insufficient_credits, orprocessing_error). Showerror_messageto the user. Both fields arenullunlessstatusisfailed. - Credits are deducted only when a job completes. Failed jobs cost nothing.
- If transcription succeeds but summarization fails, the job still ends as
complete. In that casesummary_htmlcontains a paragraph starting with(Summary could not be generated.
5. Response schemas
Status object: GET /api/transcriptions/{task_id}
{
"task_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"status": "transcribing",
"status_message": "Transcribing audio...",
"error_message": null,
"error_type": null,
"result": null
}
A failed job:
{
"task_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"status": "failed",
"status_message": "Processing failed.",
"error_message": "The uploaded file appears to be corrupted or in an unsupported format.",
"error_type": "processing_error",
"result": null
}
When status is complete, result holds a result object.
Result object
{
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"task_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"original_filename": "meeting.mp3",
"completed_at": "2026-09-24T05:56:00.357729+00:00",
"duration_seconds": 3038.7,
"transcription": [
{"timestamp": "[00:00]", "text": "Okay, let's get started."},
{"timestamp": "[20:00]", "text": "Moving on to the quarterly results."},
{"timestamp": "[01:00:00]", "text": "Thanks everyone."}
],
"summary_html": "<p>The meeting covered the <strong>quarterly results</strong>.</p>",
"final_credit_cost": 2,
"total_tokens": null
}
| Field | Notes |
|---|---|
transcription | Ordered list of segments. timestamp is a string in the format [MM:SS] or [HH:MM:SS]. How finely the transcript is split depends on the speech-to-text provider: one segment can cover a sentence or a 20-minute block. To get the full text, join all text values in order. |
summary_html | Sanitized HTML (p, strong, em, lists, headings, tables, links). null when summarize=false. |
completed_at | ISO 8601 timestamp with UTC offset, the same format on both endpoints. |
duration_seconds | Length of the processed audio or video in seconds. Jobs completed before 2026-09-24 may contain processing time instead. |
total_tokens | Often null. Do not depend on it. |
final_credit_cost | Credits charged for the job. |
6. Limits and credits
- Maximum file size (upload or URL download): 500 MB. Larger files return
413. - Cost:
max(1, ceil(file_size_MB / 30))credits, estimated from the file size at submission time. The balance is checked before the job is accepted. - URL downloads must return HTTP 200 with an
audio/*,video/*, orapplication/octet-streamcontent type within 60 seconds. Share links from Google Drive, Dropbox, and OneDrive are converted to direct-download links automatically. The file must be public. Private or internal network addresses are rejected. - Supported media includes common audio and video formats such as MP3, WAV, M4A, FLAC, OGG, WEBM, MP4, MOV, and MKV.
7. Errors
REST errors use the FastAPI shape {"detail": "<message>"}. Validation errors (422) return detail as an array. MCP errors are described in section 9.3.
| Status | Meaning | What the client should do |
|---|---|---|
| 400 | Both file and file_url sent, neither sent, the file is not audio/video, or the URL could not be downloaded. | Fix the request. Show detail to the user. Don't retry unchanged. |
| 401 | API key missing, malformed, revoked, or unknown. | Ask the user for a valid key. Don't retry. |
| 402 | Insufficient credits: "Insufficient credits. Need N, have M." | Tell the user to buy credits at quicktranscript.app. |
| 403 | The task belongs to another account. | Check the task_id and the key. |
| 404 | Unknown task_id. | Check the task_id. |
| 413 | File exceeds 500 MB. | Compress (for example to 64 kbps mono MP3) or split the file, then resubmit. |
| 422 | Invalid parameter, for example limit=100. | Fix the parameter. |
| 5xx | Server or network failure. | Retry with exponential backoff, at most a few times. |
8. Examples
8.1 curl: verify the key and check credits
export QUICKTRANSCRIPT_API_KEY="vdz_your_api_key"
curl -s https://quicktranscript.app/api/auth/test -H "X-API-Key: $QUICKTRANSCRIPT_API_KEY"
# {"authenticated":true,"user_email":null}
curl -s https://quicktranscript.app/api/credits -H "X-API-Key: $QUICKTRANSCRIPT_API_KEY"
# {"credits":12}
8.2 curl: upload a file with an explicit content type
curl -s -X POST https://quicktranscript.app/api/transcriptions \
-H "X-API-Key: $QUICKTRANSCRIPT_API_KEY" \
-F "file=@interview.m4a;type=audio/mp4"
# {"task_id":"a1b2c3d4-e5f6-7890-1234-567890abcdef","status":"pending"}
8.3 curl: transcribe from a URL without a summary
curl -s -X POST "https://quicktranscript.app/api/transcriptions?summarize=false" \
-H "X-API-Key: $QUICKTRANSCRIPT_API_KEY" \
-F "file_url=https://www.dropbox.com/s/abc123/podcast.mp3?dl=0"
8.4 bash: poll until the job finishes (requires jq)
TASK_ID="a1b2c3d4-e5f6-7890-1234-567890abcdef"
while true; do
RESPONSE=$(curl -s "https://quicktranscript.app/api/transcriptions/$TASK_ID" \
-H "X-API-Key: $QUICKTRANSCRIPT_API_KEY")
STATUS=$(echo "$RESPONSE" | jq -r .status)
echo "status: $STATUS"
case "$STATUS" in
complete) echo "$RESPONSE" | jq -r '.result.transcription[] | "\(.timestamp) \(.text)"'; break ;;
failed) echo "$RESPONSE" | jq -r .error_message; exit 1 ;;
esac
sleep 5
done
8.5 Python (httpx): complete client
import mimetypes
import os
import time
from pathlib import Path
import httpx
BASE_URL = "https://quicktranscript.app"
TERMINAL_STATUSES = {"complete", "failed"}
class QuickTranscriptError(RuntimeError):
pass
def _client() -> httpx.Client:
api_key = os.environ["QUICKTRANSCRIPT_API_KEY"]
return httpx.Client(
base_url=BASE_URL, headers={"X-API-Key": api_key}, timeout=120
)
def _check(response: httpx.Response) -> dict:
if response.is_error:
detail = response.json().get("detail", response.text)
raise QuickTranscriptError(f"{response.status_code}: {detail}")
return response.json()
def submit_file(client: httpx.Client, path: Path, summarize: bool = True) -> str:
content_type = mimetypes.guess_type(path.name)[0] or "audio/mpeg"
with path.open("rb") as media:
response = client.post(
"/api/transcriptions",
params={"summarize": str(summarize).lower()},
files={"file": (path.name, media, content_type)},
)
return _check(response)["task_id"]
def submit_url(client: httpx.Client, url: str, summarize: bool = True) -> str:
response = client.post(
"/api/transcriptions",
params={"summarize": str(summarize).lower()},
data={"file_url": url}, # form field, NOT json=
)
return _check(response)["task_id"]
def wait_for_result(
client: httpx.Client, task_id: str, interval: float = 5, timeout: float = 3600
) -> dict:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
job = _check(client.get(f"/api/transcriptions/{task_id}"))
if job["status"] == "complete":
return job["result"]
if job["status"] == "failed":
raise QuickTranscriptError(f"Job {task_id} failed: {job['error_message']}")
time.sleep(interval)
raise TimeoutError(f"Job {task_id} did not finish within {timeout}s")
if __name__ == "__main__":
with _client() as client:
print("credits:", _check(client.get("/api/credits"))["credits"])
task_id = submit_file(client, Path("meeting.mp3"))
result = wait_for_result(client, task_id)
full_text = "\n".join(
f"{segment['timestamp']} {segment['text']}"
for segment in result["transcription"] or []
)
print(full_text)
print(result["summary_html"])
8.6 JavaScript (Node.js 20+, built-in fetch)
import { openAsBlob } from "node:fs";
import { basename } from "node:path";
const BASE_URL = "https://quicktranscript.app";
const headers = { "X-API-Key": process.env.QUICKTRANSCRIPT_API_KEY };
async function check(response) {
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(body.detail)}`);
return body;
}
export async function submitFile(path, { summarize = true, type = "audio/mpeg" } = {}) {
const form = new FormData();
form.append("file", await openAsBlob(path, { type }), basename(path));
const response = await fetch(`${BASE_URL}/api/transcriptions?summarize=${summarize}`, {
method: "POST",
headers, // do NOT set Content-Type; fetch adds the multipart boundary
body: form,
});
return (await check(response)).task_id;
}
export async function submitUrl(url, { summarize = true } = {}) {
const form = new FormData();
form.append("file_url", url);
const response = await fetch(`${BASE_URL}/api/transcriptions?summarize=${summarize}`, {
method: "POST",
headers,
body: form,
});
return (await check(response)).task_id;
}
export async function waitForResult(taskId, { intervalMs = 5000, timeoutMs = 3_600_000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const job = await check(await fetch(`${BASE_URL}/api/transcriptions/${taskId}`, { headers }));
if (job.status === "complete") return job.result;
if (job.status === "failed") throw new Error(`Job ${taskId} failed: ${job.error_message}`);
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error(`Job ${taskId} timed out`);
}
const taskId = await submitFile("meeting.mp3");
const result = await waitForResult(taskId);
console.log(result.transcription.map((s) => `${s.timestamp} ${s.text}`).join("\n"));
8.7 Python: polling trigger for new completed jobs (Zapier-style)
import os
import time
import httpx
with httpx.Client(
base_url="https://quicktranscript.app",
headers={"X-API-Key": os.environ["QUICKTRANSCRIPT_API_KEY"]},
timeout=30,
) as client:
while True:
response = client.get("/api/transcriptions/completed", params={"limit": 50})
response.raise_for_status()
for job in response.json(): # each job is returned exactly once
print(job["task_id"], job["original_filename"])
# Persist the job before doing anything else: it will not be returned again.
time.sleep(60)
9. Remote MCP server
QuickTranscript also runs a hosted MCP (Model Context Protocol) server. MCP clients and agents can call it as tools instead of writing REST code. It uses the same API keys, credits, URL downloader, and job records as the REST API.
| Setting | Value |
|---|---|
| URL | https://quicktranscript.app/mcp/. The trailing slash is required. |
| Transport | Streamable HTTP (stateless, JSON responses) |
| Authentication | Your API key on every HTTP request, including initialize and tools/list: header X-API-Key: vdz_…, or Authorization: Bearer vdz_… for clients that can only send bearer tokens. If both are sent, X-API-Key wins. OAuth connectors send an OAuth access token (Authorization: Bearer qto_at_…) instead; see OAuth connections. |
| Tool schemas | Call tools/list. /docs and /openapi.json describe the REST API only. |
vdz_…) in the X-API-Key header or as
Authorization: Bearer, or an OAuth access token (qto_at_…) as Authorization: Bearer.
It does not accept Firebase tokens, cookies, or keys in the query string. Use an MCP SDK or client for the JSON-RPC
protocol; do not hand-build MCP requests.
Client compatibility
| Client | Works? | How to connect |
|---|---|---|
| Claude Code | Yes | claude mcp add --transport http quicktranscript https://quicktranscript.app/mcp/ --header "X-API-Key: $QUICKTRANSCRIPT_API_KEY" |
| Claude API (MCP connector) | Yes | Set url to the MCP endpoint and authorization_token to your API key. It is sent as a bearer token. |
| OpenAI Responses API / Agents SDK | Yes | MCP tool with server_url set to the endpoint and headers: {"X-API-Key": "…"}. |
| Cursor, VS Code, and other clients with remote-server header settings | Yes | Add the URL and an X-API-Key header in the client's MCP server settings. |
| MCP Python/TypeScript SDKs | Yes | See 9.4. |
| claude.ai custom connectors | Yes, with OAuth | Go to Customize > Connectors, click +, then Add custom connector. Enter https://quicktranscript.app/mcp/ and click Add. Leave the OAuth client ID and secret empty; Claude registers itself. Connect, then approve on the QuickTranscript page. On Team and Enterprise plans an Owner adds the connector under Organization settings > Connectors, then members click Connect. |
| ChatGPT connectors (developer mode) | Yes, with OAuth | Turn on Developer mode under Settings > Security and login. On the ChatGPT Plugins page, click the plus button and create an app with the URL https://quicktranscript.app/mcp/ and OAuth authentication. ChatGPT registers itself; approve on the QuickTranscript page. |
Client capabilities and menu names change; check your client's documentation for how it sends custom headers or bearer tokens, or how it adds remote MCP servers (Claude, ChatGPT). Custom connector availability depends on your plan, workspace, and admin policies. QuickTranscript is not a vendor-verified or directory-listed connector.
OAuth connections (claude.ai, ChatGPT)
Clients that only support OAuth use the same URL, https://quicktranscript.app/mcp/. The client
discovers the server's OAuth settings, registers itself, and opens a QuickTranscript approval page in your browser:
- The page shows the client's name, which the client declares itself and is not verified, and the address it returns to. Check both.
- Paste an existing QuickTranscript API key (create one at /api-keys) and click Approve, or click Deny. You don't sign in with your QuickTranscript account on this page.
- The client then calls the MCP tools with an OAuth access token. The key itself is never sent to the client.
Approving lets the client, on behalf of the key's account:
- submit media-URL transcriptions, which spend your credits;
- read the status and results of your account's tasks;
- read your credit balance.
Access tokens last 1 hour and the client refreshes them automatically. Refresh tokens rotate on every use; reusing an old refresh token revokes the whole connection. A connection expires 30 days after approval; then reconnect and approve again.
To disconnect, revoke or delete the API key at /api-keys. Every OAuth connection approved
with that key stops working immediately. Create a dedicated API key for each connector so you can disconnect one
without affecting your other integrations. Clients can also revoke their own connection at /revoke.
The vendor connects from its own cloud, not from your device, so the MCP server must be reachable over public
HTTPS. The hosted server at https://quicktranscript.app/mcp/ meets this requirement.
| Item | Value |
|---|---|
| Protected resource metadata | GET https://quicktranscript.app/.well-known/oauth-protected-resource (also served with the /mcp and /mcp/ suffixes). Resource https://quicktranscript.app/mcp/, authorization server https://quicktranscript.app, scope mcp. |
| Authorization server metadata | GET https://quicktranscript.app/.well-known/oauth-authorization-server |
| Endpoints | /authorize, /token, /register (Dynamic Client Registration, RFC 7591), /revoke, all on https://quicktranscript.app |
| Flow | Response type code; grants authorization_code and refresh_token; PKCE S256 only; scope mcp. If sent, resource must be https://quicktranscript.app/mcp/. |
| Client authentication | none (public client with PKCE), client_secret_post, client_secret_basic. No OpenID Connect, Client ID Metadata Documents, or client_credentials grant. ChatGPT uses Dynamic Client Registration. |
| 401 challenge | Unauthenticated MCP requests return WWW-Authenticate: Bearer resource_metadata="https://quicktranscript.app/.well-known/oauth-protected-resource/mcp/", scope="mcp" |
| Rate limits (per hour) | Client registration: 10 per client IP. Approval page submissions: 20 per browser. /token and /revoke: 120 successful requests per client, 120 failed requests per client IP. Over the limit: 429 with Retry-After. |
| Errors | Standard OAuth {"error": "…"} bodies. Storage outage: 503 {"error": "temporarily_unavailable"}; retry later. |
9.1 Tools
| Tool | Input | Success result | Notes |
|---|---|---|---|
transcribe_url |
file_url (string, required): a publicly reachable audio or video file URL. Blank or whitespace-only values are rejected.summarize (bool, default true).
|
{"task_id": "<uuid>", "status": "pending"} |
Submits a job and returns immediately. Consumes credits exactly like POST /api/transcriptions
(see section 6). Generates a summary by default; with summarize=false the
completed result has summary_html: null. Not idempotent: every successful call creates a
separate billable job, so do not retry a submission automatically.
|
get_transcription_status |
task_id (string, required): the ID returned by transcribe_url or by REST POST /api/transcriptions. |
The same status object as GET /api/transcriptions/{task_id}: task_id, status, status_message, error_message, error_type, result. |
Read-only. Poll every 5–10 seconds until status is complete or failed.
At complete, result holds a result object
(id, task_id, original_filename, completed_at,
duration_seconds, transcription, summary_html,
final_credit_cost, total_tokens). A failed job is a successful tool result with
status: "failed" and user-facing error_message and error_type.
|
get_credit_balance |
None. | {"credits": 12} |
Read-only. Doesn't spend credits. Accounts without a balance return 0. |
Successful tool results carry the JSON object in structuredContent. The size limit is the same as
the REST API: 500 MB. Larger downloads fail with a 413 tool error.
9.2 Limitations
- Only public audio or video file URLs are accepted. Share links from Google Drive, Dropbox, and OneDrive work as in REST; private or internal network addresses are rejected.
- No local file upload through MCP, and no YouTube web page ingestion. The URL must point to a media file.
- The MCP client must send your API key as a custom
X-API-Keyheader or as a bearer token on every request, or connect with OAuth. - To transcribe a local file, upload it with REST (
POST /api/transcriptionsas multipart, see example 8.2), then poll the returnedtask_idwith the MCP toolget_transcription_status.
9.3 Errors
MCP failures happen at two levels. HTTP-level failures reject the request before any MCP message is processed:
| HTTP status | Meaning | What the client should do |
|---|---|---|
| 401 | {"detail": "Invalid or missing API key."}: the key or OAuth access token is missing, malformed, expired, revoked, or unknown (in X-API-Key or Authorization: Bearer). OAuth tokens also fail once their source API key is revoked. Returned even for initialize and tools/list, with the WWW-Authenticate challenge shown in OAuth connections. | Key clients: ask the user for a valid key; don't retry. OAuth clients: refresh the token, or reconnect. |
| 503 | {"detail": "Authentication service unavailable."} | Retry later with backoff. |
| 421 | The Host header is not allowed by the server. | Use the exact URL https://quicktranscript.app/mcp/. |
| 403 | The request has an Origin header that the server does not allow. | Call the server from a backend or MCP client, not from a web page on another origin. |
Tool failures are normal MCP tool results with isError: true. Their text content is prefixed by the
MCP SDK, for example:
Error executing tool transcribe_url: 402: Insufficient credits. Need 1, have 0.
The stable part is <status_code>: <detail> at the end of the text. The codes match the REST
error table:
| Code | Meaning | What the client should do |
|---|---|---|
| 400 | Invalid input, or the URL could not be downloaded or is not allowed. | Fix the input. Show the detail to the user. Don't retry unchanged. |
| 402 | Insufficient credits. Need N, have M. | Tell the user to buy credits at quicktranscript.app. |
| 403 | Access denied. The task belongs to another account. | Check the task_id and the key. |
| 404 | Task not found. | Check the task_id. |
| 413 | File exceeds 500 MB. | Compress or split the file, then resubmit. |
| 500 | Internal server error. | Retry read-only tools with backoff. Don't resubmit transcribe_url automatically; ask the user first, because a new call creates a new billable job. |
A job that fails during processing is not a tool error: get_transcription_status returns it with status: "failed".
9.4 Python (official MCP SDK): complete client
Install the client dependencies in your uv project with uv add 'mcp>=1.28.1,<2' httpx. Set
QUICKTRANSCRIPT_API_KEY to your API key and QUICKTRANSCRIPT_MEDIA_URL to a public media
file URL, then run the script with uv run python quicktranscript_mcp.py. The API key header is set on
the httpx.AsyncClient passed to streamable_http_client.
import asyncio
import os
import time
import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
MCP_URL = "https://quicktranscript.app/mcp/"
def tool_data(result):
if result.isError:
message = result.content[0].text if result.content else "unknown error"
raise RuntimeError(message)
return result.structuredContent
async def main():
headers = {"X-API-Key": os.environ["QUICKTRANSCRIPT_API_KEY"]}
async with httpx.AsyncClient(headers=headers, timeout=120) as client:
async with streamable_http_client(MCP_URL, http_client=client) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print("Tools:", [tool.name for tool in tools.tools])
credits = tool_data(await session.call_tool("get_credit_balance", {}))
print("Credits:", credits["credits"])
job = tool_data(await session.call_tool(
"transcribe_url",
{"file_url": os.environ["QUICKTRANSCRIPT_MEDIA_URL"], "summarize": True},
))
task_id = job["task_id"]
print("Task:", task_id)
deadline = time.monotonic() + 600
while True:
status = tool_data(await session.call_tool(
"get_transcription_status", {"task_id": task_id}
))
if status["status"] in ("complete", "failed"):
break
if time.monotonic() > deadline:
raise TimeoutError(f"Task {task_id} still {status['status']}")
await asyncio.sleep(5)
if status["status"] == "failed":
print("Failed:", status["error_type"], status["error_message"])
else:
result = status["result"]
text = " ".join(seg["text"] for seg in result["transcription"] or [])
print("Transcript:", text[:500])
print("Summary HTML present:", result["summary_html"] is not None)
asyncio.run(main())
Other MCP clients can connect if they support Streamable HTTP and custom request headers; check your
client's documentation for how to set X-API-Key.
10. Short prompt for another agent
Give this summary to another agent that needs to integrate:
QuickTranscript (https://quicktranscript.app). Auth for REST and MCP: header X-API-Key: vdz_… (from env, never hard-code).
REST:
1. POST /api/transcriptions with form fields (multipart for files) and exactly one of: file (audio/* or video/* content type)
or file_url (public URL form field; JSON bodies are ignored). Optional query summarize=false. Returns 202 {task_id}.
2. Poll GET /api/transcriptions/{task_id} every 5-10 s until status is "complete" or "failed".
3. On complete, read result.transcription (list of {timestamp "[MM:SS]"/"[HH:MM:SS]", text}) and result.summary_html (HTML or null).
Errors: {"detail": ...}; 401 bad key, 402 no credits, 413 file > 500 MB, 400 bad input, 404/403 unknown or foreign task.
Balance: GET /api/credits. Key check: GET /api/auth/test. Schema: /openapi.json (REST only).
MCP: https://quicktranscript.app/mcp/ (trailing slash), Streamable HTTP, API key on every request as X-API-Key or Authorization: Bearer.
OAuth-only clients (claude.ai custom connectors, ChatGPT connectors) use the same URL: OAuth 2.1 + PKCE S256 + Dynamic Client Registration,
scope mcp, discovery at /.well-known/oauth-protected-resource; the user approves by pasting an API key. Access token 1 h, rotating refresh,
reconnect after 30 days; revoking the source API key disconnects. Also works with Claude Code, Claude API, OpenAI Responses API, MCP SDKs.
Tools (schemas via tools/list): transcribe_url(file_url, summarize=true) -> {task_id, status}; public media URLs only, spends credits,
don't auto-retry. get_transcription_status(task_id) -> same status object as REST; poll every 5-10 s. get_credit_balance() -> {credits}.
Tool errors: isError=true, text ends with "<status_code>: <detail>". HTTP 401 = bad key or token. Local files: REST upload, then MCP poll.