> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pineyard.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Every error the API returns, what it means and how to fix it.

Errors are returned with a `4xx` or `5xx` status and a JSON body containing a machine-readable `error` code:

```json theme={null}
{ "error": "campaign_no_phone_numbers" }
```

Some errors add fields:

| Field | When |
| - | - |
| `requiredPermission` | `403 forbidden`: the scope the key needs. |
| `message` | A human-readable explanation. Don't parse it; it may change. |
| `issues` or `detail` | Validation errors: the fields that failed and why. |
| `requestId` | Present on many errors. Quote it when contacting support. |
| `upstreamStatus` | `502 provider_error`: the status returned by the calling service. |

Branch on `error` (and the HTTP status), never on `message`.

<Note>
  One exception: `POST /v1/campaigns/{campaignId}/leads` returns `campaign_not_accepting` under the key `code` rather
  than `error`.
</Note>

## Retrying

| Status | Retry? |
| - | - |
| `400`, `401`, `403`, `404`, `409`, `410`, `422` | No. Fix the request first. |
| `402` | After topping up the wallet. |
| `429` | After `Retry-After` seconds. |
| `500`, `502`, `503` | Yes, with exponential back-off. On billed endpoints, reuse the same [`Idempotency-Key`](/concepts/idempotency). |

Each endpoint page in the API reference lists the errors that endpoint can return.

## Authentication & permissions

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 401 | `unauthorized` | No `Authorization: Bearer` header, or an empty token. | Send `Authorization: Bearer pk_live_…`. |
| 401 | `invalid_api_key` | The API key is unknown, revoked or expired, or its workspace was deleted. | Check the key; ask Pineyard for a new one if it was revoked or expired. |
| 403 | `api_key_not_scoped` | A key issued without scopes was used outside `/v1`. | Ask Pineyard to add scopes to the key, or use the `/v1` endpoints. |
| 403 | `api_key_owner_inactive` | The member the key acts for was removed or suspended. | Ask Pineyard to re-assign the key to an active member, or issue a new key. |
| 403 | `workspace_pending_approval` | The workspace has not been approved yet. | Wait for approval; contact support if it is taking long. |
| 403 | `workspace_suspended` | The workspace is suspended. | Contact support to reactivate the workspace. |
| 403 | `forbidden` | The key lacks the permission named in `requiredPermission`, or the resource belongs to another workspace. | Ask Pineyard to add the scope, or check that the member the key acts for has that permission. |
| 403 | `api_key_not_allowed` | This action needs a person signed in to the dashboard (sign-ins, payments, email, and similar). | Do it from the dashboard. |
| 403, 404 | `feature_not_available` | This feature is not enabled for your workspace or plan. | Contact Pineyard to enable it. |
| 403 | `prompt_access_forbidden` | Prompt text is not available to this caller. | Not available to API keys. |
| 403 | `operator_only` | Overriding lead metrics is reserved for Pineyard staff. | Contact support. |

