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

# Error Reference

> Every v2 API error code, the HTTP status it ships with, and how to act on it.

The v2 API surfaces every error as an [RFC 7807 ProblemDetails](https://datatracker.ietf.org/doc/html/rfc7807) document, served as `application/problem+json`. Switch on the `code` extension — it's part of the contract and won't move under your feet. The `title` and `detail` strings are human-readable and may be reworded between releases.

## Response shape

```json theme={null}
{
  "type": "https://docs.contraforce.com/api-reference/errors",
  "title": "Resource not found",
  "status": 404,
  "detail": "Incident 'INC-12345' not found",
  "instance": "/api/v2/workspaces/6eca6a1f-b7d1-4bb8-a055-35ad6bb4b9b1/incidents/Sentinel/INC-12345",
  "code": "NOT_FOUND",
  "requestId": "0HNLBAGCRD4RN:00000003",
  "timestamp": "2026-04-15T20:00:00.0000000+00:00"
}
```

| Field       | Source              | Description                                                                                                                                                                     |
| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`      | RFC 7807            | URI identifying the problem class. Points at this reference page (`https://docs.contraforce.com/api-reference/errors`); the `code` extension is the programmatic discriminator. |
| `title`     | RFC 7807            | Short, stable per-`code` summary.                                                                                                                                               |
| `status`    | RFC 7807            | HTTP status code, mirrored in the response status line.                                                                                                                         |
| `detail`    | RFC 7807            | Per-instance message describing what went wrong.                                                                                                                                |
| `instance`  | RFC 7807            | URI of the failing request.                                                                                                                                                     |
| `code`      | extension           | **Stable identifier you should switch on.** See the catalogue below.                                                                                                            |
| `requestId` | extension           | Trace identifier for the failed request. Quote this when contacting support.                                                                                                    |
| `timestamp` | extension           | UTC ISO-8601 timestamp the failure was generated at.                                                                                                                            |
| `target`    | extension, optional | Field or identifier the error refers to.                                                                                                                                        |
| `errors`    | extension, optional | Field-keyed validation error map. Empty key means an object-level error.                                                                                                        |

## Validation responses

When binding fails or a [FluentValidation](https://docs.fluentvalidation.net/) rule rejects, the response includes an `errors` map:

```json theme={null}
{
  "type": "https://docs.contraforce.com/api-reference/errors",
  "title": "Validation failed",
  "status": 400,
  "detail": "One or more validation errors occurred.",
  "instance": "/api/v2/workspaces/.../incidents/Sentinel/INC-12345/comments",
  "code": "VALIDATION_ERROR",
  "errors": {
    "content": ["'Content' must not be empty."],
    "extensionId": ["'Extension Id' is required."],
    "": ["At least one of source or workspaceId must be provided."]
  },
  "requestId": "0HNLBAGCRD4RN:00000007",
  "timestamp": "2026-04-15T20:00:00.0000000+00:00"
}
```

Keys are JSON property paths; the empty key carries object-level errors that aren't tied to a specific field.

***

## Error code catalogue

<Tabs>
  <Tab title="4xx — caller errors">
    | Code                          | Status | When you see it                                                                                                                     | What to do                                                                                                                   |
    | ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
    | `VALIDATION_ERROR`            | 400    | A required field is missing, malformed, or rejected by validation. The `errors` extension carries the per-field detail.             | Inspect the `errors` map. Empty keys map to object-level errors; named keys map to JSON paths.                               |
    | `BAD_REQUEST`                 | 400    | A 400 not classified as a validation failure (e.g. a malformed body the framework couldn't parse at all).                           | Inspect `detail`. Confirm the body matches the documented shape.                                                             |
    | `UNAUTHORIZED`                | 401    | Missing or invalid `Authorization` header, revoked or rotated `clientSecret`, or a disabled service account.                        | Re-authenticate with valid credentials. If you rotated, update both halves of the credential.                                |
    | `FORBIDDEN`                   | 403    | Authenticated, but the calling principal can't perform this action on this resource.                                                | Check that the credential carries the scope the endpoint requires and that the service account has the right workspace role. |
    | `INSUFFICIENT_SCOPE`          | 403    | The credential is missing one of the scopes required by the endpoint.                                                               | Add the scope to the credential in the portal. Scopes follow the `{resource}:{action}` convention.                           |
    | `INSUFFICIENT_WORKSPACE_ROLE` | 403    | The calling principal has no role on the target workspace.                                                                          | Have an organization admin assign a workspace role to the service account.                                                   |
    | `MFA_REQUIRED`                | 403    | The user-flow path requires MFA from the configured IdP.                                                                            | Complete the MFA challenge in the portal. Service accounts do not encounter this code.                                       |
    | `USER_NOT_REGISTERED`         | 403    | The authenticated user is not registered in the ContraForce user store.                                                             | An organization admin must sync or invite the user before they can call the API.                                             |
    | `EXTENSION_NOT_ENABLED`       | 400    | The endpoint requires an integration (e.g. Defender XDR, Jira) that hasn't been consented and enabled for this workspace.           | Enable the integration via the workspace configuration endpoints or in the portal.                                           |
    | `NOT_FOUND`                   | 404    | A workspace, incident, gamebook, or other addressed resource doesn't exist or isn't visible to the credential.                      | Verify the IDs in the path. For workspace-scoped routes, confirm the workspace is mapped to the service account.             |
    | `METHOD_NOT_ALLOWED`          | 405    | Wrong HTTP verb on the route.                                                                                                       | Check the [endpoint reference](/api-reference/endpoints) for the correct verb.                                               |
    | `CONFLICT`                    | 409    | The resource version on the server differs from yours, the same operation was already performed, or two concurrent writes collided. | Refetch the resource and retry with the latest state.                                                                        |
    | `UNSUPPORTED_MEDIA_TYPE`      | 415    | Wrong `Content-Type` header for the body shape.                                                                                     | Use `application/json` for JSON requests, `multipart/form-data` for SOP uploads.                                             |
    | `RATE_LIMITED`                | 429    | Too many requests in the rolling window.                                                                                            | Honor `Retry-After` if present, otherwise back off exponentially.                                                            |
  </Tab>

  <Tab title="5xx — service errors">
    | Code             | Status | When you see it                                                                                                                                                                                                                         | What to do                                                                                                     |
    | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
    | `INTERNAL_ERROR` | 500    | An unhandled exception.                                                                                                                                                                                                                 | Retry once; if persistent, contact support and quote the `requestId`.                                          |
    | `UPSTREAM_ERROR` | 502    | A downstream SIEM or EDR returned a 5xx, or our credential to the upstream is broken. Authentication failures from upstream services are also collapsed onto this code so we don't leak which side of the integration is misconfigured. | Retry after a short backoff. If persistent, contact support — only we can resolve broken upstream credentials. |
    | `TIMEOUT`        | 504    | An upstream operation didn't finish within our timeout window, or our HTTP client to the upstream timed out.                                                                                                                            | Retry after a short backoff. For long-running queries (KQL, advanced hunting), narrow the time window.         |
  </Tab>
</Tabs>

***

## Mapping behavior

A few specific cases are worth calling out because they affect how you should design retries and surface failures to your own users.

<AccordionGroup>
  <Accordion title="Upstream 4xx is translated to a caller-facing 4xx">
    When a downstream SIEM rejects a request with a 4xx (404 for an unknown ID, 400 for an unparseable filter, 409 for a conflict, 429 for rate-limit, 408/504 for timeout), the v2 API surfaces that as the matching 4xx — `NOT_FOUND`, `VALIDATION_ERROR`, `CONFLICT`, `RATE_LIMITED`, `TIMEOUT` — rather than collapsing onto an opaque 502. That way your retry logic can act on what's actually wrong.

    Only opaque or 5xx upstream failures stay as `UPSTREAM_ERROR`. Upstream 401/403 also stay opaque (they mean *our* credential to the upstream is broken; that's not something you can fix).
  </Accordion>

  <Accordion title="Client-aborted requests do not produce a body">
    When the caller disconnects mid-request, the response is an empty `499` and no ProblemDetails body is written. Treat 499 as "request was cancelled before we could respond" — your client probably already knows.
  </Accordion>

  <Accordion title="Use requestId, not traceId">
    Some development-mode payloads previously carried a `traceId` extension from ASP.NET. The v2 contract is `requestId` only. Quote the `requestId` value when contacting support — we can correlate it to backend logs without you needing to share anything else.
  </Accordion>
</AccordionGroup>

***

## Correlating with our logs

Every `requestId` is also recorded in our backend logs. Including it in support requests dramatically shortens triage:

> Hello, I'm seeing intermittent 502 `UPSTREAM_ERROR` on `POST /api/v2/incidents/across-workspaces`. Example `requestId`: `0HNLBAGCRD4RN:00000003` at `2026-04-15T20:00:00Z`.

We can pull the full backend trace and pinpoint the failure without needing the request body or your credentials.
