Docs
Developer API
Upload, manage and share files on Relay from your own code.
Quickstart
- Create a key in the API dashboard.
- Upload a file:
curl -X POST https://relay.xstlo.com/api/files/upload \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@report.pdf" - Use
data.idfor later calls anddata.urlto download it. Done.
End-to-end example
Upload a file, list your files, rename the upload, then delete it. It checks success and prints the code and requestId when a call fails.
KEY=YOUR_API_KEY
# 1. Upload (keep data.id)
curl -X POST https://relay.xstlo.com/api/files/upload \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -F "file=@report.pdf"
# 2. List
curl "https://relay.xstlo.com/api/files/list?limit=5" -H "Authorization: Bearer $KEY"
# 3. Rename
curl -X PATCH https://relay.xstlo.com/api/files/FILE_ID \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"name": "renamed.pdf"}'
# 4. Delete
curl -X DELETE "https://relay.xstlo.com/api/files/delete?fileId=FILE_ID" -H "Authorization: Bearer $KEY"Basics
| Base URL | https://relay.xstlo.com/api |
| Auth | Authorization: Bearer YOUR_API_KEY. The old ?api_key= query parameter still works but is deprecated: keys in URLs leak into logs and history. |
| Bodies | JSON, or multipart/form-data for file uploads |
| Responses | JSON with success: true and data, or success: false and error |
| CORS | Access-Control-Allow-Origin: * on every endpoint here, so browser code can call it directly |
| Request ID | X-Request-Id on every response. Include it when you contact support. |
| OpenAPI | /openapi.json: the whole API as an OpenAPI 3.1 spec, for client generators and tools like Postman. |
Limits
| Anonymous | Free key | Plus key | |
|---|---|---|---|
| Per file | 1GB | 1GB | 8GB |
| Storage | 2GB per IP | 2GB per account | 50GB per account |
| Requests | 20/hr uploads, 10/hr URL uploads (per IP) | 500/hr per key | 5,000/hr per key |
| API keys | — | 2 | 30 |
| File expiry | 15 days (7 for URL uploads) | Never | While subscribed, then 15 days |
- Storage is shared by every key on an account. On Plus it is the same 50GB pool as your web uploads. Check usage with .
- Anonymous means no key. Upload, URL upload, download and file-info work without one. A request with an invalid or rate-limited key fails; it never falls back to anonymous.
- Over a limit:
413(file too large),507(storage full) or429(rate limit). Size checks happen before any data is written. - On top of per-key limits, each network gets 1,200 API requests a minute across all endpoints. Past that, requests get
429withRetry-After.
Rate-limit headers
X-RateLimit-Limit: 500 # requests allowed in this window
X-RateLimit-Remaining: 499 # requests left
X-RateLimit-Reset: 1791234567 # Unix seconds when the window resets
Retry-After: 1834 # only on 429: seconds to waitErrors
Branch on code, not on the message. Messages can change; codes don't. Some errors add fields (for example resetAt on an anonymous 429).
{
"success": false,
"error": "File not found",
"code": "file_not_found",
"requestId": "req_5f2c9a1e7b3d4c8a9e0f1a2b"
}| Status | code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid |
| 400 | invalid_url | A URL is malformed, not http(s), or points to a private address |
| 401 | unauthorized | Not authenticated |
| 401 | missing_api_key | An API key is required but none was sent |
| 401 | invalid_api_key | The API key is unknown, disabled, or expired; the message says which |
| 403 | permission_denied | The API key lacks the permission this call needs |
| 403 | subscription_ended | The key's Relay Plus subscription has ended, so it can't upload |
| 403 | forbidden | Not allowed (for example, another account's file or folder) |
| 404 | not_found | Not found |
| 404 | file_not_found | No file with that id that you can access |
| 404 | folder_not_found | No folder with that id that you own |
| 409 | conflict | Conflicts with the current state |
| 409 | idempotency_key_in_use | A request with this Idempotency-Key is still running |
| 413 | file_too_large | The file is over your per-upload size limit |
| 422 | unprocessable | Well-formed but can't be processed |
| 422 | idempotency_key_reused | This Idempotency-Key was already used for a different request |
| 422 | unsupported_source | The remote source can't be used with the multipart flow |
| 429 | rate_limited | Too many requests; see Retry-After |
| 500 | internal_error | Something went wrong on our side |
| 502 | upstream_error | Storage or the remote source failed |
| 507 | storage_quota_exceeded | The upload would exceed your storage quota |
Safe retries (Idempotency-Key)
Upload endpoints accept an Idempotency-Key header: any unique 1–255 character printable ASCII string, such as a UUID. If a request times out, resend it with the same key. When the first attempt already succeeded you get its original response, with Idempotent-Replayed: true, instead of a second copy of the file.
- Works on
/api/files/upload,/api/files/remote-upload,/api/files/multipart/completeand/api/files/remote-upload/multipart/complete. - Successful responses are kept 24 hours. Failed requests aren't kept, so you can retry them with the same key.
- Same key, different request:
422 idempotency_key_reused. Same key while the first is still running:409 idempotency_key_in_use. - Keys are scoped to your API key (or IP without one). Without the header, every request is a new upload.
Legacy v1 API
The older /api/v1/files endpoints still work but new integrations should use the endpoints in this reference. A v1 key reaches only its own files (d/api/{keyId}/ or d/api/{accountId}/): other files return 404, and a list prefix outside them returns 403. The image CDN (/api/v1/images) is current, not legacy.