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

# Errors

> Every error code translated to plain English with fix steps

# Errors

When something goes wrong, MarginFront returns an HTTP status code and a JSON body describing what happened. This doc translates the most common ones from developer-speak into plain English, plus how to fix each one.

***

## The shape of an error response

Every error looks roughly like this:

```json theme={null}
{
  "statusCode": 400,
  "message": "name should not be empty",
  "error": "Bad Request"
}
```

For validation errors (400s), `message` is usually the most helpful — it tells you exactly which field is wrong and why.

For server errors (500s), `message` is often a generic string; the actual problem is on MarginFront's side and the response won't tell you how to fix it (because it's not your fault).

***

## `400 Bad Request`

**What happened:** Something in your request is malformed. Usually a missing required field or a field with the wrong type.

**Most common causes:**

* You forgot a required field (check the resource's doc for which fields are required)
* A UUID field isn't a valid UUID format
* A date field isn't valid ISO 8601
* An enum field has a value that isn't in the allowed list
* An email field isn't a valid email

**How to fix:** Read the `message` field — it usually names the specific field that's wrong. Fix it and retry.

**Example:**

```json theme={null}
{
  "statusCode": 400,
  "message": ["name should not be empty", "agentId must be a UUID"],
  "error": "Bad Request"
}
```

Fix: add a `name`, fix the `agentId` to be a valid UUID.

***

## `401 Unauthorized`

**What happened:** Your API key is missing, malformed, or wrong.

**Most common causes:**

* You forgot the `x-api-key` header entirely
* The header name is wrong (e.g., using `Authorization: Bearer` — that's for JWT tokens, not API keys)
* The API key is from a different environment (test vs live)
* The API key was revoked in the dashboard
* A typo when copy-pasting the key

**How to fix:**

1. Double-check the header is exactly `x-api-key: mf_sk_...`
2. Copy a fresh key from the dashboard
3. Re-export it: `export MF_API_SECRET_KEY="..."`
4. Try again

If you still get 401 after a fresh key, the endpoint might require a logged-in user session (not an API key) — see the `403` section below.

***

## `403 Forbidden`

**What happened:** Your API key is valid, but you're trying to do something it's not allowed to do.

**Most common causes:**

* **You used a publishable key (`mf_pk_*`) on a write operation.** Publishable keys are read-only by design — POST/PUT/PATCH/DELETE are rejected. Use a secret key (`mf_sk_*`) for writes.
* **Your key doesn't carry the right role for this endpoint.** A few endpoints are restricted beyond secret-vs-publishable. Spend-cap writes (`POST`/`PATCH`/`DELETE` on `/v1/control/caps`) need a key that belongs to an **owner or finance** user. The error message names the required role; use a key from someone with that role, or ask them to make the change.
* You hit an endpoint that requires a human user session (not an API key). These include:
  * `GET /v1/users/me`
  * `POST /v1/invitations/accept/:token`
  * Team invitation endpoints
  * A few org management actions

**How to fix:**

* If the response message mentions "publishable keys cannot perform write operations", switch to a secret key. See [authentication.md](./authentication) for the key type reference.
* If the endpoint is a "user-scoped" endpoint, you can't use an API key — you need to be logged in as a human via the dashboard instead.

***

## `404 Not Found`

**What happened:** The thing you're trying to read/update/delete doesn't exist.

**Most common causes:**

* Wrong UUID in the URL (typo, or using an old deleted record's ID)
* The record exists but in a DIFFERENT org than yours
* You're creating a subscription but the `customerId` / `agentId` / `planId` you reference doesn't exist
* The URL itself is wrong (e.g., typo in the resource name)

**How to fix:**

* Double-check the ID by listing the resource first and finding the correct ID
* Verify the ID belongs to your org (if you're using a different API key than usual, make sure it's pointing at the same org)
* Check the URL for typos

***

## `409 Conflict`

**What happened:** The thing you're trying to create already exists, or your update would violate a uniqueness constraint.

**Most common causes:**

* Creating a customer with an `externalId` that already exists in this org
* Creating an agent with an `agentCode` that already exists in this org
* Creating a signal with a name that already exists for that agent
* Deleting an agent/customer/plan that still has dependent records (e.g., deleting a customer that has an active subscription)

**How to fix:**

* For duplicate `externalId` / `agentCode` / name: either pick a different one, or update the existing record instead of creating a new one
* For delete-with-dependents: delete or reassign the dependent records first, then retry the delete

***

## `422 Unprocessable Entity`

**What happened:** Your request was structurally valid (valid JSON, required fields present), but the combination of values doesn't make sense.

**Most common causes:**

* Creating a subscription with an `endDate` earlier than the `startDate`
* Billing cycle math that doesn't work (e.g., `billingCycle: "custom"` without `customCycleDays`)
* An `agentId` that belongs to a different org than the customer

**How to fix:** Read the `message` field carefully — it'll describe the specific inconsistency.

***

## `429 Too Many Requests`

**What happened:** You're hitting the API faster than the rate limit allows.

**How to fix:**

* Slow down your request rate
* If logging usage events, batch them (up to 100 per call) instead of one per request
* Check response headers for `Retry-After` to see how long to wait

***

## `500 Internal Server Error`

**What happened:** Something broke on MarginFront's side. This is NOT your fault.

**Most common causes:**

* A bug in MarginFront's code
* A database connection issue
* An upstream service (Stripe, Resend, etc.) being unavailable

**How to fix:**

* Retry once after a few seconds — transient errors are common
* If it persists, email `team@marginfront.com` with:
  * The exact URL and method you called
  * The request body (with any API keys redacted)
  * The full error response body
  * A timestamp (roughly when you saw the error)

MarginFront logs every 500 error internally. If you report it, the team can usually find the specific stack trace in the logs.

***

## `502` / `503` / `504` — Gateway errors

**What happened:** MarginFront's API is temporarily unreachable or down.

**How to fix:**

* Wait a minute and retry
* Check MarginFront's status page if one exists
* If it's sustained, email `team@marginfront.com`

These are usually transient — a deployment rolling, a load balancer restart, a brief upstream outage.

***

## "I get a response but no body"

A few endpoints return an empty body on success (typically `DELETE` returning `204 No Content`). That's expected — the status code is the real signal, not the body.

If you expected a body and got nothing on a different status code, treat it as a bug and report it.

***

## "The error message mentions a field I didn't send"

This usually means a required field is missing, or a field you sent has the wrong type. The error's `message` array tells you exactly which field — read it carefully and cross-reference with the resource's doc file (e.g., [customers.md](./customers)) for the required and optional fields.

***

## Response-level error codes (inside a `200 OK`)

Some endpoints — particularly `POST /v1/usage/record` — return `200 OK` even when individual records in a batch fail. The failures appear in the `results.failed[]` array in the response body, each with a `code` and a `stored` flag.

### The `stored` flag

Every failed record tells you whether the event was saved to the database:

* **`stored: true`** — The event IS in the system (with `cost: null`). **Do NOT retry** — retrying will create a duplicate. Instead, fix the root cause (map the model in the dashboard) and the event will be backfilled automatically.
* **`stored: false`** — The event was NOT saved. **Safe to retry** after fixing the issue.

### Error codes

| Code                  | Stored? | What happened                                                                                                                                                                                                                                                                                                        | What to do                                                                                                                                                                                                                                                                                               |
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEEDS_COST_BACKFILL` | Yes     | The event is saved with `usageCost: null` for one of two reasons: the `model` + `modelProvider` combination isn't in the pricing table, OR it is in the table but has no rate for the cache tokens your event reported (MarginFront flags it rather than pricing that cache at \$0). The `error` message says which. | Go to the dashboard → Usage Events → "Needs attention." If the model is unknown, map it to a known one. If the model is known but missing cache-token pricing, email `team@marginfront.com` to have cache pricing added. Once resolved, cost is calculated retroactively and future events auto-resolve. |
| `INTERNAL_ERROR`      | No      | Something unexpected broke on our side during event processing.                                                                                                                                                                                                                                                      | Safe to retry. If it keeps happening, contact support with the `rawEventId` from the response.                                                                                                                                                                                                           |
| `VALIDATION_ERROR`    | No      | A required field is missing or has the wrong type (e.g., `modelProvider` not provided, `inputTokens` is negative).                                                                                                                                                                                                   | Read the `error` message to see which field failed. Fix the request and resend.                                                                                                                                                                                                                          |

### Example: a batch with one success and one backfill

```json theme={null}
{
  "processed": 2,
  "successful": 1,
  "failed": 1,
  "results": {
    "success": [
      {
        "customerExternalId": "acme-001",
        "model": "gpt-4o",
        "modelProvider": "openai",
        "totalCostUsd": "0.0024780000",
        "eventId": "8a7b6c5d-..."
      }
    ],
    "failed": [
      {
        "record": {
          "customerExternalId": "acme-001",
          "model": "my-custom-llm",
          "modelProvider": "custom"
        },
        "code": "NEEDS_COST_BACKFILL",
        "stored": true,
        "eventId": "a1b2c3d4-...",
        "error": "Model \"my-custom-llm\" (provider \"custom\") not found in service_pricing. Event stored — map this model in the dashboard."
      }
    ]
  }
}
```

The first event was fully processed with cost. The second was stored but needs the model mapped before cost can be calculated. HTTP status is `200` for both.

See [usage-events.md](./usage-events) for the full endpoint documentation and mapping workflow.

***

## Still stuck?

If none of the above match what you're seeing, email `team@marginfront.com` with:

1. The exact curl command you ran (API key REDACTED)
2. The full response status and body
3. What you expected to happen

Treat docs-confusion as a docs bug — if the error you got isn't in this list, we want to know so we can add it.
