Errors
The error envelope of the Dashly API, its codes, and the precise reasons that tell you which rule refused a call.
Every failure has the same shape, whatever the endpoint:
{
"error": {
"code": "validation_failed",
"message": "A required field is empty",
"details": { "field": "budget", "reason": "field_required" },
"request_id": "req_6f1c2b9e4d7a4c0e9b3f5a8d2e1c7b40"
}
}| Field | Meaning |
|---|---|
code | The error class. A fixed list, mapped one-to-one to an HTTP status. |
message | A technical description in English, for developers and logs. |
details | Structured context, when there is any: the reason, the field at fault, the accepted values, a limit. |
request_id | The id of the request, also sent in the X-Request-Id header. Quote it to support. |
Branch on codes, not messages
message is written for humans and may change at any time. It often names the value in cause, and its wording is improved without notice. Never parse it.
Your code should branch on:
error.code, for the class of problem (and the HTTP status);error.details.reason, for the exact rule that refused the call, when present.
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
if (!res.ok) {
const { error } = await res.json();
if (error.code === "rate_limited") {
// wait Retry-After seconds, then retry
} else if (error.details?.reason === "scope_missing") {
// ask for a key with error.details.required
} else if (error.details?.reason === "plan_required") {
// the agency's plan lacks error.details.feature
}
}Codes and reasons are part of the API contract: new ones can be added, existing ones are never renamed.
Error codes
| error.code | HTTP status |
|---|---|
unauthorized | 401 |
forbidden | 403 |
not_found | 404 |
invalid_request | 400 |
validation_failed | 422 |
payload_too_large | 413 |
method_not_allowed | 405 |
conflict | 409 |
rate_limited | 429 |
server_error | 500 |
400 invalid_request: the request itself is malformed (an unknown query parameter, a filter or sort the resource does not support, invalid JSON, a body that is not an object, a badIdempotency-Key).detailslists what is accepted when that helps.401 unauthorized: no key, an unknown key, or a revoked or expired key. See Authentication.403 forbidden: the key is valid but may not do this.details.reasonsays why (missing scope, plan, suspended key…).404 not_found: the resource, the record or the action does not exist for this agency. Records of other agencies are indistinguishable from records that do not exist.409 conflict: the current state forbids the action, or anIdempotency-Keywas reused (see Idempotency).413 payload_too_large: the body is over 1 MiB;details.limitis the limit in bytes.422 validation_failed: the request is well-formed but a value is refused.429 rate_limited: too many requests, or the monthly quota is spent. Always comes withRetry-After. See Rate limits and quota.500 server_error: a problem on our side. It is recorded automatically; retry later and quote therequest_idif it persists.
Refusal reasons
When a business rule refuses a call, details.reason names it, next to the unchanged code. The table below lists every reason with its class and a short meaning.
| details.reason | Class | Meaning |
|---|---|---|
agency_memory_fact_refused | validation | This fact cannot be kept in the assistant's memory |
agency_memory_full | conflict | The assistant's memory is full |
agency_memory_not_installed | conflict | Assistant memory not installed |
already_exists | conflict | The name is already in use |
already_linked | conflict | Repository already linked to the project |
already_member | conflict | The person is already a member |
ambiguous_name | validation | Several items match this name |
assistant_client_rate_limited | rate_limit | Too many messages to the assistant, retry later |
assistant_conversation_title_invalid | validation | The conversation title must be between 1 and 120 characters |
assistant_conversations_not_installed | conflict | Assistant conversations not installed |
assistant_exchange_limit | rate_limit | Too many actions asked in one message |
assistant_instructions_limit_reached | conflict | Assistant instruction limit reached |
assistant_instructions_not_installed | conflict | Assistant instructions not installed |
assistant_reply_withheld | validation | The assistant's reply was withheld |
assistant_ticket_duplicate | conflict | A request with the same content was opened recently |
assistant_ticket_rate_limited | rate_limit | Too many requests opened by the assistant for this client, retry later |
automation_limit_reached | conflict | Automation rule limit reached |
bot_not_in_channel | conflict | The bot is not in the channel |
brief_needs_client | validation | The opportunity has no organisation to file the brief under |
channel_archived | conflict | The channel is archived |
channel_not_found | not_found | Slack channel not found |
chat_files_limit_reached | conflict | Too many files on one message |
client_assignees_not_installed | conflict | Client assignees not installed |
client_has_no_projects | conflict | The client has no project |
client_reminders_not_installed | conflict | Client reminders not installed |
client_visibility_required | conflict | Assigning the client makes the ticket visible in their portal; confirm it |
column_name_required | validation | A column has no name |
conflict | conflict | The current state no longer allows this action |
connection_expired | conflict | The service connection is no longer valid |
connector_cannot_issue_key | authz | A connector cannot create API keys |
consent_required | validation | This service connects by consent, not by key |
content_required | validation | The step content is required |
crawl_credits_insufficient | conflict | Not enough crawl credits left |
crawl_not_running | conflict | No crawl is running |
crawl_running | conflict | A crawl is already running for this client |
credits_exhausted | conflict | Not enough credits left this month |
cycle_other_project | validation | The cycle belongs to another project |
deal_deck_not_provider | conflict | A deal's presentation is never shown to providers |
deal_has_milestone | conflict | The deal already carries another milestone |
deal_has_project | conflict | The deal is already linked to another project |
deal_not_recurring | conflict | The deal is not recurring |
deal_planned_project_closed | validation | The project is not live delivery |
deal_portal_not_installed | conflict | Deal portal visibility not installed |
deal_project_other_client | validation | The deal and the project belong to different clients |
deck_notes_not_installed | conflict | Speaker notes not installed |
deletion_disabled | authz | Deleting records is disabled for this agency |
dns_record_missing | conflict | DNS verification record not found |
doc_access_client_only | validation | Access to clients and providers is set on a client's document |
doc_briefs_not_installed | conflict | Attaching documents to the pipe is not installed |
doc_client_has_children | conflict | This document has sub-pages |
doc_comments_not_installed | conflict | Document comments not installed |
doc_has_comments | conflict | The document has comments |
doc_project_no_client | validation | A provider's document is written on a client's project |
doc_provider_not_on_agency | validation | This provider works on none of the agency's projects |
doc_provider_not_on_client | validation | This provider does not work on this client |
domain_not_verified | conflict | Domain not verified for the agency (TXT record missing) |
duplicate_column_key | validation | Two columns share the same key |
duplicate_field_key | validation | Two form fields share the same key |
embed_host_not_allowed | validation | Embed host is not on the allowlist |
field_required | validation | A required field is empty |
file_empty | validation | File is empty |
file_missing | validation | File is missing |
file_too_large | validation | File is too large |
file_type_blocked | validation | File type refused (web page, SVG or script) |
follow_up_pipeline_required | validation | Choose the follow-up pipeline of the new deals |
forbidden | authz | Not allowed |
form_fields_invalid | validation | A form field is invalid (key or label) |
form_fields_missing | validation | A form step needs at least one field |
geo_not_ready | conflict | AI visibility module not ready (disabled, no engine or no tracked question) |
github_token_invalid | validation | GitHub refused the access token |
github_unavailable | upstream | GitHub is unavailable |
identity_required | validation | An organisation or a person is required |
image_not_found | not_found | No usable image at this address |
image_type_unsupported | validation | Unsupported image format |
impersonation_refused | authz | Impersonation refused for this account |
internal | internal | Internal error |
internal_deck_client_project | validation | An internal presentation cannot be filed in a client's project |
internal_deck_not_portal | conflict | An internal presentation cannot be shown in a client portal |
internal_item_client_project | validation | An internal work item cannot be filed in a client's project |
invalid_api_key | validation | API key rejected by the remote service |
invalid_input | validation | Invalid input |
invalid_repository | validation | Not a GitHub repository reference |
invalid_state | conflict | The current state does not allow this action |
invalid_status | validation | Invalid status |
invitation_not_pending | conflict | The invitation is no longer pending |
invite_pending | conflict | The address already has a pending invitation |
invoice_deal_other_client | validation | The invoice belongs to another client than the deal |
item_project_not_shared | conflict | This item's project is not shared with any provider |
key_not_attached | authz | The API key is not attached to a member |
key_suspended | authz | The API key is suspended after unusual activity |
limit_reached | conflict | Limit reached |
llm_output_invalid | upstream | The model's answer was unreadable |
member_integrations_not_installed | conflict | Personal integrations are not installed |
member_linkedin_taken | conflict | This LinkedIn account is already connected by another member |
mention_internal_client | validation | Mention of a client in an internal note |
mention_unknown | validation | Mention of a person without access to the ticket |
merge_already_merged | conflict | A record was already merged into another one |
merge_ancestor | validation | The kept ticket is a subtask of a merged ticket |
merge_client_mismatch | conflict | A merged record belongs to another client: confirm the change |
merge_self | validation | A record cannot be merged into itself |
milestone_deal_other_client | validation | The deal belongs to another client |
milestone_has_deal | conflict | The milestone already has its deal |
milestone_not_planned | conflict | The milestone has no period to postpone |
milestone_other_client | validation | The milestone belongs to another client |
milestone_other_project | validation | The milestone belongs to another project |
name_required | validation | Name is required |
no_follow_up_pipeline | conflict | The agency has no follow-up pipeline |
no_open_column | validation | Every column marks work as finished |
no_terminal_column | validation | No column marks work as finished |
not_a_client_member | validation | The person is not a member of this ticket's client portal |
not_a_member | validation | The person is not a member of the agency |
not_connected | conflict | The service is not connected |
not_found | not_found | Not found |
notification_events_not_installed | conflict | Per-event notification settings not installed |
onboarding_locked | conflict | The onboarding can no longer be edited |
origin_invalid | validation | Origin refused |
owner_protected | conflict | The agency owner cannot be changed or removed |
partner_has_active_shares | conflict | The partner still has shared projects: confirm they are closed too |
partner_has_deals | conflict | The partner is on a deal and cannot be deleted |
partner_name_required | validation | The partner needs a name |
partner_not_linked | validation | The agency is not a linked partner |
periodic_reports_not_installed | conflict | Periodic reports not installed |
permission_missing | authz | The member lacks the required permission |
phone_invalid | validation | Invalid phone number (E.164 expected) |
phone_taken | conflict | Phone number already assigned to another member |
pipe_other_client | validation | The opportunity does not belong to this document's client |
plan_required | authz | The agency's plan does not include this feature |
platform_admin_required | authz | Only a platform administrator may do this |
portal_already_member | conflict | The address is already a member of this space |
portal_deal_no_client | validation | The deal has no client organisation |
portal_invitations_limit_reached | conflict | Too many pending invites |
portal_last_admin | conflict | The space's last admin cannot be removed |
portal_member_self | conflict | A portal member cannot change or remove themselves |
portal_quick_links_limit_reached | conflict | Quick link limit reached |
portal_replacement_invalid | validation | The replacement admin must be another member of this space |
portal_revoke_deal_open | conflict | Every deal of the client must be lost before its portal access is revoked |
portal_revoke_not_prospect | conflict | Only a prospect's portal access is revoked from a deal |
portal_role_escalation | authz | A portal member cannot grant a role above their own |
portal_setup_pending | conflict | Finish the space onboarding first |
pptx_empty | validation | Import produced no usable content |
pptx_no_slides | validation | No slide found in the PPTX file |
pptx_unreadable | validation | PPTX file is unreadable |
private_key_unreadable | validation | Unreadable private key (PEM) |
project_other_client | validation | The project does not belong to this client |
prompt_limit_reached | conflict | Tracked-question limit reached |
provider_assistant_cap_invalid | validation | The monthly cap per provider must be a whole number between 0 and 500 |
query_filter_full | validation | The query filter is full |
query_filter_name_taken | conflict | A query filter has this name |
query_filter_too_slow | validation | The query filter took too long |
query_filters_not_installed | conflict | Query filters not installed |
rate_limited | rate_limit | Too many attempts, retry shortly |
recurrence_end_before_start | validation | The recurrence ends before it starts |
recurrence_milestone_missing | validation | A start after a milestone needs its milestone |
recurrence_milestone_other_client | validation | The milestone belongs to another client than the deal |
recurrence_start_missing | validation | A start on a date needs its date |
repositories_not_installed | conflict | Linking repositories to a project is not installed |
request_closed | conflict | The request is closed |
required_steps_missing | validation | Required steps are still incomplete |
resources_not_installed | conflict | Resources table not installed |
review_access_refused | validation | Review access list refused (admin role removed or person outside client/agency) |
review_origin_missing | conflict | No review domain configured |
review_site_people_limit_reached | conflict | Review site people limit |
reviewer_not_member | validation | The person does not belong to the agency or the chosen client |
roadmap_not_installed | conflict | The roadmap is not installed |
scope_missing | authz | The API key lacks the required scope |
scope_too_broad | authz | A key cannot grant scopes it does not hold |
seat_limit_reached | conflict | Seat limit reached |
select_options_missing | validation | A select field has no options |
sentry_issue_invalid | validation | Not a Sentry issue reference |
sentry_issues_not_installed | conflict | Sentry issues not installed |
sentry_org_not_found | validation | Unknown Sentry organisation |
sentry_token_invalid | validation | Sentry refused the auth token |
sentry_unavailable | upstream | Sentry is unavailable |
seo_checklist_not_installed | conflict | SEO checklist not installed |
services_not_installed | conflict | Following a client's services is not installed |
social_already_published | conflict | The post is already published |
social_body_invalid | validation | Empty text or too long for the network |
social_date_required | validation | A publication date is required |
social_media_limit | validation | Too many media, or media that cannot mix |
social_media_too_large | validation | The file is too large for the network |
social_media_type | validation | This network does not take this file type |
social_not_installed | conflict | Social module not installed |
social_zernio_account_invalid | validation | Unknown Zernio account, or not on the author's network |
social_zernio_key_invalid | validation | Zernio refused the API key |
social_zernio_unavailable | upstream | Zernio is unavailable |
social_zernio_unconfigured | conflict | Zernio is not connected |
split_key_reused | conflict | This idempotency key was used for another split |
split_no_client | validation | The deal or project has no client |
split_source_kind_mismatch | validation | The current deal's row must keep the deal's kind |
split_source_not_deal | validation | Only a deal being split has a 'current deal' row |
stage_has_deals | conflict | The stage still holds deals |
storage_not_configured | internal | Storage is not configured |
storage_unavailable | upstream | Storage is unavailable |
sub_project_nested | validation | Sub-projects are one level deep |
sub_project_other_client | validation | A sub-project has the client of its global project |
sub_project_self | validation | A project cannot be its own sub-project |
system_folder_locked | conflict | A client's folder cannot be renamed or deleted |
tag_limit_reached | conflict | Automation tag limit reached |
ticket_has_no_client | conflict | The ticket belongs to no client |
ticket_other_client | validation | The work item does not belong to this client |
timer_not_running | conflict | No timer is running |
too_long | validation | Text is too long |
too_many_addresses | validation | Too many widget addresses |
too_many_repositories | conflict | Too many repositories on the project |
unauthenticated | authz | Not authenticated |
upload_failed | upstream | File upload failed |
upstream_rejected | upstream | The remote service rejected the request |
upstream_unavailable | upstream | The remote service is unavailable |
url_not_allowed | validation | URL not allowed: only public HTTPS addresses are accepted |
website_missing | validation | The client has no website on record |
weekly_bilan_not_installed | conflict | The weekly summary is not installed |
whatsapp_filing_rate_limited | rate_limit | Too many requests filed on WhatsApp for this client, retry later |
workflow_invalid | validation | Invalid workflow (missing columns, unnamed column, or no terminal / non-terminal column) |
workflow_too_few_columns | validation | A workflow needs two columns |
wrong_password | authz | Wrong password |
Some reasons are documented with the feature they belong to rather than in this table: the 429 reasons (burst_limit, write_burst_limit, ip_limit, auth_failures, volume_budget, agency_quota_exhausted, user_quota_exhausted) in Rate limits and quota, the 409 reasons idempotency_key_conflict and idempotency_key_in_flight in Idempotency, and feature_disabled (a 404 for a collection that is switched off on the platform, with details.feature).
Common details
details.field
When a single field is at fault, details.field names it, as the key you sent (endAt, projectId, or the key of a custom field):
{
"error": {
"code": "validation_failed",
"message": "Invalid input",
"details": { "field": "endAt", "reason": "invalid_input" },
"request_id": "req_…"
}
}When the body does not match the resource's schema at all (a wrong type, a missing required field, an unknown field), details is instead a list, one entry per problem:
{
"error": {
"code": "validation_failed",
"message": "Invalid input: expected string, received undefined",
"details": [
{ "field": "name", "message": "Invalid input: expected string, received undefined" }
],
"request_id": "req_…"
}
}details.feature with plan_required
A feature the agency's plan does not include is refused with 403 forbidden, details.reason: "plan_required", and details.feature, the id of the missing feature or add-on (for example chat, portal_branding, seo or rank_tracking). Use it to point the agency to the right upgrade instead of parsing the message.
{
"error": {
"code": "forbidden",
"message": "The sitemap tool requires the Pro plan or above.",
"details": { "feature": "seo", "reason": "plan_required" },
"request_id": "req_…"
}
}details.max and details.count with limits
A refusal because a ceiling is reached (limit_reached and the more specific *_limit_reached reasons) carries details.max, the ceiling, and details.count, the current number, when it is known:
{
"error": {
"code": "validation_failed",
"message": "This client already tracks 50 questions, the maximum. …",
"details": { "max": 50, "count": 50, "reason": "limit_reached" },
"request_id": "req_…"
}
}Accepted values
A 400 about a query parameter lists what the resource accepts: accepted, filterable or sortable. A 403 scope_missing lists required and granted. See Requests and responses and Authentication.