Other languages
Nothing in the File Service needs a CCS library. A backend in any language gets a machine token with one HTTP call, calls a REST API with JSON bodies, and moves bytes with plain PUTs and GETs to signed URLs. This guide walks through all three for a backend that is not Node.js.
-
Get a machine token. A
client_credentialsrequest with HTTP Basic authentication, the scope and the resource identifier:Terminal window curl -s -X POST "https://auth.centralcodestation.com/oauth/token" -u "$CCS_FILES_CLIENT_ID:$CCS_FILES_CLIENT_SECRET" \--data-urlencode "grant_type=client_credentials" \--data-urlencode "scope=files:manage" \--data-urlencode "resource=https://files.centralcodestation.com/api"# {"access_token":"eyJ…","token_type":"Bearer","expires_in":3600,"scope":"files:manage"}Cache the token until shortly before
expires_inruns out, share one pending request between threads, and on a 401 from the API get a new one and replay once. -
Generate a client from the OpenAPI document, or write the few calls you need by hand. The OpenAPI document page has generator commands for Java, C#, Python, Go and TypeScript types, and the pitfalls (snake_case on the wire, the error enum is open, the server URL is the host).
-
Create an upload ticket with
POST P/uploads:Terminal window curl -s -X POST "$FILES_API_URL/tenants/$CCS_TENANT_ID/applications/$CCS_APPLICATION_ID/uploads" \-H "Authorization: Bearer $FILES_TOKEN" -H "Content-Type: application/json" \-H "Idempotency-Key: 6f1c2e0a-7d0e-4b8e-9a51-0c3f1e5d2b77" \-d '{"name":"IMG_5913.jpg","size":4194304,"mime_type":"image/jpeg","owner_id":"user-ana","external_ref":"post:123","visibility":"application"}'# {"upload_id":"01J9…","file_id":"01J9…","version_no":1,"status":"open","method":"put","url":"https://…","headers":{"Content-Type":"image/jpeg"},"expires_at":"…"} -
PUT the bytes to
urlwith exactly the ticket’sheaders, and nothing else (noAuthorization):Terminal window curl -s -X PUT "$TICKET_URL" -H "Content-Type: image/jpeg" --data-binary @IMG_5913.jpgFor
method: "multipart", PUT eachpart_sizeslice to its part URL and keep every response’sETag. More part URLs come fromPOST P/uploads/:upload_id/parts. -
Complete with
POST P/uploads/:upload_id/complete, with{ "parts": [{ "part_number": 1, "etag": "\"…\"" }, …] }for multipart or an empty body for a single PUT. The answer is the file; store itsfile_id. -
Mint URLs when you render:
POST P/files/urlsfor a page,GET P/files/:file_id/downloadfor one file.
The full rules (lifetimes, sizes, resume, failures) are in the upload protocol reference.
What the Node client does that you must do yourself
Section titled “What the Node client does that you must do yourself”| Concern | Do |
|---|---|
| Token cache | Renew before expiry; one request at a time; replay once on 401 |
| Retries | Retry GET, PUT, PATCH and DELETE on network errors and 5xx (not 501) with back-off. Retry a POST that creates something only with the same Idempotency-Key |
| 429 | Wait the Retry-After seconds (also in the body as retry_after) |
| Optimistic concurrency | Send the item’s revision as If-Match; on 412 revision_mismatch read it again |
| Errors | Branch on the body’s error field, never on message. Decode it as a string: new codes are not a breaking change |
| Paging | Pass next_cursor as after until it is null; limit up to 500 on listings |
| Webhook signatures | Verify X-Files-Signature over the raw body before you parse it: the algorithm is on Webhooks |
| Waiting for processing | Poll the file until no processing step is pending, or use a webhook for file.ready. A step that failed with notify done did not stop processing; see Processing status |