Skip to content

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.

  1. Get a machine token. A client_credentials request 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_in runs out, share one pending request between threads, and on a 401 from the API get a new one and replay once.

  2. 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).

  3. 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":"…"}
  4. PUT the bytes to url with exactly the ticket’s headers, and nothing else (no Authorization):

    Terminal window
    curl -s -X PUT "$TICKET_URL" -H "Content-Type: image/jpeg" --data-binary @IMG_5913.jpg

    For method: "multipart", PUT each part_size slice to its part URL and keep every response’s ETag. More part URLs come from POST P/uploads/:upload_id/parts.

  5. 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 its file_id.

  6. Mint URLs when you render: POST P/files/urls for a page, GET P/files/:file_id/download for 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