On September 10, Render's API stopped being something you curl and became something you import. In a single day, Render shipped CLI v2.28.0, Python SDK v1.1.0, and TypeScript SDK v1.1.0 — all three bannered together on a new "Tooling Releases" rail on the Render changelog. The REST endpoints are still there, but the blessed way to drive Render programmatically is now pip install render and npm i @renderinc/sdk.
That reframes what "Render-compatible" means. Until now, an alternative platform or migration target could claim compatibility by matching REST endpoints: same paths, same JSON. After September 10, the contract has a compiled form — typed methods, pagination helpers, and error classes that fail loudly on anything the spec doesn't say.
If your migration story is "point your Render tooling at our fleet," the tooling just got stricter. This post inventories exactly what shipped, extracts the client surface a compatible API must now match, and turns it into a conformance checklist.
The new bar, in one table
Before the release notes: here is the whole argument compressed. Each row is one thing compatibility used to mean, what it means now, and the concrete September 10 artifact that sets it.
| You used to match | You must match now | Concrete artifact |
|---|---|---|
| List endpoints return JSON arrays | Every list item carries its own cursor string | 26 *WithCursor envelopes in the OpenAPI spec; SDK list methods thread per-item cursors |
| Errors have human-readable messages | Errors carry stable, machine-readable code values | {id, message, code} envelope plus an errorCode vocabulary; ClientError.code in both SDKs |
| Endpoint coverage (paths + verbs) | Entry-point and method names with typed signatures | Render client, sandboxes.snapshots.create(kind="runtime"), listGroups() — 31 resource modules in the Python REST client |
| Plan names as documented strings | Plan identifiers the SDKs accept and send | flex for Workflows, 256mb–40g for Key Value, 2c-8g-style compute plans in regenerated clients |
| Bearer-token auth | Auth plus client conventions | RENDER_API_KEY env var, render whoami --output json machine-readable identity |
The rest of this post substantiates every row.
What shipped on September 10
The headline acts are the two SDKs, both cut as v1.1.0 and both still labeled beta. They move in lockstep — same version, same day, mirrored features — which is itself a signal: Render is treating the two languages as one surface.
The Python changelog for 1.1.0 is dominated by Sandboxes: sandboxes.list_groups(), full snapshot lifecycle (snapshots.create / from_id / list / delete), a snapshot kind distinguishing filesystem (writable filesystem only) from runtime (memory and CPU state too), optional expires_at, and sandboxes.create(snapshot_id=...) to boot from a snapshot instead of a base image. Snapshot failures get typed errors — SnapshotNotFoundError, SnapshotNotReadyError, SnapshotPlanMismatchError — each exposing the API's error code when the server sends one. Smaller but telling additions: Key Value accepts the size-based plan names (256mb, 1g, 5g, 10g, 20g, 40g) for automatic provisioning, ClientError.code now carries the API error code generally, and a ResourceRefType.WORKFLOW fix stops blueprint parsing from raising ValueError on blueprints that declare workflows.
The TypeScript changelog mirrors all of it: sandboxes.listGroups(), sandboxes.snapshots with create / get / list / delete, kind plus expiresAt, snapshotId on create, SandboxSnapshotNotFoundError / SandboxSnapshotNotReadyError / SandboxSnapshotPlanMismatchError, Key Value autoProvision.plan support, and ClientError exposing the API error code. Both SDKs also note the same final line: the REST client was regenerated from the latest OpenAPI schema, picking up new compute plan identifiers, sandbox execution listing and retrieval, service disk usage events, and env-group linking against artifact sources.
The CLI kept pace with not one but two releases on September 10. v2.27.0 added the snapshot commands (render ea sandboxes snapshots create/get/list, sandboxes create --snapshot-id), made render blueprints validate report workflows, and patched golang.org/x/crypto for an upstream advisory. v2.28.0 followed the same day with snapshot deletion, a render whoami that now includes your user ID and speaks --output json|yaml, and a help-text wrapping fix. Machine-readable whoami is a small change with an audience beyond humans: it is exactly the kind of surface agents and scripts probe first.
One honesty note before the analysis: all of this is beta. The SDK README still warns the SDK is in early access and "subject to breaking changes without notice," and the 1.0.0 release three weeks earlier rewrote the Workflows calling convention outright (tasks take a TaskContext first parameter; @app.task returns a non-callable TaskDefinition; subtasks go through ctx.run). Early adopters have already paid one breaking migration, and the PyPI package itself was renamed from render_sdk to render on August 21. The bar is being set now — while tracking it is still cheap.
Why generated clients change the contract
The single most important line in either changelog is the dullest one: "Regenerated the REST client from the latest OpenAPI schema." Render publishes its OpenAPI spec ("Render Public API," v1.0.0), and the SDKs' REST layers are compiled views of it — currently 131 paths, 208 operations, and 164 schemas. The spec is the contract; the SDKs are what the contract looks like after codegen.
That inverts the comfortable assumption that SDK users are a niche you can ignore while serving REST. The SDK is the REST contract, compiled. A mismatch between your API and Render's that used to surface as a subtly different curl response now surfaces as a thrown TypeError, a failed model parse, or a pagination helper that never terminates. Three details from the actual spec show how deep the compiled surface goes.
First, pagination. Render does not use page-level next_cursor envelopes. Instead, the spec defines 26 per-item *WithCursor wrappers — serviceWithCursor, deployWithCursor, taskRunWithCursor, and so on — where every item in a list response carries its own cursor string alongside the resource:
{
"service": { "id": "srv-abc123", "name": "api" },
"cursor": "eyJpZCI6MTIzfQ=="
}SDK list helpers are written against that shape. A compatible API that returns bare arrays, or page-level cursors in the style of most REST APIs, will compile fine against nothing — the generated client's deserialization simply fails. This is the single most distinctive item on the checklist, and the easiest to get wrong by following generic REST conventions.
Second, errors. The spec's error schema is {id, message, code}, where code is documented as "a stable, machine-readable identifier" that clients can handle specially, backed by an errorCode vocabulary (including the new snapshot_* family). The field is deliberately a plain string so that new codes are additive, never breaking — and the spec text says outright that the vocabulary exists "so generated clients get typed constants."
Version 1.1.0 is the release where the SDKs started consuming that design: ClientError.code on both sides, plus per-resource typed errors. Compatibility here means returning the same codes on the same failures, not merely the same HTTP statuses. A SnapshotPlanMismatchError that your API surfaces as a bare 400 with a prose message will slip past every except clause written against the real SDK.
Third, vocabulary churn. Plan identifiers are live API vocabulary, not documentation. September alone shows the churn: the flex compute plan replaced starter and standard for Workflows on September 1 (more on that below), size-based Key Value plans landed in the SDKs on September 10, and the CLI's late-August releases taught its plan pickers 2c-8g-style compute plan names.
Each churn event flows through the same pipe — spec update, client regeneration, SDK release — so the SDK release notes are effectively a diff log for the API surface. Required reading, monthly.
The September churn that proves it
The SDKs did not ship into a quiet month, and the surrounding changelog entries demonstrate why release-note tracking is now load-bearing. On September 1, Render introduced the flex compute plan for Workflows: up to 1 CPU and 4 GB of RAM per task run, billed only for CPU and RAM actually consumed, automatically replacing starter and standard for existing tasks. A plan rename with automatic migration is precisely the kind of change that breaks hardcoded plan strings in user code — and precisely the kind the regenerated SDK clients absorb, since they ship the new identifiers days later.
On September 3, services gained a dedicated Deploys page: full deploy history with no date cutoff (previously capped at 90 days on the Events page), a Live badge on the current deploy, in-progress status, and a banner when auto-deploys are disabled; cron jobs got separate Builds and Runs pages. Dashboards are not API surface — but the deploy-history data the page renders is modeled in the API as deployWithCursor envelopes, the same per-item-cursor shape the SDKs consume. The product and the client surface are two views of one model; drift in one is drift in both.
The pattern for compatibles to internalize: Render now ships API evolution as a triple — changelog entry, spec update, regenerated clients — on a monthly cadence. Tracking any two without the third leaves a gap the SDKs will expose.
What "Render-compatible" owes now
Start with the strongest objection, because it is reasonable: the SDKs are beta, early-access, barely a month past 1.0 — surely REST parity is still the real bar, and SDK conformance is a someday problem. Three answers.
First, as established above, the SDKs are generated from the same spec your REST implementation would target. SDK conformance is REST conformance, executed by a stricter client. There is no cheaper way to audit your endpoint coverage than running the official client against it.
Second, the consumers are increasingly not humans. Render ships agent-oriented surfaces — CLI and MCP documentation, an agents page for deploying with a coding agent — and agents import packages; they do not hand-roll curl. Every migration guide that will ever say "point your Render tooling at us" now implicitly includes pip install render and the RENDER_API_KEY convention. A compatible API that only passes curl-level tests fails the first agent that imports the SDK, lists services, and walks per-item cursors.
Third, beta cuts both ways. Yes, the surface will move — August's TaskContext rewrite proves it. But that is an argument for pinning and tracking now ("compatible with @renderinc/sdk 1.x, verified against the September spec"), not for waiting. The teams that start conformance testing while the SDK is young get their mismatch reports for free from every beta release; the teams that wait inherit a stable, wide contract with no incremental on-ramp.
Concretely, if you operate or are evaluating a Render-compatible API, September 10 assigns three tasks:
- Diff the OpenAPI spec on every SDK regeneration, and treat new identifiers (plan names, error codes, resource modules) as contract changes.
- Run the SDKs' list, get, and error paths against your implementation as a conformance suite — walk a cursor-paginated list to termination, assert a typed error code on a known failure, create against each plan identifier you claim.
- Version your compatibility claim against the SDK releases, the way you would against a database wire protocol. "Render-compatible" without a version number no longer parses.
Render spent September turning its API from endpoints into a toolchain: typed clients in two languages, a CLI that speaks JSON about itself, compute plans that bill for actual consumption, and deploy history without a cutoff. The toolchain is the contract now. Match it, test against it, and say which version you match — or cede the migration story to whoever does.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



