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

MethodPathPurposeSuccess response
GET/api/pingHealth check{ ok: true }
POST/api/parcel/textSend a text parcel{ code, url, expiresAt }
POST/api/parcel/file/initDeclare a pending file{ fileId }
PUT/api/parcel/file/:fileIdUpload raw bytes{ ok: true }
POST/api/parcel/sealSeal files into one parcel{ code, url, expiresAt }
GET/api/parcel/:codePick up and consume one claim{ kind, text?, files?, expiresAt, remainingClaims }
GET/d/:fileId/:tokenDownload one fileFile 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.

  1. Declare it POST /api/parcel/file/init with {name, size, mime} to get a fileId. size must equal the exact byte count.
  2. 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'.
  3. Seal POST /api/parcel/seal with {fileIds:[...]} to get the 6-digit code. Send every fileId for a multi-file parcel.

Errors

HTTPerrorWhen it happens
400bad_requestInvalid JSON, a missing field, or an upload that does not match the declared size
400too_largeText over 64KB, a file over 100MB, or a parcel over 20 files / 200MB
404not_foundUnknown or exhausted code (same message as a wrong code)
404no_initThe PUT target is missing, already uploaded, or not pending
404unknown_fileA fileId or its stored object is missing during seal
409already_sealedThe file already belongs to another parcel
410expiredThe code exists but has expired (only inside the 10-minute grace window)
429rate_limitedPer-IP limit hit; wait and retry
429turnstile_requiredRepeated pickup failures from this IP; retry with a Turnstile token
503busyA global quota or the code pool is temporarily exhausted; retry later

Related

Back to home