## Requests, limits & server

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400, 422 | `invalid_body` | The request body (or query) failed validation. Details are in `issues` (or `detail`). `422` on `POST /v1/campaigns/{campaignId}/leads`. | Fix the listed fields and resend. |
| 400 | `invalid_query` | Query string failed validation. | Fix the query parameters listed in issues/detail. |
| 400 | `invalid_params` | Commerce query parameters invalid. | Fix the parameters listed in issues. |
| 400 | `invalid_request` | The input was rejected. | Correct the input described in `message`. |
| 404 | `not_found` | The resource does not exist in your workspace, or the feature is not enabled for it. | Check the ID and path. |
| 400 | `no_fields_to_update` | PATCH body empty. | Send at least one field. |
| 409 | `name_taken` | An agent, campaign or campaign type with that name already exists (unique constraint). | Choose a different name. |
| 409 | `concurrent_modification` | Resource changed concurrently (optimistic lock); workflows include currentRevision. | Reload and retry with the latest revision. |
| 429 | `rate_limited` | Too many requests. | Wait for `Retry-After` seconds, then retry. See [Rate limits](/concepts/rate-limits). |
| 500, 400, 404, 413 | `internal_error` | An unexpected error. A `400` with this code usually means the body is not valid JSON; a `413` means the body is larger than 1 MB. | Check the body. For `500`, retry, then contact support with the `requestId`. |
| 503 | `database_unavailable` | The service is temporarily unavailable. | Retry after `Retry-After` seconds. |
| 502 | `provider_error` | The calling service rejected the request (`upstreamStatus` 4xx) or was unavailable (5xx). | For 4xx, check the settings named in `message`. For 5xx, retry in a minute. |
| 500 | `LIMIT_FILE_SIZE` | The uploaded file is larger than the endpoint allows. Currently returned as `500`. | Upload a smaller file. |
| 413, 400 | `file_too_large` | The file is over the size limit. | Upload a smaller file. |
| 400 | `file_required` | Multipart request had no file. | Attach the file in the expected form field. |
| 400 | `no_files` | No files uploaded. | Attach files. |
| 400 | `upload_error` | Malformed multipart upload. | Send a single 'file' field. |
| 500 | `upload_failed` | Fallback when an upload error has no message. | Retry; contact support if persistent. |
| 502 | `suggestion_failed` | AI suggestion generation failed (any error). | Retry. |
| 422 | `rewrite_too_long` | `POST /campaigns/suggest-metric-prompt`: the AI-generated or rewritten metric prompt came out longer than the 5,000-character limit. | Try again, or shorten the original prompt. |

## Idempotency

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400, 422 | `idempotency_key_required` | A billed endpoint was called without an `Idempotency-Key` header (`400`), or a `/v1` lead push without `idempotencyKey` in the body (`422`). | Send a unique key per action. See [Idempotency](/concepts/idempotency). |
| 400 | `idempotency_key_invalid` | Idempotency-Key is over 200 chars or has non-printable or non-ASCII characters. | Use up to 200 printable ASCII characters. |
| 409 | `idempotency_in_progress` | A request with the same Idempotency-Key is still running. | Retry the same request after Retry-After (1s). |
| 422, 409 | `idempotency_key_reused` | The `Idempotency-Key` was already used for a different request (`422`). On `POST /calls/test`, `409` means a call with this key already exists. | Use a new key for a new request. |
| 503 | `idempotency_unavailable` | The idempotency check could not run. Nothing was done. | Retry shortly with the same key. |

## Leads & results (`/v1`)

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 409 | `campaign_not_accepting` | The campaign is not a running API campaign. Returned as `{"code": "campaign_not_accepting", "reason": …}`; `reason` is `not_api` or the campaign status. | Push leads only to a running campaign whose source is `API`. |
| 422, 400 | `invalid_phone` | Phone number could not be normalised. | Send a valid phone number, preferably E.164 (for example `+15555550100`). |
| 429 | `journey_campaign_live_cap` | Journey campaign has too many leads pending or in flight. | Retry the same push later. |
| 403 | `api_results_not_enabled` | API lead results are not enabled for this workspace. | Ask support to enable API results. |
| 404 | `lead_not_found` | No lead (campaign contact) with that id in this workspace or campaign. | Use the leadId returned when the lead was pushed. |
| 410 | `result_expired` | The retained result for this lead has expired. | Fetch results sooner, or rely on result webhooks. |
| 404 | `result_not_found` | No result with that id (or it expired). | Check the resultId. |

