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

# Errors

> Status codes, the error envelope, and the endpoints that report failure in the body instead.

## Error envelope

Errors come back as a NestJS error object.

```json theme={null}
{
  "statusCode": 404,
  "message": "Link not found",
  "error": "Not Found"
}
```

When the failure is request validation, `message` is an **array** of field-level messages instead of
a string. Handle both shapes.

```json theme={null}
{
  "statusCode": 400,
  "message": [
    "Destination URL is required",
    "Title is required"
  ],
  "error": "Bad Request"
}
```

## Status codes

| Code | Meaning | Typical cause |
| - | - | - |
| `400` | Bad Request | A field failed validation, an unknown field was sent, `x-workspace-id` is missing, or a bulk `ids` array is empty |
| `401` | Unauthorized | No `Authorization` header, an expired or revoked key or token, or an `x-workspace-id` that does not match the API key's workspace |
| `403` | Forbidden | The role lacks the required permission, the plan does not include the feature, a plan quota is exhausted, or an analytics range predates the plan's retention window |
| `404` | Not Found | The resource does not exist, or belongs to another workspace |
| `409` | Conflict | The short code, domain, folder name or tag name is already taken |
| `500` | Internal Server Error | An unhandled fault on our side |
| `502` | Bad Gateway | An uploaded `ogImageFile` could not be saved to file storage. Safe to retry |
| `503` | Service Unavailable | An `ogImageFile` was uploaded but file storage is not available on the server |

<Note>
  A `500` is the one response that does **not** carry an `error` field — NestJS's default handler
  returns only `statusCode` and `message`:

  ```json theme={null}
  { "statusCode": 500, "message": "Internal server error" }
  ```

  Retry it with backoff. Every other status returns all three fields.
</Note>

## Reading a 403

`403` covers three different situations, separated only by the message.

| Message pattern | What to do |
| - | - |
| `…is not available on your current plan` | The workspace's plan lacks the feature — upgrade |
| `…limit reached` / `Please upgrade your plan to create more…` | The quota is exhausted — upgrade or free some capacity |
| `You do not have access to this workspace` / permission denied | The caller's role or seat is the problem — not the plan |

## Endpoints that report failure in the body

Two endpoints return a success status with a failure payload. Branch on the body, not the status code.

<AccordionGroup>
  <Accordion title="POST /domains — duplicate domains">
    A domain already registered by another account returns `201` with `domainId: null` and an
    `error` string. Check `domainId` before treating the call as a success.
  </Accordion>

  <Accordion title="POST /domains/{id}/verify — DNS not ready">
    A failed CNAME check returns `200` with `verified: false` and the `cnameTarget` to publish.
    Branch on `verified`.
  </Accordion>
</AccordionGroup>

## Handling errors

```javascript theme={null}
const res = await fetch('https://api.linkutm.com/api/v1/links', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer lk_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ url: 'https://acme.com/pricing' }),
});

if (!res.ok) {
  const err = await res.json();
  const detail = Array.isArray(err.message) ? err.message.join('; ') : err.message;
  throw new Error(`linkutm ${err.statusCode}: ${detail}`);
}
```

Retry `5xx` responses and network timeouts with exponential backoff and jitter. Never retry a `4xx`
— the request itself is the problem.


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