# Errors

The error response body, what each HTTP status means, how to read validation errors, and every error code the API returns.

The API uses standard HTTP status codes, and every error response has the same JSON body.

## The error body

```json
{
  "success": false,
  "error": "VALIDATION_ERROR",
  "message": "Request validation failed. Check the errors for details.",
  "details": {
    "errors": [
      { "field": "lead.first_name", "message": "Field required", "type": "missing" }
    ]
  },
  "request_id": "3b0f6c1e-8a2d-4e7b-9c5a-2d1f0e9b8a7c"
}
```

| Field | Meaning |
| - | - |
| `success` | Always `false` on an error. |
| `error` | A stable code for the kind of error. Branch on this. |
| `message` | A human-readable explanation. Its wording can change, so don't parse it. |
| `details` | Extra context for some errors, such as the failing fields of a validation error. Not always present. |
| `request_id` | The request's ID, also sent as the `X-Request-ID` header. Quote it when you contact support. |

## Status codes

| Status | Meaning | Retry? |
| - | - | - |
| `400` | The request is invalid. | No. Fix the request first. |
| `401` | The API key is missing, invalid, revoked or expired. | No. |
| `403` | The key lacks the required scope, or the organization is deactivated. | No. |
| `404` | The endpoint, or the resource in your organization, doesn't exist. | No. |
| `405` | The endpoint doesn't accept this HTTP method. | No. |
| `409` | A conflict: the resource already exists, or it changed since you read it. | For `IDEMPOTENCY_IN_PROGRESS`, after `Retry-After`. For `SEQUENCE_MODIFIED`, after reading the campaign again. |
| `413` | The request body is larger than 1 MB. | No. |
| `429` | You've hit a rate limit. | Yes, after `Retry-After`. See [Rate limits](https://docs.versionseven.ai/guides/rate-limits). |
| `500` | Something failed on our side. | Yes, with backoff. Send an [`Idempotency-Key`](https://docs.versionseven.ai/guides/idempotency) on create requests so a retry is safe. |
| `503` | A dependency timed out, or the key couldn't be checked. | Yes, with backoff. |

## Validation errors

A request that doesn't match the endpoint's schema answers `400 VALIDATION_ERROR`, and `details.errors` lists every problem. Each entry has:

- `field`: the path to the field, with nested fields joined by dots, such as `lead.first_name`. It's `request` when the problem isn't tied to one field.
- `message`: what's wrong with the value.
- `type`: a short machine-readable reason, such as `missing`.

Common causes are a missing required field, a value of the wrong type, a string longer than 2,000 characters, and a number out of range, such as a `limit` above the endpoint's maximum.

## IDs that don't exist

- A path ID that isn't a valid UUID answers `404 NOT_FOUND`.
- An ID in a request body or query string that isn't a valid UUID, such as `campaign_id` or `stage_id`, answers `400 VALIDATION_ERROR`.
- A path ID for a resource that isn't in your organization answers that resource's not-found code, such as `404 CAMPAIGN_NOT_FOUND`.
- An ID in a request body that isn't in your organization answers `400`, such as `400 INVALID_STAGE`. This way a response never confirms an ID that belongs to another organization.

## Error codes

Every code the API can return. Each endpoint's reference page lists the codes that endpoint can return.

| Code | Status | Category | Meaning |
| - | - | - | - |
| `VALIDATION_ERROR` | 400 | Request | The request didn't match the endpoint's schema: a missing or malformed field, a bad query parameter, or a value out of range. `details.errors` lists each failing field with its `field`, `message` and `type`. |
| `BAD_REQUEST` | 400 | Request | The request was malformed in a way no more specific code covers. |
| `NO_UPDATES` | 400 | Request | The update request contained no fields to change. |
| `NO_SEQUENCE` | 400 | Request | The campaign has no sequence, so there's no step to update. |
| `SEQUENCE_VALIDATION_FAILED` | 400 | Request | The sequence breaks a structural rule, such as an unknown step type or a `linkedin_connection` step without a `conditional` after it. The message lists every rule that failed. |
| `INVALID_WEBHOOK_URL` | 400 | Request | The webhook URL must use `https` and resolve to a public address. |
| `INVALID_DATE_FILTER` | 400 | Request | `date_filter` must be a number of days such as `30d`, or an ISO 8601 timestamp. |
| `INVALID_VARIATION` | 400 | Request | `variation` must be one of `a`, `b`, `unassigned` or `all`. |
| `INVALID_LEAD` | 400 | Request | The `lead_id` in the request body isn't a lead in your organization. This is a `400` rather than a `404` so the response never confirms an ID that belongs to another organization. |
| `INVALID_STAGE` | 400 | Request | The `stage_id` in the request body isn't a stage in your organization. This is a `400` rather than a `404` for the same reason as `INVALID_LEAD`. |
| `INVALID_OWNER` | 400 | Request | The `owner_id` in the request body isn't a member of your organization. This is a `400` rather than a `404` for the same reason as `INVALID_LEAD`. |
| `INVALID_ACCOUNT` | 400 | Request | A sender account ID in the request isn't a connected account in your organization. Answered when a connected assistant assigns sender accounts to a campaign. This is a `400` rather than a `404` for the same reason as `INVALID_LEAD`. |
| `INVALID_WINDOW` | 400 | Request | The daily-stats window is unparseable, has from after to, or spans more than 400 days. |
| `INVALID_GROUP_BY` | 400 | Request | group\_by must be none, sender or variation. |
| `PAYLOAD_TOO_LARGE` | 413 | Request | The request body is larger than 1 MB. |
| `UNAUTHORIZED` | 401 | Authentication | The `Authorization` header is missing or malformed, or the API key is unknown or has been deactivated. |
| `API_KEY_EXPIRED` | 401 | Authentication | The API key is past its expiry date. Create a new key in the Victoria AI app. |
| `FORBIDDEN` | 403 | Authentication | This API key isn't allowed to make the request. |
| `INSUFFICIENT_SCOPE` | 403 | Authentication | The API key doesn't have the scope this endpoint requires, such as `leads:write`. |
| `ORGANIZATION_DEACTIVATED` | 403 | Authentication | The organization that owns this API key has been deactivated. |
| `NO_DEFAULT_ORGANIZATION` | 403 | Authentication | You belong to more than one organization and none is set as the default for connected assistants. Pick one on the consent screen, in Settings → Connect your AI, or with the switch\_organization tool. |
| `NO_ACTIVE_SUBSCRIPTION` | 403 | Authentication | The organization doesn't have an active subscription. |
| `TRIAL_LEAD_CAP_REACHED` | 403 | Authentication | The organization is on a free trial and has used up its lead quota, so the lead wasn't created. `details` carries the `cap`, the number `used` and the `remaining` count. Subscribe in the Victoria AI app to add more. |
| `NOT_FOUND` | 404 | Not found | No endpoint matches the path. A resource ID in the path that isn't a valid UUID also answers `NOT_FOUND`. |
| `CAMPAIGN_NOT_FOUND` | 404 | Not found | No campaign with this ID exists in your organization. |
| `LEAD_NOT_FOUND` | 404 | Not found | No lead with this ID exists in your organization. |
| `LEAD_NOT_IN_CAMPAIGN` | 404 | Not found | The lead isn't enrolled in the campaign given by `campaign_id`. |
| `DEAL_NOT_FOUND` | 404 | Not found | No deal with this ID exists in your organization. |
| `PIPELINE_NOT_FOUND` | 404 | Not found | No pipeline with this ID exists in your organization. |
| `WEBHOOK_NOT_FOUND` | 404 | Not found | No webhook with this ID exists on the campaign. |
| `STEP_NOT_FOUND` | 404 | Not found | No step with this ID exists in the campaign's sequence, including inside conditional branches. |
| `CONVERSATION_NOT_FOUND` | 404 | Not found | No conversation with this ID exists in your organization. |
| `AI_FIELD_NOT_FOUND` | 404 | Not found | No AI field with this ID exists on the campaign. |
| `METHOD_NOT_ALLOWED` | 405 | Not found | The path exists but doesn't accept this HTTP method. |
| `LEAD_ALREADY_EXISTS` | 409 | Conflict | A lead with the same email or LinkedIn URL already exists in your organization. From [Create a lead](https://docs.versionseven.ai/api-reference/leads/create-lead), this means the request named no `campaign_id` to enrol the existing lead in, or raced an identical request. Changing a lead's email or LinkedIn URL to another lead's answers it too. When it's known, `details.existing_lead_id` identifies the existing lead. |
| `LEAD_ALREADY_IN_CAMPAIGN` | 409 | Conflict | The lead already exists in your organization and is already enrolled in the campaign given by `campaign_id`, so there's nothing to do. `details` carries the lead's `existing_lead_id`, the `campaign_id` and the enrolment's `sequence_lead_id`. |
| `LEAD_SUPPRESSED` | 409 | Conflict | The contact is on your organization's do-not-contact list, so the lead wasn't created, enrolled or updated. `details.suppressed_by` is `email`, `domain` or `linkedin_url`, and `details.value` is the entry that matched. |
| `DUPLICATE_WEBHOOK` | 409 | Conflict | An active webhook for this URL already exists on the campaign. `details.webhook_id` identifies it. |
| `WEBHOOK_SLOT_TAKEN` | 409 | Conflict | A campaign has one response webhook, and a different URL already holds this campaign's. `details.webhook_id` identifies it. Send `replace: true` to repoint that webhook at the new URL; the previous receiver then stops getting this campaign's events. |
| `CAMPAIGN_NOT_READY` | 409 | Conflict | The campaign can't be activated: at least one readiness check is blocking, such as a step missing content, a variable with no personalization field, an unassigned or unauthenticated sender, or no enrolled leads. `details.checks` lists every check with its status. |
| `CAMPAIGN_ACTIVATION_WARNINGS` | 409 | Conflict | The campaign's readiness checks passed with warnings only. `details.checks` lists them. Repeat the request with `ack_warnings: true` to activate anyway. |
| `SEQUENCE_MODIFIED` | 409 | Conflict | The sequence changed after it was read. Retrieve the campaign again and retry. |
| `STEP_ID_AMBIGUOUS` | 409 | Conflict | More than one step in the sequence has this ID, so the update can't tell which one to change. |
| `VARIATION_B_MISSING` | 409 | Conflict | The campaign's sequence has no `variation_b` with steps, so there's nothing to test against variation A. Write one with [Replace a campaign's sequence](https://docs.versionseven.ai/api-reference/campaigns/replace-sequence) before turning A/B testing on or promoting a winner. |
| `AB_TESTING_DISABLED` | 409 | Conflict | A/B testing is off for this campaign, so there's no test to promote a winner from. Turn it on with [Turn A/B testing on or off](https://docs.versionseven.ai/api-reference/campaigns/set-ab-testing) first. |
| `ACCOUNT_NOT_CONNECTED` | 409 | Conflict | A sender account being assigned to the campaign has disconnected. Reconnect it in the Victoria AI app or with a [reconnect link](https://docs.versionseven.ai/api-reference/accounts/create-connect-link) first. `details.account_ids` lists the accounts. |
| `ACCOUNT_IN_USE` | 409 | Conflict | A sender account sends for one active campaign at a time, and one being assigned is already used by another active campaign. `details.conflicts` names each account and the campaign holding it. |
| `ACCOUNT_LIMIT_REACHED` | 409 | Conflict | Connecting a new sender account would exceed the organization's seats for that platform. `details` carries the `max` and the number `used`. Free a seat, add one in the Victoria AI app, or reconnect an existing account instead. |
| `AI_FIELD_EXISTS` | 409 | Conflict | An active AI field with this name already exists on the campaign. Names are compared without regard to case. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | Conflict | This `Idempotency-Key` was already used with a different request. Use a new key for each distinct request. |
| `IDEMPOTENCY_IN_PROGRESS` | 409 | Conflict | The first request with this `Idempotency-Key` is still running. Retry after the `Retry-After` interval. |
| `RATE_LIMITED` | 429 | Rate limits | Too many requests for this API key: more than 100 a minute to one endpoint, or 600 a minute in total. Retry after the number of seconds in the `Retry-After` header. See [Rate limits](https://docs.versionseven.ai/guides/rate-limits). |
| `DAILY_SPEND_LIMIT` | 429 | Rate limits | Lead-database spend from this connection (an AI connector or API key) would pass the organization's rolling 24-hour limit (5,000 credits by default). `details` carry `daily_limit_credits`, `spent_credits`, `remaining_credits` and `call_ceiling_credits`. Ask for fewer leads or try later; the Lead Database page and the in-app Copilot are not limited. |
| `INTERNAL_ERROR` | 500 | Server | Something failed on our side. The response never includes internal details; quote its `request_id` when you contact support. |
| `ORG_HAS_NO_MEMBERS` | 500 | Server | The organization has no members, so the campaign can't be created. Contact support. |
| `UPSTREAM_TIMEOUT` | 503 | Server | A service the API depends on timed out. The request is safe to retry. |
| `AUTH_UNAVAILABLE` | 503 | Server | The API key couldn't be checked because the authentication service was unavailable. The request is safe to retry. |
| `HTTP_ERROR` | 500 | Server | An HTTP error that no more specific code covers. |