## Campaigns

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 404 | `campaign_not_found` | No campaign with that id in this workspace. | Check the campaign id. |
| 409 | `campaign_not_draft` | Campaign can only be changed in DRAFT (or PAUSED/CREDIT\_EXHAUSTED for edits; contacts only in DRAFT). | Pause the campaign, or create a new one. |
| 409 | `campaign_delete_active` | Only DRAFT, CANCELED or COMPLETED campaigns can be deleted. | Stop the campaign first. |
| 409 | `campaign_delete_import_in_progress` | A CRM import for this campaign is still pending or running. | Wait for the import to finish, then delete. |
| 400 | `campaign_not_running` | The campaign is not running (also returned for an unknown campaign ID). | Only pause running campaigns. |
| 400 | `campaign_not_paused` | UNCLEAR: mapped in the /v1 resume handler but never thrown by resumeCampaign/startCampaign in this snapshot. | See `message`. |
| 400 | `campaign_not_startable` | Campaign status is not DRAFT, PAUSED or CREDIT\_EXHAUSTED (or a journey or inbound campaign is in a non-startable state). | Only start or resume DRAFT/PAUSED campaigns; use restart for finished ones. |
| 400 | `invalid_reserved_concurrency` | reservedConcurrency value out of range. | Send a valid reservedConcurrency. |
| 402 | `workspace_not_active` | Workspace is not ACTIVE, so no billable activity is allowed. | Contact support to reactivate the workspace. |
| 402 | `activity_frozen` | Workspace activity is frozen. | Contact support or billing. |
| 402 | `insufficient_credits` | Wallet balance too low (and no active trial). | Top up the wallet and retry. |
| 404 | `workflow_not_found` | No workflow with that id (or workflows not enabled). | Check the workflowId and that workflows are enabled. |
| 409 | `workflow_not_active` | Workflow is not ACTIVE (not published or archived). | Activate (publish) the workflow first. |
| 400 | `campaign_workflow_not_active` | Journey campaign's workflow is missing or not ACTIVE at start. | Activate the workflow, then start the campaign. |
| 400 | `campaign_no_pending_contacts` | Campaign has no PENDING contacts to dial or enrol. | Add contacts before starting. |
| 400 | `campaign_schedule_expired` | Campaign's scheduledEndAt is in the past. | Extend or clear the schedule end date. |
| 400 | `campaign_is_inbound_misconfigured` | An outbound-style campaign uses an INBOUND agent and has no contacts. | Use an outbound agent, or create an inbound campaign on a number. |
| 400 | `campaign_channel_needs_workflow` | CUSTOM-channel campaign has no workflow attached. | Attach a workflow or change the channel. |
| 400 | `campaign_channel_needs_email_sender` | EMAIL campaign has no sender mailbox. | Attach an email sender. |
| 400 | `campaign_channel_needs_email_template` | EMAIL campaign has no email template. | Attach at least one email template. |
| 400 | `campaign_channel_needs_agent` | Voice-leg campaign has no agent. | Set agentId. |
| 400 | `campaign_channel_needs_sender` | Message-leg campaign has no WhatsApp sender. | Connect or select a WhatsApp sender. |
| 400 | `campaign_channel_needs_template` | Message-leg campaign has no outreach template, or the template id does not exist. | Set an approved outreach template. |
| 400 | `outreach_template_not_approved` | Outreach template is not APPROVED. | Get the template approved before starting. |
| 400 | `outreach_template_needs_runtime_context` | Outreach template has bindings that can only be filled at runtime. | Use a template without runtime-only bindings for first-touch outreach. |
| 400 | `campaign_no_phone_numbers` | Voice campaign has no phone numbers on an enabled carrier attached. | Attach phone numbers (POST /campaigns/\{id}/phone-numbers). |
| 400 | `reserved_exceeds_allocation` | Running campaigns would reserve more concurrent calls than your workspace has. | Lower `reservedConcurrency`, pause another campaign, or ask Pineyard for more capacity. |
| 400 | `inbound_did_unavailable` | Inbound campaign's phone number is missing, not assigned to this workspace, or its carrier is disabled. | Choose an assigned phone number. |
| 400 | `inbound_did_has_no_agent` | The number has no ACTIVE INBOUND agent assigned. | Assign an active inbound agent to the number. |
| 409 | `inbound_did_already_in_campaign` | The number already belongs to a non-terminal inbound campaign. | Stop the other inbound campaign or use another number. |
| 400 | `inbound_agent_has_no_number` | Inbound agent chosen but no number routes to it. | Assign a number to the inbound agent, or pass phoneNumberId. |
| 400 | `inbound_agent_has_many_numbers` | Inbound agent has several numbers and none was specified. | Pass phoneNumberId. |
| 400 | `inbound_campaign_action_not_supported` | Operation is not valid for INBOUND campaigns (uploads, pause/resume/restart, source switch, re-engage, remessage, outreach). | Do not perform this action on inbound campaigns. |
| 400 | `journey_campaign_source_unsupported` | Journey (workflow) campaigns only support CSV or API sources. | Use source CSV or API. |
| 400 | `journey_campaign_contact_cap` | Too many contacts for a journey campaign. | Split the contacts across campaigns. |
| 400 | `journey_campaign_action_unsupported` | Restart or contact-retry is not supported for journey campaigns. | Use workflow run controls instead. |
| 400 | `campaign_type_not_found` | campaignTypeId does not exist or is not ACTIVE. | Use an active campaign type id. |
| 400 | `reserved_extraction_key` | A custom extraction key uses a built-in signal name (key field included on the dashboard route). | Rename the field. |
| 400 | `reserved_metric_key` | An evaluation metric key uses a built-in signal name. | Rename the metric. |
| 400 | `metric_key_collision` | A metric key collides with an extraction field name. | Rename the metric or the field. |
| 400 | `default_crm_connection_required` | CRM\_WEBHOOK campaign created without defaultCrmConnectionId. | Pass defaultCrmConnectionId. |
| 409 | `campaign_source_locked` | Campaign source cannot change once it has contacts or API deliveries, or is not DRAFT. | Create a new campaign with the desired source. |
| 404, 400 | `agent_not_found` | Agent id missing or not in this workspace (400 on POST /v1/campaigns, inbound-number assignment and suggest routes). | Use an agent id from GET /agents or /v1/agents. |
| 400 | `phone_number_not_in_workspace` | Phone number id not owned or assigned to this workspace, or its carrier is disabled. | Use ids from GET /phone-numbers. |
| 409 | `campaign_running_no_remove` | Cannot detach a number from a RUNNING campaign. | Pause the campaign first. |
| 422 | `workflow_invalid` | Stored or submitted workflow definition fails parsing or validation (details included). | Fix the workflow per details and republish. |
| 404 | `upload_not_found_or_expired` | The `uploadId` is unknown or expired. On some endpoints currently returned as `internal_error`. | Upload the CSV again and use the new `uploadId`. |
| 422 | `excel_mangled_phone` | Phone column contains scientific-notation numbers (Excel lost the digits). | Format the phone column as text and re-export the CSV. |
| 400 | `invalid_csv_format` | The CSV needs a header row and at least one data row. Returned with the message as `error`. | Fix the CSV. |
| 415 | `unsupported_brief_type` | Brief file is not PDF, TXT or Markdown. | Upload a PDF, TXT or Markdown file. |
| 400 | `brief_required` | No brief text and no file content. | Paste text or upload a brief. |
| 422 | `brief_unusable` | A campaign could not be drafted from the brief. | Provide a fuller brief. |
| 409 | `campaign_not_editable` | Campaign status does not allow this live edit (schedule, outreach, metrics). | See `message`. |
| 409 | `campaign_not_reengageable` | Campaign is in a state where re-engage is not allowed. | See `message`. |
| 409 | `incomplete_retry_disabled` | Finish-interrupted requested but incompleteRetryEnabled is off. | Enable incomplete-call retry in the schedule first. |
| 409 | `campaign_channel_not_message_followup` | Remessage requested on a campaign without a message leg. | Use a campaign whose channel includes WhatsApp messaging. |
| 400 | `campaign_channel_switch_not_supported` | The requested channel switch is not allowed on outreach edit. | See `message`. |
| 400 | `campaign_channel_has_no_rules` | Outreach rules were sent for a channel that has none. | See `message`. |
| 404, 400 | `template_not_found` | Template id not in this workspace (400 in order-notification rules). | Use an existing template id. |
| 400 | `template_not_approved` | Template is not APPROVED. | Wait for approval or pick an approved template. |
| 400 | `campaign_has_no_journey` | Campaign does not run a workflow. | Use journey endpoints only on journey campaigns. |
| 404 | `workflow_run_not_found` | No workflow run for that lead or run id. | Check the id. |
| 400 | `no_extraction_data` | Campaign has no extraction data to analyse. | Wait for completed calls with extractions, then analyse. |
| 502 | `analysis_failed` | Campaign analysis generation failed. | Retry. |
| 404 | `contact_not_found` | Campaign contact not found. | Check contactId. |
| 400 | `campaign_not_active` | Retry requested but the campaign is not active. | Resume or start the campaign first. |
| 400 | `contact_not_failed` | Only FAILED contacts can be retried. | Retry only failed contacts. |
| 400 | `campaign_not_in_draft` | Adding contacts or CRM import requires a DRAFT campaign (or a live CRM\_WEBHOOK campaign for imports). | Create a new draft campaign. |
| 400 | `empty_message` | Chat message is blank. | Send a non-empty message. |
| 429 | `message_limit_reached` | Campaign chat session hit its fair-use message cap. | Start a new campaign conversation. |
| 500 | `chat_failed` | Chat send failed (also returned for invalid input on non-stream POST /campaigns/:id/chat). | Retry. |
| 410 | `expired` | Export job artifact has expired. | Create a new export. |
| 400 | `campaign_already_terminal` | Stop requested on an already finished campaign. | No action needed. |
| 400 | `campaign_not_restartable` | Only stopped (`COMPLETED` or `CANCELED`) campaigns can be restarted. | Stop the campaign first, or use `resume` for a paused one. |
| 400 | `agent_not_found_or_not_provisioned` | Agent missing, not ACTIVE, or not provisioned. | Use an active, provisioned agent. |
| 422 | `excel_row_limit_exceeded` | Too many rows for an XLSX export. | Choose CSV or a shorter range. |
| 409 | `contact_opted_out` | Contact opted out of messages. | Do not message this contact. |

