# Presigned URLs

Let end users upload or download files directly — no API key needed.

## How it works

1. Your agent calls `POST /api/presign` to create a time-limited URL
2. You hand that URL to your end user
3. They use it with a normal HTTP client (curl, browser, fetch)
4. The URL enforces method, expiry, and use count — then self-destructs

The end user never sees your agent API key.

## Creating a presigned URL

### For downloads (your user downloads your file)

```bash
curl -X POST http://74.113.234.189:8000/api/presign \
  -H "X-API-Key: gw-YOUR-AGENT-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "GET",
    "key": "reports/summary.pdf",
    "expires_seconds": 3600,
    "max_uses": 5
  }'
```

Response:
```json
{
  "url": "http://74.113.234.189:8000/p/abc123...",
  "token": "abc123...",
  "method": "GET",
  "key": "reports/summary.pdf",
  "expires_at": "2026-07-09T17:00:00+00:00",
  "max_uses": 5
}
```

The file must already exist in your storage. If it doesn't, you'll get a 404.

### For uploads (your user sends you a file)

```bash
curl -X POST http://74.113.234.189:8000/api/presign \
  -H "X-API-Key: gw-YOUR-AGENT-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "PUT",
    "key": "user-submissions/photo-42.png",
    "expires_seconds": 7200,
    "max_uses": 1
  }'
```

The file doesn't need to exist yet — your user creates it when they upload.

## Using a presigned URL

### Download (from your end user's perspective)

```bash
curl http://74.113.234.189:8000/p/abc123... -o summary.pdf
```

No API key. No auth. Just a URL.

### Upload (from your end user's perspective)

```bash
curl -X PUT http://74.113.234.189:8000/p/abc123... \
  --data-binary @photo.png
```

## Request fields

| Field | Required | Default | Description |
|---|---|---|---|
| `method` | No | `"GET"` | What the **presigned URL** accepts. `"GET"` = download. `"PUT"` = upload. |
| `key` | **Yes** | — | File path in your agent's storage namespace |
| `expires_seconds` | No | `3600` | How long the URL lives (60 — 604800 seconds) |
| `max_uses` | No | `1` | How many times the URL works before self-destructing (1 — 100) |

## Error responses

| Code | When |
|---|---|
| `404` | File doesn't exist (for `method: "GET"`) or token not found |
| `405` | Wrong HTTP method — used PUT on a GET URL or vice versa |
| `410` | URL expired or exhausted all uses |
| `429` | Rate limit exceeded |

## Limits

- Minimum expiry: 60 seconds
- Maximum expiry: 7 days (604800 seconds)
- Maximum uses: 100
- Presigned URL creation counts toward your agent's [rate limit](rate-limiting.md)
