# REST API quickstart

Upload audio, start a job, poll, download the transcript. Base URL: `https://gimmetext.com/api/v1`. Every request needs `Authorization: Bearer gt_live_XXXX`.

> **Tip (optional):** The REST API takes audio. The upload page and AI links take any video. To send a video through the API, pull the sound out first:
>
> `ffmpeg -i input.mp4 -vn -ac 1 -c:a libopus -b:a 32k output.ogg`
>
> Then upload `output.ogg`. It's faster, much smaller, and your video stays on your computer.

## curl

```bash
KEY=gt_live_XXXX
API=https://gimmetext.com/api/v1

# 1. Create an upload
curl -s -X POST $API/uploads -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"audio/ogg","size_bytes":'$(wc -c < file.ogg)'}'
# → {"upload_id":"<uuid>.ogg","upload_url":"https://…","expires_at":"…"}

# 2. PUT the file
curl -s -X PUT "$UPLOAD_URL" -H "Content-Type: audio/ogg" --data-binary @file.ogg

# 3. Create a job (duration in seconds)
curl -s -X POST $API/jobs -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"upload_id":"'$UPLOAD_ID'","duration_seconds":600,"language":"en"}'
# → 202 {"id":"<job-id>","status":"queued"}

# 4. Poll until "done"
curl -s $API/jobs/$JOB_ID -H "Authorization: Bearer $KEY"

# 5. Download the transcript (txt, srt or json)
curl -s "$API/jobs/$JOB_ID/transcript?format=txt" -H "Authorization: Bearer $KEY"
```

## Python

```python
import os, time, requests

API = "https://gimmetext.com/api/v1"
H = {"Authorization": "Bearer gt_live_XXXX"}
path = "file.ogg"

up = requests.post(f"{API}/uploads", headers=H, json={
    "content_type": "audio/ogg", "size_bytes": os.path.getsize(path)}).json()
with open(path, "rb") as f:
    requests.put(up["upload_url"], data=f, headers={"Content-Type": "audio/ogg"}).raise_for_status()

job = requests.post(f"{API}/jobs", headers=H, json={
    "upload_id": up["upload_id"], "duration_seconds": 600}).json()

while True:
    s = requests.get(f"{API}/jobs/{job['id']}", headers=H).json()
    if s["status"] in ("done", "failed"):
        break
    time.sleep(5)

print(requests.get(f"{API}/jobs/{job['id']}/transcript?format=txt", headers=H).text)
```

## JavaScript (Node 18+)

```js
import { readFile } from "node:fs/promises";

const API = "https://gimmetext.com/api/v1";
const H = { Authorization: "Bearer gt_live_XXXX", "Content-Type": "application/json" };
const file = await readFile("file.ogg");

const up = await (await fetch(`${API}/uploads`, {
  method: "POST", headers: H,
  body: JSON.stringify({ content_type: "audio/ogg", size_bytes: file.length }),
})).json();
await fetch(up.upload_url, { method: "PUT", headers: { "Content-Type": "audio/ogg" }, body: file });

const job = await (await fetch(`${API}/jobs`, {
  method: "POST", headers: H,
  body: JSON.stringify({ upload_id: up.upload_id, duration_seconds: 600 }),
})).json();

let s;
do {
  await new Promise((r) => setTimeout(r, 5000));
  s = await (await fetch(`${API}/jobs/${job.id}`, { headers: H })).json();
} while (s.status !== "done" && s.status !== "failed");

console.log(await (await fetch(`${API}/jobs/${job.id}/transcript?format=txt`, { headers: H })).text());
```

`duration_seconds` is the audio length; it is checked against your remaining minutes. Get it with `ffprobe -v error -show_entries format=duration -of csv=p=0 file.ogg`.

## Endpoints

| Method | Path | Does |
| --- | --- | --- |
| POST | `/uploads` | `{ content_type, size_bytes }` → `{ upload_id, upload_url, expires_at }` |
| POST | `/jobs` | `{ upload_id, duration_seconds, language? }` → `202 { id, status }` |
| GET | `/jobs?limit=20` | Your jobs, newest first (no transcript text) |
| GET | `/jobs/{id}` | `status`, `progress`, `error`, `duration_seconds` |
| GET | `/jobs/{id}/transcript?format=txt\|srt\|json` | The transcript |
| DELETE | `/jobs/{id}` | Deletes the job and its transcript → `204` |
| GET | `/usage` | `plan`, `remaining_seconds`, `monthly_seconds`, `bonus_seconds`, `period_end` |

## Errors and limits

Errors look like `{ "error": { "code": "...", "message": "..." } }`.

| Status | Meaning |
| --- | --- |
| 401 | Missing or invalid API key |
| 402 | Plan isn't active, or not enough minutes left |
| 404 | Job or upload not found (or not yours) |
| 409 | Transcript not ready yet |
| 413 | File larger than your limit |
| 429 | Over the per-minute request limit (free 10, paid 60) (wait `Retry-After` seconds) or too many jobs running |

Full description: [/openapi.json](/openapi.json). See also [Limits](/docs/limits).