## Agents, calls & phone numbers

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400 | `prompt_required` | Agent create has no prompt after assembly. | Provide a prompt or studioConfig. |
| 400 | `invalid_transfer_number` | Transfer number is not E.164. | Use E.164, for example `+15555550100`. |
| 400 | `transfer_number_required` | Transfer enabled without a number. | Set a transfer number or disable transfer. |
| 404 | `background_audio_not_found` | Background audio id not owned by this workspace or not available. | Use an id from GET /background-audio. |
| 409 | `agent_limit_reached` | Agent quota reached on duplicate. | Remove an unused agent or contact support. |
| 409 | `in_use` | Agent is referenced by campaigns, so it cannot be permanently deleted. | Archive instead, or remove the campaigns. |
| 500 | `sandbox_from_number_not_configured` | Test calls without your own number are not available. | Pass `fromPhoneNumberId`. |
| 400, 409 | `agent_not_provisioned` | The agent is still being set up. | Wait a moment, or save the agent again. |
| 503 | `sandbox_from_number_unavailable` | The shared test number is unavailable. | Retry later, or pass `fromPhoneNumberId`. |
| 501 | `web_session_not_supported` | Web test sessions are not available. | Use a phone test call. |
| 501 | `not_supported` | This operation is not available for your workspace. | Contact Pineyard. |
| 404 | `dictionary_not_found` | Dictionary not found. | Check the id. |
| 404 | `function_not_found` | Function not found. | Check the id. |
| 400 | `invalid_export_period` | period is not one of today, last\_7\_days, this\_month, last\_month, all\_time, custom. | Use a supported period. |
| 400 | `invalid_export_range` | Custom range missing from/to, or to ≤ from. | Send valid from \< to. |
| 400 | `unknown_metric` | Metric key is not defined for this campaign. | Use a defined metric key. |
| 400 | `invalid_value` | Value is not in the metric's allowed list. | Use one of 'allowed'. |
| 400 | `no_transcript` | Call has no transcript to translate. | Only translate calls with transcripts. |
| 409 | `no_contact_for_call` | No contact is linked to this call. | See `message`. |
| 409 | `phone_number_released_upstream` | The caller-ID number is no longer available. | Use a different phone number. |
| 409 | `do_not_call` | Destination is on the do-not-call list. | Remove it from DNC or call another number. |
| 404 | `phone_number_not_found` | Phone number not in this workspace. | Check the id. |
| 409 | `phone_number_not_synced` | The phone number is still being set up. | Retry later. |
| 400 | `agent_archived` | Agent is archived. | Use an active agent. |
| 400 | `agent_type_not_inbound` | Agent is not INBOUND type. | Use an inbound agent. |
| 501 | `preview_not_supported` | A preview is not available for this voice. | Pick another voice. |
| 404 | `call_not_found` | Speech analysis or dialer call not found. | Check the id. |

