# RK Drive API

Read-only HTTP API for the **Radha's Kitchen** Google Drive folder. Use it to list, search and download files from scripts and tools such as Remotion pipelines, ffmpeg, n8n, Zapier or curl.

- **Base URL:** `https://radhaskitchen-drive.pages.dev/v1`
- **Access:** read-only. Only `GET` (plus `HEAD`/`OPTIONS`) is accepted; anything else returns `405`.
- **Scope:** the *Radha's Kitchen* folder and everything inside it. Every other file in the Drive returns `403 Outside the shared folder`, even if you know its ID.
- **Format:** JSON. Times are ISO 8601 UTC and sizes are in bytes.

## Quick start

1. Open the site, go to **Settings → API keys**, give the key a name (for example *Remotion pipeline*) and click **Create key**.
2. Copy the key. It starts with `rkd_` and is shown only once.
3. Call the API:

```bash
export RK_DRIVE_API_KEY=rkd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
curl -H "Authorization: Bearer $RK_DRIVE_API_KEY" https://radhaskitchen-drive.pages.dev/v1/me
```

```json
{
  "key":  { "id": "0ed4d2baf2e2", "name": "Remotion pipeline" },
  "root": { "id": "1t5jnxRsdLyFyMsSRpTuIFubQuiqpS4gb", "name": "Radha's Kitchen" },
  "access": "read-only"
}
```

## Authentication

Send the key in one of these ways, listed in order of preference:

