API
The deploydiff API reads a project's environments, capture batches, diff batches, their tasks and its notifications as JSON, and starts capture and diff batches. It answers with the same data the dashboard shows.
The API comes with the paid plans. Each successful call is billed.
API keys
A key belongs to one project. An organization Owner or Admin creates it from the project's API page in the dashboard, which also shows the project id. The key's secret is shown once, when it is created: store it where your tool keeps its secrets.
A key holds a project role, and has that role's rights on its project:
| Role | Can |
|---|---|
| Participant | Read everything the API offers on the project |
| Member | Read, and start capture batches and diff batches |
A key never has an organization role: it cannot change settings, members or keys, nor delete anything. A project has at most 25 active keys. Revoking a key takes effect on its next call. See Roles & Permissions.
Authentication
Send the key in the Authorization header of every call:
curl https://deploydiff.com/api/v1/projects/{project_id}/environments \
-H "Authorization: Bearer dd_…"The API reads the key from this header only — never from a cookie or the address.
Addresses
Every endpoint is under one project:
https://deploydiff.com/api/v1/projects/{project_id}/…- Ids are UUIDs.
- An address takes no trailing slash: one with a slash is redirected to the address without it.
- Every
GETendpoint also answersHEAD, with the same status and headers and no body.
| Method | Endpoint | Minimum key role |
|---|---|---|
| GET | /environments | Participant |
| GET | /environments/{environment_id} | Participant |
| GET | /environments/{environment_id}/capture-batches | Participant |
| POST | /environments/{environment_id}/capture-batches | Member |
| GET | /capture-batches/{capture_batch_id} | Participant |
| GET | /capture-tasks/{capture_task_id} | Participant |
| GET | /environments/{environment_id}/diff-batches | Participant |
| POST | /diff-batches | Member |
| GET | /diff-batches/{diff_batch_id} | Participant |
| GET | /diff-tasks/{diff_task_id} | Participant |
| GET | /environments/{environment_id}/notifications | Participant |
| GET | /notifications | Participant |
| GET | /notifications/{notification_id} | Participant |
To see the API at work, follow Capture after a deploy.
Responses
Bodies are JSON. Every object says what it is in an object field ("environment", "capture_batch", …), carries its id, and ends with created_at and updated_at. Dates are ISO 8601 timestamps with their offset. A field with no value is present and null, never left out.
The page of each object in the dashboard shows its JSON, the same as the API returns.
Lists
A list answers one page at a time, newest first:
{
"object": "list",
"data": [
{
"object": "capture_batch",
"id": "00000000-0000-0000-0000-000000000002"
}
],
"next_cursor": "WyIyMDI2LTExLTA2VDEwOjAwOjAwLjMxKzAwOjAwIiwiYTEuLi4iXQ"
}| Parameter | Value |
|---|---|
limit | How many items, from 1 to 100. Default 20. |
cursor | The next_cursor of the page, to read the page after it. |
next_cursor is null on the last page. A cursor does not expire: keep one to read on later from where you stopped. Send it back exactly as you received it: its format is ours and may change.
Errors
An error answers with its HTTP status and a body naming it:
{
"error": {
"code": "capture_interval",
"message": "The organization's plan allows one capture on demand every minute per environment, clicks and calls together.",
"retry_after": 42
}
}code is stable: test it, not the message, which is written for people. Some errors add a field beside them, listed below.
| Status | code | When |
|---|---|---|
| 400 | invalid_parameter | A query parameter is out of range, malformed, or sent twice. |
| 400 | invalid_cursor | cursor is not a next_cursor this API returned. |
| 400 | invalid_body | The body of a POST is not a JSON object with the fields the endpoint takes, or is over 16 KB. |
| 401 | missing_key | No Authorization: Bearer header. |
| 401 | invalid_key | The key is unknown or revoked. |
| 402 | usage_limit_reached | The organization has reached its usage limit for the billing period: batches start again when it renews. |
| 402 | billing_blocked | The organization has an unpaid invoice that was cancelled: contact support. |
| 403 | wrong_project | The key does not belong to the project in the address. |
| 403 | plan_required | The organization is not on a paid plan. |
| 403 | role_required | The endpoint needs a Member key. |
| 404 | not_found | No such endpoint, or nothing with this id in the project. |
| 405 | method_not_allowed | The endpoint does not take this method. The Allow header lists those it takes. |
| 409 | capture_not_done | A diff was asked between capture batches that are not both finished. |
| 422 | not_runnable | A capture batch was created but has nothing it can run. Adds capture_batch_id. |
| 429 | capture_interval | A capture on demand came too soon after the last one in the environment. Adds retry_after. |
| 500 | internal_error | Something went wrong on our side. |
| 504 | timeout | The call took too long on our side. |
After a 500 or a 504 on a POST, the batch may have been created all the same. Read the environment's latest batches (?limit=1) before sending the call again. A GET can simply be sent again.
Limits
- Captures on demand. An environment takes one capture on demand per interval set by the organization's plan — one a minute on the paid plans — the dashboard's and the API's together. One that comes too soon is answered
429 capture_interval, with the seconds to wait inretry_afterand in theRetry-Afterheader. Diff batches have no such interval. - Request rate. One IP address sends at most 120 requests a minute. Beyond, calls are answered
429in plain text, without the JSON body or aRetry-Afterheader: wait for the next minute. - Time. A read answers within 7 seconds, a start within 15, or
504 timeout. - Body size. A
POSTbody is at most 16 KB.
Billing
Each successful call — a 2xx answer, HEAD included — is billed at the rate on the pricing page, drawn from your plan's included usage like the rest. A call that is refused or fails is not billed. The captures and diffs an API call starts are billed like any others.
Your organization's usage page shows the calls, hour by hour, under API Call.