## Documents, chatbots & knowledge bases

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 404 | `document_not_found` | Document not found. | Check the id. |
| 400 | `no_external_id` | The document upload was not completed. | Start the upload again. |
| 400 | `document_not_provisioned` | The document upload has not been confirmed. | Confirm the upload first. |
| 404 | `chatbot_not_found` | Chatbot not found. | Check the id. |
| 404 | `knowledge_base_not_found` | Knowledge base not found or not owned. | Check the knowledge base ids. |
| 409 | `knowledge_base_in_use` | Knowledge base is attached to a live chatbot. | Detach it first. |
| 409 | `rotation_conflict` | Another key rotation won the race. | Re-read the chatbot and retry. |
| 404 | `session_not_found` | Chat session not found. | Check sid. |
| 413 | `document_too_large` | KB document over the size limit. | Upload a smaller file. |
| 400 | `empty_document` | KB document is empty. | Upload a non-empty file. |
| 415 | `unsupported_document_type` | KB document type not supported. | Upload a supported document type. |
| 503 | `document_storage_unavailable` | Document storage is unavailable. | Contact support. |
| 502 | `document_storage_failed` | Storing the document failed. | Retry. |
| 502 | `document_ingest_unavailable` | The document could not be queued for processing. | Retry. |
| 404 | `kb_document_not_found` | KB document not found. | Check docId. |