| Method | Example |
|---|---|
| `Authorization` header | `Authorization: Bearer rkd_…` |
| `X-API-Key` header | `X-API-Key: rkd_…` |
| Query parameter | `?key=rkd_…` (only for tools that can't set headers; the key ends up in logs and history) |

- **Storage:** the site stores only a SHA-256 hash of each key. A lost key can't be recovered, so revoke it and create a new one.
- **Revoking:** click 🗑 next to the key in **Settings → API keys**. It stops working immediately.
- **Naming:** use one key per tool, so you can revoke a single tool without breaking the others.

## Endpoints

| Method & path | Purpose |
|---|---|
| `GET /v1` | Endpoint list as JSON (no key needed) |
| `GET /v1/me` | Key name and root folder |
| `GET /v1/list` | Children of one folder |
| `GET /v1/tree` | Everything under a folder, recursively, with paths |
| `GET /v1/search` | Find files by name |
| `GET /v1/resolve` | Look up a file or folder by its path |
| `GET /v1/files/{id}` | Metadata for one file |
| `GET /v1/files/{id}/download` | File bytes |
| `GET /v1/files/{id}/thumbnail` | Preview image |
| `GET /v1/files/{id}/link` | Temporary download URL that needs no key |

### GET /v1/list

Lists the direct children of a folder, with folders first and then by name.

| Param | Default | Notes |
|---|---|---|
| `folder` | `root` | Folder ID. `root` means the *Radha's Kitchen* folder. |
| `pageSize` | `200` | Maximum `1000`. |
| `pageToken` | | Pass back `nextPageToken` to get the next page. |

```bash
curl -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  "https://radhaskitchen-drive.pages.dev/v1/list?pageSize=2"
```

```json
{
  "folder": "1t5jnxRsdLyFyMsSRpTuIFubQuiqpS4gb",
  "files": [
    {
      "id": "1GCSWCe8g1aMB4VHj4qw_BUX_Udihzce-",
      "name": "01. Gul Papdichi Vadi (Posted)",
      "isFolder": true,
      "mimeType": "application/vnd.google-apps.folder",
      "modifiedTime": "2026-07-23T08:30:34.695Z",
      "createdTime": "2026-07-16T07:18:09.767Z",
      "webViewLink": "https://drive.google.com/drive/folders/1GCSWCe8g1aMB4VHj4qw_BUX_Udihzce-"
    }
  ],
  "nextPageToken": "~!!~AAfjcz…"
}
```

`nextPageToken` is `null` on the last page.

### GET /v1/tree

Walks a folder recursively and returns a flat list. Each item has a `path` relative to the folder you asked for.

| Param | Default | Notes |
|---|---|---|
| `folder` | `root` | Folder ID to start from. |
| `depth` | `20` | How many levels deep to go (1 = the folder's own children only). |
| `type` | all | `files` returns only files; `folders` returns only folders. |

```bash
curl -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  "https://radhaskitchen-drive.pages.dev/v1/tree?type=files"
```

```json
{
  "folder": "1t5jnxRsdLyFyMsSRpTuIFubQuiqpS4gb",
  "count": 1427,
  "truncated": false,
  "files": [
    {
      "id": "1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t",
      "name": "chicken sukka.mp4",
      "path": "chicken sukka.mp4",
      "size": 802325598,
      "md5": "7b4600c8ebc507d3d35514dad5be555b",
      "video": { "durationMs": 384199, "width": 1920, "height": 1080 },
      "downloadUrl": "https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/download"
    }
  ]
}
```

A single call returns at most **5,000** items. If `truncated` is `true`, call `tree` once for each sub-folder instead.

### GET /v1/search

Searches file names (a "name contains" match) inside the shared folder. Results are sorted with the most recently modified first.

| Param | Notes |
|---|---|
| `q` | Required. Text to look for in names. |

```bash
curl -G -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  --data-urlencode "q=Upma" https://radhaskitchen-drive.pages.dev/v1/search
```

Search checks the 300 newest name matches in the whole Drive and keeps those inside the shared folder. A very common word can therefore miss some files. For a complete list, use `tree` and filter it yourself.

### GET /v1/resolve

Finds a file or folder by its path below the shared folder.

| Param | Notes |
|---|---|
| `path` | For example `18. Masoorachi Aamti & Bhajaniche Vade/rename-map.csv`. Separate levels with `/`. |

```bash
curl -G -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  --data-urlencode "path=18. Masoorachi Aamti & Bhajaniche Vade/rename-map.csv" \
  https://radhaskitchen-drive.pages.dev/v1/resolve
```

Returns a [file object](#file-object), or `404 Not found: <segment>` naming the first part of the path that didn't match. A name that itself contains `/` (for example `05. Sodyacha Sheera/Upma`) can't be resolved by path; use its ID instead.

### GET /v1/files/{id}

Returns the [file object](#file-object) for one file or folder.

```bash
curl -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  https://radhaskitchen-drive.pages.dev/v1/files/17r8tin08ssD-ioN91dQc-IAJHfgMAoWG
```

```json
{
  "id": "17r8tin08ssD-ioN91dQc-IAJHfgMAoWG",
  "name": "rename-map.csv",
  "isFolder": false,
  "mimeType": "text/csv",
  "modifiedTime": "2026-08-26T12:26:24.000Z",
  "createdTime": "2026-09-08T22:44:53.084Z",
  "size": 3501,
  "md5": "0cafa0600bcea7f7d3857586c9b1fb79",
  "downloadUrl": "https://radhaskitchen-drive.pages.dev/v1/files/17r8tin08ssD-ioN91dQc-IAJHfgMAoWG/download",
  "webViewLink": "https://drive.google.com/file/d/17r8tin08ssD-ioN91dQc-IAJHfgMAoWG/view?usp=drivesdk"
}
```

### GET /v1/files/{id}/download

Streams the file's bytes. The filename is sent in `Content-Disposition`.

| Param | Notes |
|---|---|
| `format` | Only for Google Docs/Sheets/Slides/Drawings: `pdf`, `docx`, `xlsx`, `pptx`, `png`. The default is the Office format. |
| `inline` | `1` makes the browser display the file instead of saving it. |

- **Range requests** work for normal files: send `Range: bytes=0-1048575` and you get `206 Partial Content`. This lets `curl -C -`, `aria2c`, download managers and video players resume or seek.
- **Google Docs exports** are generated on the fly, so they don't support Range.
- **Folders** return `400`. Use `list` or `tree` for them.

```bash
# download, keeping the Drive filename
curl -OJ -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/download

# resume a broken download
curl -C - -o "chicken sukka.mp4" -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/download
```

To check a download, compare the file's MD5 with the `md5` field from `files/{id}`.

### GET /v1/files/{id}/thumbnail

Returns Google's preview image (JPEG or PNG) for videos, photos, PDFs and docs. Only files whose object has a `thumbnailUrl` field have one; any other file returns `404`.

| Param | Default | Notes |
|---|---|---|
| `s` | `480` | Longest side in pixels, up to `1600`. |

```bash
curl -o thumb.jpg -H "Authorization: Bearer $RK_DRIVE_API_KEY"   "https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/thumbnail?s=800"
```

### GET /v1/files/{id}/link

Creates a signed URL that downloads the file **without an API key** until it expires. Use it with tools that accept only a URL, such as ffmpeg, Remotion `staticFile` replacements, webhooks or a link sent to an editor.

| Param | Default | Notes |
|---|---|---|
| `ttl` | `3600` | Lifetime in seconds, from 60 up to 604800 (7 days). |
| `format` | | Same as `download`, for Google Docs. |

```bash
curl -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  "https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/link?ttl=86400"
```

```json
{
  "url": "https://radhaskitchen-drive.pages.dev/v1/dl/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t?exp=1791700000&sig=…",
  "expiresAt": "2026-10-11T06:26:40.000Z"
}
```

```bash
ffprobe "$(curl -s -H "Authorization: Bearer $RK_DRIVE_API_KEY" \
  https://radhaskitchen-drive.pages.dev/v1/files/<id>/link | jq -r .url)"
```

- **Who can use it:** anyone who has the link can download that one file until it expires, so share it with care.
- **Tampering:** a changed or expired link returns `403`.
- **Range requests:** supported.
- **Revoking keys:** revoking an API key does **not** cancel links that were already issued; they still run until their expiry time.

## File object

| Field | Type | Notes |
|---|---|---|
| `id` | string | Drive file ID; use it in every `/files/{id}` call. |
| `name` | string | |
| `isFolder` | boolean | |
| `mimeType` | string | Google types start with `application/vnd.google-apps.` |
| `modifiedTime`, `createdTime` | string | ISO 8601 UTC. |
| `size` | number | Bytes. Files only; `null` for Google Docs. |
| `md5` | string | Files only; absent for Google Docs. |
| `video` | object | `{ durationMs, width, height }` for videos Drive has processed. |
| `image` | object | `{ width, height }` for images. |
| `path` | string | Only in `tree` results; relative to the folder you started from. |
| `downloadUrl` | string | Files only; needs the API key. |
| `thumbnailUrl` | string | Only when Drive has a preview image; needs the API key. |
| `webViewLink` | string | Opens the item in Google Drive (needs Drive access). |

## Errors

Errors return JSON in the form `{ "error": "message" }`.

| Status | Meaning |
|---|---|
| `400` | Bad or missing parameter (for example `q is required`), or you tried to download a folder. |
| `401` | Missing or invalid API key. |
| `403` | The item is outside the shared folder, or a signed link is expired or wrong. |
| `404` | The ID or path doesn't exist, or the endpoint is unknown. |
| `405` | Not a GET request; the API is read-only. |
| `415` | A Google file type that can't be exported (for example Forms). |
| `503` | The site's Google Drive connection is down. An admin must reconnect it in Settings. |

## Code examples

**Python**: download every `.mp4` inside one episode folder:

```python
import os, requests

BASE = "https://radhaskitchen-drive.pages.dev/v1"
H = {"Authorization": f"Bearer {os.environ['RK_DRIVE_API_KEY']}"}

ep = requests.get(f"{BASE}/resolve", headers=H,
                  params={"path": "18. Masoorachi Aamti & Bhajaniche Vade"}).json()
tree = requests.get(f"{BASE}/tree", headers=H,
                    params={"folder": ep["id"], "type": "files"}).json()

for f in tree["files"]:
    if not f["name"].lower().endswith(".mp4"):
        continue
    dest = os.path.join("downloads", f["path"])
    os.makedirs(os.path.dirname(dest) or ".", exist_ok=True)
    with requests.get(f["downloadUrl"], headers=H, stream=True) as r:
        r.raise_for_status()
        with open(dest, "wb") as out:
            for chunk in r.iter_content(8 << 20):
                out.write(chunk)
    print("saved", dest, f["size"])
```

**Node.js (18+)**: list one folder:

```js
const BASE = "https://radhaskitchen-drive.pages.dev/v1";
const H = { Authorization: `Bearer ${process.env.RK_DRIVE_API_KEY}` };

const { files } = await fetch(`${BASE}/list`, { headers: H }).then(r => r.json());
for (const f of files) console.log(f.isFolder ? "[dir]" : f.size, f.name);
```

**PowerShell**: download a file:

```powershell
$h = @{ Authorization = "Bearer $env:RK_DRIVE_API_KEY" }
Invoke-WebRequest -Headers $h -OutFile "chicken sukka.mp4" `
  "https://radhaskitchen-drive.pages.dev/v1/files/1b_WGVvKPMdIrsKYQKaprFPoTmGoJR37t/download"
```

**Browser / fetch**: the API sends CORS headers (`Access-Control-Allow-Origin: *`), so a web page can call it directly. Never put a key in a public web page. Mint a signed `link` on a server and give the page that link instead.

## Limits & notes

- **Read-only:** nothing in the Drive can be changed through this API. To upload, rename or delete files, use the website.
- **Folder jail:** only the *Radha's Kitchen* folder is reachable. The folder is set in the site's configuration (`ROOT_FOLDER`), not per key.
- **`tree`:** returns at most 5,000 items per call.
- **Large files:** there's no size cap. Files stream straight from Google, and multi-GB videos are fine (use Range to resume).
- **Google quotas:** all keys share the site's Google Drive quota, so avoid hammering the API in tight loops. Cache the `tree` output and use `md5` or `modifiedTime` to see what changed.
- **Keys:** keep them in a secret store such as Doppler, not in code or git.
