# LiteFileShare API Docs (Pickup-Code Mode)

LiteFileShare (轻湖文件快递柜) is a no-signup file-sharing product by Lite Lake (轻湖): **upload a batch of files → get a 6-digit pickup code → download by code**. Files are kept for 48 hours.

- 中文版：[`/docs/api.zhs.md`](/docs/api.zhs.md)
- Web app: `https://fileshare.litelake.com/` (upload / pickup)
- This Markdown source: [`/docs/api.md`](/docs/api.md) (AI agents / crawlers should fetch this address)
- Skill install guide: [`/docs/skill.md`](/docs/skill.md) (no hand-written HTTP; upload/download in one command)

## Base URL

**`https://fileshare.litelake.com`**

This is the single public entry of the current deployment (the BFF). All examples below use it. The service is **unauthenticated** (`/api/public` zone): no API key, no login. Binary traffic (upload/download) goes straight to SC object storage via presigned URLs and **never passes through this service**; the service only signs those URLs.

## Conventions

- Except for the presigned PUT/GET, every endpoint is `POST` with `application/json`.
- Responses are always an `APIResponse` envelope: `{"code": 200, "message": "ok", "data": {...}}`.
- **HTTP status policy**: success and business errors (400/4101/4102/4103) always return HTTP 200 — use `body.code` to tell them apart. **429 and 503 use real HTTP status codes** (429 includes a `Retry-After` header).
- Send an `X-Device-Id` header (UUID, stable per client) on every request: wrong-code rate limiting counts per IP + device, so a stable device id avoids collateral blocking when several clients share one IP.
- All timestamps are RFC3339 UTC (e.g. `2026-09-12T04:00:00Z`).

### Rate limits (per IP)

| Endpoint | Limit |
|---|---|
| `POST /api/public/shares/init` | 12/hour + 30/day (plus the 30/min public-write baseline) |
| `POST /api/public/shares/refresh` | 30/minute |
| `POST /api/public/shares/complete` | 30/minute (public-write baseline) |
| `POST /api/public/pickup/verify` | 30/minute total; wrong-code failures 5 per 10 minutes (per IP + device) |
| `POST /api/public/pickup/download` | 60/minute; shares the wrong-code failure counter with verify |

### Error codes

| body.code | Meaning | HTTP |
|---|---|---|
| 200 | Success | 200 |
| 400 | Invalid parameters / over limits (see message) | 200 |
| 4101 | Pickup code invalid or expired (uniform wording, anti-enumeration) | 200 |
| 4102 | Files missing (complete: some files never arrived; data carries `missingFileIds`) | 200 |
| 4103 | Upload session deadline passed (complete later than init + 24h) | 200 |
| 429 | Rate limited (HTTP 429 + `Retry-After` seconds) | 429 |
| 503 | Storage (SC/Redis) unavailable, fail-closed | 503 |

### Constraints (defaults)

| Item | Limit |
|---|---|
| Files per batch | 20 |
| Single file size | 256 MB |
| Total batch size | 2 GB |
| Upload URL (PUT) validity | 60 minutes (call refresh to re-sign after a 403) |
| Download URL (GET) validity | 5 minutes |
| complete deadline | within 24 hours after init |
| File retention | 48 hours after complete |

## Upload flow (3 steps)

```text
init (get presigned URLs) → PUT each file straight to SC → complete (get the pickup code)
```

### Step 1: POST /api/public/shares/init — create an upload session

```bash
curl -sS -X POST https://fileshare.litelake.com/api/public/shares/init \
  -H 'Content-Type: application/json' \
  -H "X-Device-Id: $(cat ~/.cache/litelake/fileshare/device_id 2>/dev/null || uuidgen)" \
  -d '{
    "files": [
      {"filename": "report.pdf", "size": 1048576, "contentType": "application/pdf"},
      {"filename": "data.csv", "size": 2048, "contentType": "text/csv"}
    ]
  }'
```

Response (`data`):

```json
{
  "shareId": "b1c2d3e4f5a6",
  "completeDeadline": "2026-09-13T04:00:00Z",
  "files": [
    {"fileId": "f1e2d3c4b5a7", "uploadUrl": "https://sc.../20260912/b1c2.../f1e2...?X-Amz-..."},
    {"fileId": "a9b8c7d6e5f4", "uploadUrl": "https://sc.../20260912/b1c2.../a9b8...?X-Amz-..."}
  ]
}
```

