# SDKs

There is no SDK to install — and that's deliberate. CurlyFlies is one HTTP call, so every language's standard HTTP client *is* the SDK. This page gives you copy-paste patterns for Python and JavaScript. (Official `pip install curlyflies` / `npm install curlyflies` packages are on the roadmap, not yet released.)

All examples assume:

```
CURLY_KEY = your API key (curly_live_…)
Base URL  = https://curlyflies.com/v1
Auth      = Authorization: Bearer {key} header on every request
```

---

## Python

Uses [`requests`](https://pypi.org/project/requests/) (`pip install requests`). `httpx` works identically.

### Upload a file

```python
import os
import requests

CURLY_KEY = os.environ["CURLY_KEY"]
BASE = "https://curlyflies.com/v1"
HEADERS = {"Authorization": f"Bearer {CURLY_KEY}"}

def upload(path: str, ttl_seconds: int = 86400) -> dict:
    with open(path, "rb") as f:
        r = requests.post(
            f"{BASE}/upload",
            headers=HEADERS,
            files={"file": f},
            data={"ttl_seconds": ttl_seconds},
        )
    r.raise_for_status()
    return r.json()

result = upload("output.png")
print(result["url"])         # https://curlyflies.com/f/x7k2p9.png
print(result["expires_at"])  # 2026-06-02T09:41:00Z
```

### Upload base64 content (no file on disk)

```python
import base64

def upload_bytes(data: bytes, filename: str = "file.png") -> dict:
    r = requests.post(
        f"{BASE}/upload",
        headers=HEADERS,
        data={
            "content_base64": base64.b64encode(data).decode(),
            "filename": filename,
        },
    )
    r.raise_for_status()
    return r.json()
```

### Re-host a remote URL

```python
def upload_from_url(url: str, ttl_seconds: int = 86400) -> dict:
    r = requests.post(
        f"{BASE}/upload-url",
        headers=HEADERS,
        json={"url": url, "ttl_seconds": ttl_seconds},
    )
    r.raise_for_status()
    return r.json()

hosted = upload_from_url("https://oaidalleapiprodscus.blob.core.windows.net/private/gen/abc123.png?se=...")
```

### Check, then use, then delete

```python
def file_info(file_id: str) -> dict:
    r = requests.get(f"{BASE}/files/{file_id}", headers=HEADERS)
    if r.status_code == 404:
        return {"exists": False}
    r.raise_for_status()
    return r.json()

def delete_file(file_id: str) -> bool:
    r = requests.delete(f"{BASE}/files/{file_id}", headers=HEADERS)
    r.raise_for_status()
    return r.json()["deleted"]
```

### Handle rate limits

```python
import time

def upload_with_retry(path: str, retries: int = 3) -> dict:
    for attempt in range(retries):
        with open(path, "rb") as f:
            r = requests.post(f"{BASE}/upload", headers=HEADERS, files={"file": f})
        if r.status_code == 429:
            wait = int(r.headers.get("Retry-After", 2 ** attempt))
            time.sleep(wait)
            continue
        r.raise_for_status()
        return r.json()
    raise RuntimeError("rate limited after retries")
```

---

## JavaScript / TypeScript

Uses the built-in `fetch` (Node 18+, Deno, Bun, browsers). No dependencies.

### Upload a file (Node)

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

const CURLY_KEY = process.env.CURLY_KEY;
const BASE = "https://curlyflies.com/v1";

async function upload(path, ttlSeconds = 86400) {
  const bytes = await readFile(path);
  const form = new FormData();
  form.append("file", new Blob([bytes]), path.split("/").pop());
  form.append("ttl_seconds", String(ttlSeconds));

  const res = await fetch(`${BASE}/upload`, {
    method: "POST",
    headers: { Authorization: `Bearer ${CURLY_KEY}` },
    body: form,
  });
  if (!res.ok) throw new Error(`upload failed: ${res.status}`);
  return res.json();
}

const { url, expires_at } = await upload("output.png");
console.log(url, expires_at);
```

### Upload from the browser (e.g. an `<input type="file">`)

```js
async function uploadFile(file, apiKey) {
  const form = new FormData();
  form.append("file", file);
  const res = await fetch("https://curlyflies.com/v1/upload", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
    body: form,
  });
  if (!res.ok) throw new Error(`upload failed: ${res.status}`);
  return res.json(); // { url, expires_at, file_id, ... }
}
```

### Re-host a remote URL

```js
async function uploadFromUrl(remoteUrl, ttlSeconds = 86400) {
  const res = await fetch(`${BASE}/upload-url`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${CURLY_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url: remoteUrl, ttl_seconds: ttlSeconds }),
  });
  if (!res.ok) throw new Error(`re-host failed: ${res.status}`);
  return res.json();
}
```

### Info, delete, usage

```js
const info   = (id) => fetch(`${BASE}/files/${id}`, { headers: auth() }).then(jsonOr404);
const remove = (id) => fetch(`${BASE}/files/${id}`, { method: "DELETE", headers: auth() }).then(r => r.json());
const usage  = ()   => fetch(`${BASE}/usage`, { headers: auth() }).then(r => r.json());

function auth() { return { Authorization: `Bearer ${CURLY_KEY}` }; }
async function jsonOr404(r) { return r.status === 404 ? { exists: false } : r.json(); }
```

---

## Patterns worth copying

**Always read `expires_at`.** Every response containing a URL includes it. If your workflow can pause (queues, human review), call `GET /v1/files/{id}` before reusing an old URL.

**Re-host instead of proxying.** If an upstream model gives you a signed URL, call `/upload-url` immediately — signed URLs often expire in seconds.

**Clean up on success.** Files expire on their own, but calling `DELETE` after the downstream API confirms receipt keeps your usage tidy and is good agent hygiene.

**Let agents provision themselves.** `POST /v1/agent/signup` issues a free-plan key with no human involved — see the [API reference](/docs/api.md#agent-signup).

## MCP instead of HTTP

If your agent runs in an MCP-capable client (Claude Desktop, Cursor, etc.), skip the code entirely and [connect the MCP server](/docs/mcp.md) — the agent gets `upload_file`, `upload_from_url`, `get_file_info`, and `delete_file` as native tools.
