Dashly Docs

Authentication

How API keys identify your agency, how scopes limit what a key can do, and how keys are revoked, suspended or minted by OAuth connectors.

Every call to the Dashly API carries a key in the Authorization header:

Authorization: Bearer ak_…
curl https://api.usedashly.com/v1/me \
  -H "Authorization: Bearer $DASHLY_API_KEY"

A request without the header, with an unknown key, or with a revoked or expired key gets 401 unauthorized.

A key belongs to the agency

A key authenticates an agency, not a person. Everything it reads or writes is limited to that agency, and an id from another agency behaves exactly like an id that does not exist (404).

Each key also has an owner, a member of the agency. The owner is who the key's usage is charged to (see Rate limits and quota), and writes made with the key are recorded in the agency's activity history under the member who issued it, marked as coming from the API.

Creating a key

Create keys in the dashboard under Connectors → API & webhooks. You need a role that can manage the agency's settings.

  • The key is shown once, at creation. Dashly stores only a hash of it, so a lost key cannot be recovered: revoke it and create a new one.
  • Keys look like ak_ followed by random characters. The dashboard later identifies a key by its prefix, its first 11 characters (for example ak_Xk2mP9qR).
  • A key can have an expiry (30, 90 or 365 days from the dashboard). After that date it is refused with 401.

You can also create keys through the API itself, which makes unattended rotation possible: create the replacement, switch your configuration over, then revoke the old key.

curl -X POST https://api.usedashly.com/v1/agency-settings/actions/create-api-key \
  -H "Authorization: Bearer $DASHLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Warehouse sync (2026-11)", "scopes": ["clients:read", "deals:read"] }'
{
  "data": {
    "key": "ak_…",
    "name": "Warehouse sync (2026-11)",
    "prefix": "ak_Xk2mP9qR"
  }
}

This needs the agency-settings:write scope. Two rules apply:

  • A key can never create a key broader than itself. Asking for a scope the calling key does not cover is refused with 403 and details.reason: "scope_too_broad". Without scopes, the new key gets full access (*), so this only works from a full-access key.
  • A key with an expiry passes it on: the new key expires at the same time.

Scopes

A scope has the form <resource>:<read|write>, where <resource> is a collection name from the catalogue (clients, deals, tickets, time-logs…). Two wildcards cover everything:

ScopeGrants
*Read and write on every resource.
*:readRead on every resource.
*:writeRead and write on every resource.
clients:readRead clients.
clients:writeRead and write clients. A write scope implies the matching read scope.

The dashboard offers two presets:

PresetScopeTypical use
Read-only (default)*:readDashboards, exports, one-way syncs out of Dashly.
Full access*Integrations that create or change data.

For anything narrower, create the key through create-api-key with an exact list of scopes (up to 50). Each resource's scopes are listed in the scopes field of the catalogue returned by GET /v1 (see Requests and responses).

Actions (verbs) follow the same rules: an action that changes data needs the resource's write scope, a read-only action needs its read scope. See Actions.

On personal collections such as time-logs, a key with the write scope but not the read scope only reaches the entries of its own owner.

Open resources

Three endpoints answer any valid key, whatever its scopes, because they expose no agency records. They let even a narrowly scoped key describe itself and the API:

  • GET /v1: the catalogue of resources.
  • GET /v1/me: the key's agency, scopes and remaining quota.
  • GET /v1/fields: the field registry, including your custom fields.

GET /v1/openapi.json needs no key at all. See OpenAPI.

Missing scope

A call outside the key's scopes is refused with 403 forbidden and details.reason: "scope_missing". The details name the scope that was needed and the ones the key has:

{
  "error": {
    "code": "forbidden",
    "message": "This key lacks the \"clients:write\" scope.",
    "details": {
      "required": "clients:write",
      "granted": ["*:read"],
      "reason": "scope_missing"
    },
    "request_id": "req_6f1c2b9e4d7a4c0e9b3f5a8d2e1c7b40"
  }
}

Revoking a key

Revoke a key from Connectors → API & webhooks, or through the API by its prefix:

curl -X POST https://api.usedashly.com/v1/agency-settings/actions/revoke-api-key \
  -H "Authorization: Bearer $DASHLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "prefix": "ak_Xk2mP9qR" }'

Revocation is permanent and takes effect on the next request, which gets 401. A key may revoke itself. Revoked keys stay listed in the dashboard so the agency keeps the history of what was calling it.

When a member leaves the agency, the keys they created are revoked automatically.

Suspended keys

Dashly suspends a key that keeps reading data after being told to slow down (see Rate limits and quota). A suspended key is refused with 403 forbidden and details.reason: "key_suspended".

Suspension is reversible: an agency admin can reactivate the key from Connectors → API & webhooks. Check the integration before doing so, and revoke the key instead if you think it leaked.

OAuth connectors

When a member connects an AI client or another app through OAuth (for example Claude through the MCP server), Dashly issues an OAuth token instead of asking them to paste a key. Behind that token stands a key minted for the connection:

  • Its scopes come from the role of the member who authorized it.
  • It appears in the API keys list with an OAuth connector badge, and it has no secret you can copy.
  • It is subject to the same scopes, limits, quota and suspension as any other key.
  • It ends with the connection: revoking the connection, or the member leaving, revokes it.
  • It cannot create API keys (403, details.reason: "connector_cannot_issue_key"), because a plain key would outlive the connection.

A rejected OAuth token gets 401; reconnect the connector to get a new one. See Connect a client.

On this page