## Billing

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400 | `invalid_cursor` | Ledger cursor invalid. | Use nextCursor from the previous page. |
| 400 | `invalid_custom_range` | Custom ledger range missing or reversed. | Send from \< to. |
| 409 | `no_recharge_found` | 'Since last recharge' range requested but no recharge exists. | Choose another range. |
| 400 | `invalid_order_id` | orderId path param invalid. | Use a valid order id. |
| 404 | `recharge_not_found` | Recharge not found in this workspace. | Check orderId. |
| 409 | `receipt_not_available` | Recharge not CREDITED yet. | Wait until payment is credited. |

## CRM & integrations

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400, 404 | `crm_connection_not_found` | CRM connection not found (400 on campaign create: missing or not ACTIVE; 404 on CRM routes). | Use an active CRM connection id. |
| 400 | `crm_connection_not_active` | CRM connection exists but is not ACTIVE. | Reconnect the CRM. |
| 404 | `unknown_crm` | CRM slug not recognised. | Use a slug from GET /crm/providers. |
| 400 | `provider_not_api_key` | This CRM uses OAuth, not API-key connect. | Use the OAuth connect flow. |
| 400 | `invalid_credentials` | The credentials for the connected service (CRM, integration or messaging account) were rejected. `message` has details. | Re-enter valid credentials. |
| 404 | `crm_resource_not_found` | The CRM returned not found. | Check the resource or segment in the CRM. |
| 502 | `crm_upstream_unavailable` | CRM unreachable. | Retry later. |
| 502 | `crm_upstream_error` | Unexpected CRM response. | Retry; contact support if persistent. |
| 400 | `webhooks_not_supported` | This CRM does not support webhooks. | Use manual imports. |
| 400 | `invalid_mapping_target` | Field mapping target invalid. | See `message`. |
| 400 | `invalid_mapping_source` | Field mapping source invalid. | See `message`. |
| 404 | `crm_import_not_found` | CRM import not found. | Check the import id. |
| 400 | `template_or_preset_required` | Order-notification rule has neither template nor preset. | Set templateId or preset. |
| 400 | `invalid_redirect_uri` | Integration redirect URL not allowed. | Use an allowed redirect URL. |
| 400 | `tool_requires_oauth` | Integration uses OAuth, not API key. | Use the connect link flow. |
| 400 | `invalid_tool` | Integration slug not available. | Use a listed integration. |
| 400 | `invalid_input` | Integration input rejected. | Fix the input per message. |
| 409 | `workspace_archived` | Integration sub-account archived. | Contact support. |

