Requests and responses
Base URLs, response envelopes, public ids, pagination, filtering, sorting, search and the discovery endpoints of the Dashly API.
Base URL
https://api.usedashly.com/v1The same API is also mounted under the application at https://app.usedashly.com/api/v1. Both behave identically; prefer api.usedashly.com.
Requests and responses are JSON. Send Content-Type: application/json with every request that has a body.
Operations
Every collection (clients, contacts, deals, tickets…) follows the same pattern:
| Method | Path | Does | Success |
|---|---|---|---|
GET | /v1/{resource} | List records | 200 |
POST | /v1/{resource} | Create a record | 201 |
GET | /v1/{resource}/{id} | Read one record | 200 |
PATCH | /v1/{resource}/{id} | Update the fields you send | 200 |
DELETE | /v1/{resource}/{id} | Delete the record | 204 |
Some collections are read-only, and some cannot be deleted; the catalogue (below) lists the methods each one accepts. A method a collection does not accept answers 405 method_not_allowed.
Request bodies are strict: a field the resource does not accept is refused with 422 validation_failed, never silently ignored. PATCH only changes the fields present in the body, with one exception, customFields, covered in Custom fields.
Beyond create, update and delete, many operations are actions, such as converting a lead or importing a CSV. See Actions.
Envelopes
A single record comes back in data:
{ "data": { "id": "hsbftmp3f5", "name": "Boudbois", "status": "active" } }A list comes back in data, with pagination in meta:
{
"data": [{ "id": "hsbftmp3f5", "name": "Boudbois", "status": "active" }],
"meta": { "page": 1, "perPage": 50, "total": 128, "totalPages": 3 }
}An error comes back in error. See Errors.
{
"error": {
"code": "not_found",
"message": "No clients item \"x9x9x9x9x9\".",
"request_id": "req_6f1c2b9e4d7a4c0e9b3f5a8d2e1c7b40"
}
}Ids are public ids
Every id the API returns or accepts is a short public id such as hsbftmp3f5. Custom fields have ids of the form fld_…. Use public ids everywhere, in paths and in relation fields:
curl -X POST https://api.usedashly.com/v1/contacts \
-H "Authorization: Bearer $DASHLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "firstName": "Claire", "lastName": "Martin", "clientId": "hsbftmp3f5" }'An id that is unknown, or that belongs to another agency, is answered the same way: 404 in a path, 422 with details.reason: "not_found" in a body.
Pagination
Lists are paginated by page number.
| Parameter | Meaning | Default |
|---|---|---|
page | Page to return, starting at 1 | 1 |
per_page | Records per page, at most 200 | 50 |
A per_page above 200 is refused with 400 invalid_request rather than silently capped, so you never skip records without knowing. Use meta.totalPages to know when to stop:
curl "https://api.usedashly.com/v1/deals?page=2&per_page=200" \
-H "Authorization: Bearer $DASHLY_API_KEY"Pagination is offset-based. If records are created or deleted while you page through a collection, a record can move between pages. Sort on a stable field such as createdAt for long syncs.
Sorting
sort takes one field name, prefixed with - for descending order:
?sort=-createdAt
?sort=nameEach resource declares its sortable fields and its default order. Clients, for example, sort on createdAt, name and status, newest first by default.
Search
q runs a case-insensitive "contains" search over the resource's searchable fields (for clients: name and sector):
?q=bakeryFilters
filter[field]=value keeps the records whose field equals the value. Several filters combine with AND:
curl "https://api.usedashly.com/v1/deals?filter[status]=open&filter[clientId]=hsbftmp3f5" \
-H "Authorization: Bearer $DASHLY_API_KEY"Values are checked against the field's type: a boolean takes true or false, a number must be numeric, and a field with a fixed list of values only accepts one of them.
Unknown parameters are errors
An unknown query parameter, a field that cannot be filtered or sorted on, or a value outside a field's list answers 400 invalid_request, and details lists what is accepted. A typo never returns the whole unfiltered collection.
{
"error": {
"code": "invalid_request",
"message": "Filter \"stage\" is not supported.",
"details": { "filterable": ["status", "clientId"] },
"request_id": "req_0b8e3a1f9c2d4e6f8a7b5c3d1e9f0a2b"
}
}Request ids
Every response carries an X-Request-Id header, and every error envelope repeats it as request_id. Quote it when you contact support.
You can send your own id in X-Request-Id (8 to 128 characters from A-Z a-z 0-9 _ . : -). Dashly then uses yours, so you can find a call under the id your own logs already use. Anything else is replaced by a generated id of the form req_….
Body size
A request body is limited to 1 MiB. A larger body is refused with 413 payload_too_large before anything is read or written; details.limit gives the limit in bytes.
Discovery
Three endpoints describe the API and your agency. Any valid key can call them, whatever its scopes.
GET /v1: the catalogue
Lists every resource and its contract: readable and writable fields, relations, filters, sorts, searchable fields, actions, custom field entity and scopes.
curl https://api.usedashly.com/v1 \
-H "Authorization: Bearer $DASHLY_API_KEY"{
"data": {
"version": "v1",
"resources": [
{
"name": "clients",
"label": "Clients",
"path": "/v1/clients",
"methods": ["GET", "POST", "PATCH", "DELETE"],
"attributes": ["id", "name", "slug", "sector", "website", "linkedinUrl", "siren", "status", "locale", "createdAt"],
"writable": ["name", "slug", "sector", "website", "linkedinUrl", "siren", "status", "locale"],
"relations": [],
"filterable": ["status", "sector", "lead"],
"actions": ["import-csv", "site-statistics", "ai-visibility" /* … */],
"sortable": ["createdAt", "name", "status"],
"searchable": ["name", "sector"],
"customFields": "account",
"backdatable": true,
"scopes": ["clients:read", "clients:write"]
}
// … every other resource
]
}
}customFields names the entity whose custom fields apply to the resource (see Custom fields). backdatable: true means a POST may carry a createdAt, so records imported from another tool keep their original date.
GET /v1/me: the key
Returns the key's agency, its scopes and the agency's remaining monthly quota. Call it at startup to validate the key without reading any record.
{
"data": {
"agency": { "name": "Northwind Studio", "slug": "northwind-studio" },
"scopes": ["*:read"],
"quota": {
"used": 1240,
"limit": 50000,
"remaining": 48760,
"unlimited": false,
"resetsAt": "2026-11-01"
}
}
}On an unlimited plan, limit and remaining are null and unlimited is true.
GET /v1/fields: the field registry
Lists the fields of each entity, built-in and custom. Add ?entity=account|deal|lead|ticket|contact to get one entity. See Custom fields.
Identifying your client
You may send X-Dashly-Surface: <name> (lowercase letters, digits, _ and -, up to 20 characters) to label your integration in the agency's activity history. The header grants nothing; it only helps the agency tell which tool made a change.