Build your first integration
Ce contenu n’est pas encore disponible dans votre langue.
The API reference tells you what each endpoint does. This page shows you how to put four of them together into something useful.
Everything below is plain curl, so it translates to any language.
Set up
Section titled “Set up”-
Get a key
Section titled “Get a key”Settings → MCP → API keys → New key. See API keys and tokens.
Terminal window export DOTBY_TOKEN="dotby_pat_..." -
Find out who you are
Section titled “Find out who you are”Always start here.
metells you your user, your workspaces, their ids and slugs, your role in each, and each one’s plan.Terminal window curl -H "Authorization: Bearer $DOTBY_TOKEN" \https://api.dotby.app/v1/me -
Note the addressing rule
Section titled “Note the addressing rule”You can use human identifiers anywhere an id is expected — reads and writes.
Thing Use Workspace slug or id — acmeProject key or id — ENGTask KEY-Nor id —ENG-42So
/v1/issues/ENG-42works. You rarely need to look up an id.
Recipe 1 — create a task from CI
Section titled “Recipe 1 — create a task from CI”The classic: a failed build opens a bug.
Tasks are created under a project, and the project can be named by its key:
curl -X POST \ https://api.dotby.app/v1/workspaces/acme/projects/ENG/issues \ -H "Authorization: Bearer $DOTBY_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: build-4821-failure" \ -d '{ "title": "Build 4821 failed on main", "description": "Nightly build failed. See the run log." }'A good key is something the calling system already has and will reuse on retry: a build number, a webhook delivery id, a commit SHA.
Recipe 2 — page through a list
Section titled “Recipe 2 — page through a list”Every list endpoint returns the same envelope:
{ "data": [ ... ], "has_more": true, "next_cursor": "..." }Page until has_more is false:
cursor=""while :; do page=$(curl -s -H "Authorization: Bearer $DOTBY_TOKEN" \ "https://api.dotby.app/v1/workspaces/acme/issues?limit=100&cursor=$cursor") echo "$page" | jq -c '.data[]' [ "$(echo "$page" | jq -r .has_more)" = "true" ] || break cursor=$(echo "$page" | jq -r .next_cursor)donelimit defaults to 25 and maxes at 100.
Do not conclude something is absent from one page. Filters can thin a page
without ending the list, so an empty page with has_more: true is normal. Page
to the end before deciding.
Recipe 3 — sync cheaply with delta polling
Section titled “Recipe 3 — sync cheaply with delta polling”Crawling everything on a schedule is wasteful. Crawl once, then poll for what changed:
curl -H "Authorization: Bearer $DOTBY_TOKEN" \ "https://api.dotby.app/v1/workspaces/acme/issues?updatedAfter=1770000000000"updatedAfter takes epoch milliseconds. Store the timestamp of your last
successful sync and pass it back next time.
It returns only tasks meaningfully edited strictly after that time. Page
until next_cursor is null, then record your poll time.
One caveat worth knowing: legacy rows that have never been edited since
update-stamping began are excluded. Do one full crawl to seed your store, and
use updatedAfter for increments only.
Recipe 4 — batch create with per-item results
Section titled “Recipe 4 — batch create with per-item results”Importing a list? Create up to 100 tasks in one call:
curl -X POST https://api.dotby.app/v1/workspaces/acme/issues/batch \ -H "Authorization: Bearer $DOTBY_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: import-2026-03-04" \ -d '{ "projectId": "ENG", "items": [ { "title": "Migrate the auth service" }, { "title": "Update the runbook" } ] }'The response splits results per item, under HTTP 200:
{ "succeeded": [{ "index": 0, "...": "..." }], "failed": [{ "index": 1, "problem": {} }] }One bad row never poisons the other ninety-nine — which also means a 200 does
not mean everything worked. Always read failed.
Handle errors properly
Section titled “Handle errors properly”Errors are RFC 9457 problem+json with a machine-stable code. Branch on the
code, never on the message.
| Status | Code | What to do |
|---|---|---|
| 401 | unauthenticated, invalid_token |
Token is wrong or revoked. Do not retry. |
| 403 | forbidden, insufficient_scope |
The owner lacks the right. Do not retry. |
| 403 | upgrade_required |
Workspace is on Free. Do not retry. |
| 404 | not_found_or_forbidden |
Missing or invisible to you — deliberately indistinguishable. |
| 409 | idempotency_conflict |
Same key, different body. Fix the key. |
| 429 | rate_limited |
Wait Retry-After seconds, then retry. |
| 400 | validation_failed |
Bad input. Do not retry. |
Only 429 is worth retrying automatically.
Generate a client instead
Section titled “Generate a client instead”The whole surface is described by an OpenAPI 3.1 document:
curl https://api.dotby.app/v1/openapi.jsonFeed it to your generator of choice and skip hand-writing request types. The docs are generated from the same source, so the spec cannot drift from behavior.
Or skip the API entirely
Section titled “Or skip the API entirely”Two shortcuts that are often the right answer:
- The CLI already wraps all of this —
--json, a built-in--jq, and exit codes you can branch on in a shell script. - MCP gives an AI agent the same operations with no code at all.
- REST API reference — every endpoint and field.
- API keys and tokens — rotation and limits.
- Use the CLI — the shell-shaped version of this page.