For a while, the most accurate description of our API lived in three places at once: the route code, the documentation page, and the collection of requests each person on the team had saved locally. They agreed most of the time. “Most of the time” is not a great foundation for a generated client.
So we checked in an OpenAPI 3.1 document for v2 and published the same file at /openapi.json. The interesting work was not choosing a version number or adding summaries. It was deciding how to represent what the API really does, including the responses that do not resemble the tidy JSON examples used in most API tooling demos.
01 / THE MISSING CONTRACT
Prose explains. A schema commits.
Human documentation is good at context. It can tell you when to use a URL instead of a file upload, or why a transparent PNG is different from an alpha mask. It is less good at giving a validator every allowed value for bg_position, or telling a code generator that a successful response might be raw WebP bytes.
Upload or reference an image, choose output controls, and receive an image or JSON result.
Return account metadata that is safe for an authenticated client to inspect.
Expose the versioned health surface used by integrations and operations.
Keep error codes, request IDs, status values, and optional details in one envelope.
The spec gives each of those surfaces a machine-readable shape. The prose docs still do the teaching. We don't see them as competitors; one is the map and the other explains why you might want to take a particular road.
02 / THE STRANGE PART
A successful response is often not JSON
The normal BackgroundErase request returns image bytes. PNG, JPEG, WebP, BMP, TIFF, and GIF each have their own media type. An alpha-only response is a PNG mask. Then there is a JSON-in, JSON-out path for callers that send image_base64 or image_url, where the result contains encoded image data, dimensions, and a MIME type.
Binary response
- Best for file and multipart workflows.
- Response body is the image itself.
- Client must branch on the response content type.
JSON response
- Useful when the entire request stays in JSON.
- Image data is base64 encoded.
- Payload is larger, but metadata travels beside it.
If the spec calls every success application/json, generated code confidently tries to parse a valid PNG as text. That failure is memorable for the wrong reason. The 3.1 document describes the binary bodies directly and keeps the JSON branch separate.
responses:
"200":
content:
image/png:
schema: { type: string, format: binary }
image/webp:
schema: { type: string, format: binary }
application/json:
schema: { $ref: "#/components/schemas/Base64ImageResponse" }The request side has the same kind of branching. Multipart callers can send a file, base64 value, or URL. JSON callers use the text-friendly forms. Output format, channel mode, crop, size, despill, background color, fit, and position are enumerated where they should be. A typo should fail near the person who typed it, not after a production worker has stored the wrong thing.
03 / FAILURE SHAPES
Make failure boring enough to automate
Every v2 error uses one envelope: a stable code, human-readable message, HTTP status, request ID, and optional details. The spec defines authentication failures, invalid images, payload limits, validation errors, rate limits, upstream inference failures, and temporary service pressure.
The request ID is not decoration
It appears in the response and in server logs. When a report arrives with the ID, the investigation starts with one request instead of a vague time range and a screenshot of an error toast.
Rate-limit responses document Retry-After as well. A schema cannot know how many attempts your queue should make, or whether a failed catalog image should pause the rest of a batch. It can make sure the raw timing information survives into the client that makes those choices.
04 / KEEPING IT TRUE
The document is only useful while it disagrees loudly
Publishing a spec is a one-day task. Keeping it accurate is the actual project. The file stored with the API and the file served by the website are meant to be the same artifact, so changes can be reviewed against implementation instead of rewritten from memory later.
We also leave absent features absent. The current API is synchronous. There are no webhook registrations, job resources, callbacks, or asynchronous batch endpoints in the spec because those endpoints do not exist. A contract earns trust partly by resisting the temptation to describe the roadmap as if it had already shipped.
You can import the file into Postman, inspect it in an OpenAPI viewer, or generate a client as a starting point. We still recommend reviewing the generated code around binary bodies, timeouts, retries, and where returned files are stored. Generation removes repetitive typing. It does not remove application design.
The full contract is public and versioned with the production API surface.
Open the OpenAPI 3.1 file