| Field | Meaning |
|---|---|
| `shareId` | Upload session ID (pass back to complete) |
| `completeDeadline` | Hard deadline for complete (init + 24h); later → 4103 |
| `files[].fileId` | File ID (used by refresh and the 4102 missing list) |
| `files[].uploadUrl` | Presigned PUT URL, valid for 60 minutes |

### Step 2: PUT uploadUrl — upload each file straight to SC

For each file, send a **PUT** to its `uploadUrl` with the raw file bytes as body. **Do not add custom headers** (the signature covers only the path and query string):

```bash
curl -sS -X PUT "<uploadUrl>" \
  -H 'Content-Type: application/pdf' \
  --data-binary @report.pdf
```

- HTTP 200 means success; **403** means the URL expired (>60 min) — re-sign via step 2.5.
- For large files in Python, stream the body (see the skill's `http.client` implementation) instead of reading the whole file into memory.

### Step 2.5 (if needed): POST /api/public/shares/refresh — re-sign expired upload URLs

Call this when a PUT returns 403. Idempotent:

```bash
curl -sS -X POST https://fileshare.litelake.com/api/public/shares/refresh \
  -H 'Content-Type: application/json' \
  -d '{"shareId": "b1c2d3e4f5a6", "fileIds": ["f1e2d3c4b5a7"]}'
```

Response `data`: `{"files": [{"fileId": "...", "uploadUrl": "..."}]}` — retry the PUTs with the fresh `uploadUrl` values.

### Step 3: POST /api/public/shares/complete — finish and get the pickup code

Call after every PUT succeeded:

```bash
curl -sS -X POST https://fileshare.litelake.com/api/public/shares/complete \
  -H 'Content-Type: application/json' \
  -d '{"shareId": "b1c2d3e4f5a6"}'
```

Response (`data`):

```json
{
  "pickupCode": "K7M2XQ",
  "expiresAt": "2026-09-14T04:00:00Z",
  "fileCount": 2,
  "totalSize": 1050624
}
```

- `code=4102`: `data.missingFileIds` lists files that did not land — re-upload them and call complete again.
- `code=4103`: the session deadline passed — start over from init.
- The pickup code is 6 characters from `0-9A-Z` (minus I/L/O/U); it expires 48h after complete.

## Download flow (2 steps)

```text
verify (check code + list files) → download per file (get a presigned GET URL)
```

### POST /api/public/pickup/verify — check a code and list files

```bash
curl -sS -X POST https://fileshare.litelake.com/api/public/pickup/verify \
  -H 'Content-Type: application/json' \
  -H "X-Device-Id: $DEVICE_ID" \
  -d '{"code": "K7M2XQ"}'
```

Pickup codes are **tolerantly normalized**: paste as-is — the server strips spaces/hyphens, upper-cases, maps `O→0` and `I/L→1`.

Response (`data`):

```json
{
  "expiresAt": "2026-09-14T04:00:00Z",
  "files": [
    {"fileId": "f1e2d3c4b5a7", "filename": "report.pdf", "size": 1048576},
    {"fileId": "a9b8c7d6e5f4", "filename": "data.csv", "size": 2048}
  ]
}
```

`code=4101` means the code is invalid or expired (not distinguished, anti-enumeration).

### POST /api/public/pickup/download — sign a download URL

```bash
curl -sS -X POST https://fileshare.litelake.com/api/public/pickup/download \
  -H 'Content-Type: application/json' \
  -H "X-Device-Id: $DEVICE_ID" \
  -d '{"code": "K7M2XQ", "fileId": "f1e2d3c4b5a7"}'
```

Response (`data`): `{"url": "https://sc...?...response-content-disposition=...", "filename": "report.pdf"}`

The URL is valid for 5 minutes and the response carries `Content-Disposition: attachment` (non-ASCII filenames RFC2231-encoded). GET it to download:

```bash
curl -sS -o "report.pdf" "<url>"
```

## Usage notes

- init/verify are the most abuse-sensitive endpoints: batch files into a single init (up to 20 files) instead of calling per file.
- 5 consecutive wrong codes trigger a 10-minute ban (per IP + device); never brute-force codes.
- On 429, wait for the `Retry-After` interval before retrying.
- Expired files (48h) and stalled sessions (24h) are cleaned up server-side; no delete API needed.
