REST API
cpcp's HTTP API shares the same path as the website: anonymous, rate-limited, and parcels expire after one hour. Prefer MCP for agent integrations; use REST when you need full control of the request.
Boundaries first
- Up to 64KB of text; 100MB per file; at most 20 files and 200MB per parcel.
- Parcels expire after one hour and can be claimed at most 10 times. No account is needed; create MCP tokens at /en/keys.
- Endpoints are rate-limited per IP. Handle 429 with backoff; do not retry in parallel.
- Except for uploads, requests and responses are JSON. Errors use { error, message? }.
Endpoints
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /api/ping | Health check | { ok: true } |
| POST | /api/parcel/text | Send a text parcel | { code, url, expiresAt } |
| POST | /api/parcel/file/init | Declare a pending file | { fileId } |
| PUT | /api/parcel/file/:fileId | Upload raw bytes | { ok: true } |
| POST | /api/parcel/seal | Seal files into one parcel | { code, url, expiresAt } |
| GET | /api/parcel/:code | Pick up and consume one claim | { kind, text?, files?, expiresAt, remainingClaims } |
| GET | /d/:fileId/:token | Download one file | File stream (attachment) |
Send a text parcel
Body: {"content":"hello"}; an optional ttl (seconds, default 3600) is accepted.
curl -s https://<this-site>/api/parcel/text -H 'content-type: application/json' -d '{"content":"hello"}'
Response: {"code":"123456","url":"https://<this-site>/p/123456","expiresAt":1760000000000}. Share the code or URL with the recipient.
Send a file
The uploaded byte count must match the size declared at init, or the PUT returns 400 bad_request. A repeated PUT after success returns 404 no_init and never overwrites the stored object.
When pickup returns kind file, each files entry has name, size, and downloadUrl. downloadUrl is a signed relative path that only works on this site and expires with the parcel.
- Declare it POST /api/parcel/file/init with {name, size, mime} to get a fileId. size must equal the exact byte count.
- Upload the bytes PUT /api/parcel/file/<fileId> with the raw body: curl -X PUT --data-binary @note.txt -H 'content-type: application/octet-stream'.
- Seal POST /api/parcel/seal with {fileIds:[...]} to get the 6-digit code. Send every fileId for a multi-file parcel.
Errors
| HTTP | error | When it happens |
|---|---|---|
| 400 | bad_request | Invalid JSON, a missing field, or an upload that does not match the declared size |
| 400 | too_large | Text over 64KB, a file over 100MB, or a parcel over 20 files / 200MB |
| 404 | not_found | Unknown or exhausted code (same message as a wrong code) |
| 404 | no_init | The PUT target is missing, already uploaded, or not pending |
| 404 | unknown_file | A fileId or its stored object is missing during seal |
| 409 | already_sealed | The file already belongs to another parcel |
| 410 | expired | The code exists but has expired (only inside the 10-minute grace window) |
| 429 | rate_limited | Per-IP limit hit; wait and retry |
| 429 | turnstile_required | Repeated pickup failures from this IP; retry with a Turnstile token |
| 503 | busy | A global quota or the code pool is temporarily exhausted; retry later |