Conventions¶
The same rules apply to every endpoint. The reference gives the details of each one.
Requests and responses¶
- Bodies are JSON (
Content-Type: application/json), in UTF-8. Field names are in camelCase. - Identifiers are strings. Treat them as opaque.
- Instants are UTC in ISO 8601 (
2026-10-16T08:12:44Z); calendar dates have no time (2026-11-15). - Enumeration values are lowercase snake_case:
open,geo_map,read_write. - Fields without a value are left out of responses rather than sent as
null. - A field the API does not know is an error, not ignored: a typo such as
adressgets400.
| Request | Success |
|---|---|
GET |
200 with the resource or the page |
POST that creates |
201, the new resource, and its URL in Location |
PATCH, PUT, actions such as POST /v1/tasks/{taskId}/resolve |
200 with the resource as it now is |
DELETE |
204 with no body |
Pagination¶
Lists return one page at a time:
{
"data": [ ... ],
"pagination": { "limit": 50, "hasMore": true, "nextCursor": "eyJvIjo1MH0.k3P..." }
}
limitsets the page size: 50 by default, 200 at most.- To get the next page, send the same request with
cursorset tonextCursor. The last page hashasMore: falseand nonextCursor. - A cursor is bound to the filters of the request that produced it. Changing a filter while paging gets
invalid_cursor: start again without a cursor.
def all_tasks(api, project_id, **filters):
params = {"limit": 200, **filters}
while True:
page = api.get(f"https://api.touch2build.com/v1/projects/{project_id}/tasks", params=params).json()
yield from page["data"]
if not page["pagination"]["hasMore"]:
return
params["cursor"] = page["pagination"]["nextCursor"]
Filters and sort¶
Filters are query parameters. Repeat a parameter to accept several values (status=open&status=resolved). sort takes one field, with - in front for descending order: sort=-due_date. Each list documents its filters and sort fields.
Partial updates¶
PATCH follows JSON Merge Patch (RFC 7396), sent as application/merge-patch+json (or application/json):
- a field you leave out is not changed;
- a field set to
nullis cleared; - any other value replaces the current one.
curl -X PATCH https://api.touch2build.com/v1/tasks/$TASK_ID \
-H "Authorization: Bearer $T2B_API_KEY" \
-H "Content-Type: application/merge-patch+json" \
-d '{"dueDate": "2026-12-01", "description": null}'
A status change is not a PATCH: use the actions resolve, close, reopen and forward.
Retrying creations safely¶
When a request times out, you cannot tell whether the server created the resource. Creating a task (POST /v1/projects/{projectId}/tasks) and a comment (POST /v1/tasks/{taskId}/comments) accept an Idempotency-Key header: a unique string of 8 to 64 letters, digits or ._:-, such as a UUID. Retrying with the same key returns the resource the first request created instead of a duplicate.
Generate the key once per resource you want to create, and keep it for the retries.
Files¶
- Downloads are sub-resources such as
/content,/imageor/thumbnail, and answer with the file itself (image/jpeg,application/pdf...). - Photos and other files are uploaded as the raw request body, with their
Content-Type. Plans are the exception:multipart/form-datawith the PDF and its metadata. - A request body is limited to 50 MB.
- A plan is processed after its upload. Until it is ready, its image and file answer
plan_processing: retry a few seconds later.
Rate limits¶
Requests are limited per API key (or per token). Over the limit, the API answers 429 with the code rate_limited and a Retry-After header giving the seconds to wait. Wait that long before retrying, and spread bulk jobs over time instead of sending requests in parallel.
Request identifiers¶
Every response carries an X-Request-Id header, also found in error bodies as requestId. Quote it when you contact support. You can send your own X-Request-Id (1 to 64 letters, digits or ._-) to follow a request through your logs and ours.