## Calendar & live sheets

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400 | `google_oauth_not_configured` | Google OAuth client not configured. | Contact support. |
| 400 | `google_not_connected` | No Google Calendar connection. | Connect Google Calendar first. |
| 400 | `google_token_unavailable` | Stored Google token has no refresh token. | Reconnect Google. |
| 400 | `sheets_not_connected` | No active Google Sheets connection. | Connect Google Sheets. |
| 400 | `invalid_sheet_url` | Spreadsheet URL not parseable. | Paste a full Google Sheets URL. |
| 409 | `sheet_already_linked` | Campaign already has a sheet. | Unlink first. |
| 404 | `sheet_not_linked` | Campaign has no sheet link. | Create or attach a sheet first. |
| 400 | `sheet_rebuild_attached_forbidden` | Attached (client-owned) sheets cannot be rebuilt. | Only rebuild generated sheets. |
| 502 | `sheet_rebuild_tab_not_created` | New tab creation failed on rebuild. | Retry. |
| 400 | `sheet_columns_required` | Layout has no columns. | Add columns. |
| 400 | `sheet_phone_column_required` | Layout lacks a phone column. | Include the phone column. |
| 400 | `sheet_column_label_required` | A column has an empty label. | Label every column. |
| 400 | `duplicate_sheet_column` | Duplicate column key. | Remove the duplicate. |
| 400 | `duplicate_sheet_header` | Duplicate header label. | Use unique labels. |
| 400 | `unknown_sheet_column` | Column key not in the field catalog. | Use keys from /sheet/configuration. |
| 400 | `attached_identity_column_not_writable` | Identity column not writable on attached sheets. | Remove that column. |
| 400 | `sheet_match_column_required` | Attached sheet needs a valid matchColumn. | Pass a matchColumn that exists in the header. |
| 400 | `sheet_header_collision` | Managed labels collide with existing sheet headers. | Rename the columns. |
| 409 | `sheet_match_column_missing` | Match column no longer in the sheet. | Restore the column or update matchColumn. |
| 409 | `sheet_lead_id_missing` | Sheet rows lack lead ids. | See `message`. |
| 400 | `sheet_not_found` | Google says the spreadsheet does not exist. | Check the link. |
| 400 | `sheet_forbidden` | Connected Google account cannot access the sheet. | Share the sheet with the connected account. |
| 400 | `google_sheets_error` | Other Google Sheets write or read failure. | Read message; retry. |

## Result webhooks

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 404 | `webhook_endpoint_not_found` | Result webhook endpoint not found. | Check the id. |
| 404 | `webhook_delivery_not_found` | Delivery not found. | Check the id. |

## WhatsApp

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 502 | `llm_not_configured` | AI analysis is not available right now. | Contact support. |
| 404 | `sender_not_found` | WhatsApp sender not found. | Check the id. |
| 400 | `cannot_disconnect_shared` | Shared or platform senders cannot be disconnected. | None. |
| 400 | `tag_required` | Collection tag missing. | Provide a tag. |
| 404 | `collection_not_found` | Media collection not found. | Check the id. |
| 404 | `media_item_not_found` | Media item not found. | Check the id. |
| 404 | `item_not_found` | Media item not found. | Check itemId. |
| 400 | `invalid_url` | Media URL not https. | Use an https URL. |
| 400 | `invalid_kind` | TEXT is not a media kind. | Use IMAGE, DOCUMENT, VIDEO or AUDIO. |
| 400 | `unsupported_type` | File type not allowed. | Upload image, document, video or audio. |
| 404 | `preset_not_found` | Template preset not found. | Check the id. |
| 409 | `template_already_exists` | Template name already exists. | Choose another name. |
| 400 | `template_sender_invalid` | Template's sender is invalid. | Pick a valid sender. |
| 400 | `template_already_approved` | Template is already approved. | No action needed. |
| 400 | `template_invalid` | Template components fail validation (issues included). | Fix the issues and resubmit. |
| 400 | `template_media_missing` | Media-header template has no uploaded attachment. | Upload the attachment first. |
| 400 | `template_media_review_failed` | The attachment was saved but could not be prepared for review. | Re-upload the attachment and submit again. |
| 400 | `status_is_provider_managed` | Template status is synced automatically and cannot be set by hand. | Use `POST /whatsapp/templates/{id}/sync-status`. |
| 400 | `no_file` | No file uploaded for template media. | Attach a file. |
| 404 | `conversation_not_found` | Conversation not found. | Check the id. |
| 400 | `text_required` | Message body empty. | Provide text. |
| 400 | `template_required` | Free-form text can't be sent to this conversation right now. | Send an approved template. |
| 400 | `unsupported_media_type` | Media type not sendable. | Use a supported type. |
| 400 | `media_too_large` | Media exceeds the per-kind limit. | Send a smaller file. |
| 400 | `window_closed` | 24h customer-service window closed. | Send a template message instead. |
| 400 | `no_sender_available` | No usable WhatsApp sender for the workspace. | Connect a sender. |
| 400 | `template_sender_mismatch` | The template was approved for a different WhatsApp business account. | Use a template approved for this sender. |
| 400 | `collection_empty` | Collection has no items. | Add items. |
| 429 | `send_cap_reached` | Daily WhatsApp send cap reached. | Retry tomorrow or contact support. |
| 404 | `message_not_found` | Message not found. | Check the id. |
| 409 | `message_not_outbound` | Only outbound messages can be retried. | None. |
| 409 | `message_not_failed` | Only FAILED messages can be retried. | None. |
| 409 | `sender_unavailable` | Original sender gone or revoked. | Reconnect a sender and send anew. |
| 409 | `message_already_retried` | Message already retried. | Retry the newer message. |
| 400 | `template_not_configured` | Campaign or flow has no template configured. | Configure a template. |
| 500 | `template_binding_unresolved` | A template variable could not be filled. Currently returned as `500`. | Provide the lead fields the template uses. |
| 402 | `provider_wallet_empty` | The WhatsApp messaging account has no balance. | Contact Pineyard. |
| 403 | `account_restricted` | The WhatsApp messaging account is restricted. | Contact Pineyard. |
| 502 | `upstream_unavailable` | A connected service (messaging or speech) is unavailable. | Retry later. |
| 500 | `invalid_response` | A connected service returned an unusable response. | Retry. |
| 500 | `unknown` | Unclassified upstream failure. | Retry; contact support. |
| 400 | `invalid_whatsapp_media_templates` | Some agent templates are not approved or have no media. | Use approved templates with media (templateIds lists the rejected ones). |
| 400 | `invalid_whatsapp_payment_template` | Payment template missing, not approved, or lacks a payment-link button. | Pick an approved template with a payment-link button. |

## Speech analysis

| Status | `error` | Meaning | Fix |
| - | - | - | - |
| 400 | `files_required` | Bulk upload without files. | Attach files. |
| 400 | `file_empty` | Uploaded recording is empty. | Upload a non-empty file. |
| 404 | `rep_not_found` | Rep not found. | Check the id. |
| 404 | `scorecard_not_found` | Scorecard not found. | Check the id. |
| 409 | `not_retryable` | Only FAILED analyses can be retried. | None. |
| 409 | `audio_missing` | Stored recording gone. | Upload again. |
| 400 | `name_required` | Team or rep name missing. | Provide a name. |
| 400 | `email_required` | Rep email missing. | Provide an email. |
| 409 | `team_exists` | Team name already used. | Choose another name. |
| 404 | `team_not_found` | Team not found. | Check the id. |
| 409 | `rep_exists` | Rep email already used. | Use another email. |
| 502 | `provider_no_member_id` | The rep could not be created. | Retry. |
| 400 | `call_required` | Neither callId nor campaignContactId was given. | Pass one. |
| 409 | `already_analysed` | Call already analysed. | None. |
| 422 | `no_recording` | Call has no available recording. | Only analyse recorded calls. |
| 422 | `no_agent` | Call has no agent. | None. |
| 502 | `recording_fetch_failed` | Recording download failed. | Retry. |
| 503 | `not_configured` | Speech analysis is not available right now. | Contact support. |
| 502 | `conflict` | Speech analysis could not complete the request. | Retry; contact support if it persists. |
| 502 | `upstream_error` | Speech analysis failed unexpectedly. | Retry. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.