Skip to main content

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:
{
  "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:
{
  "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.
  • 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 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) 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

CodeStored?What happenedWhat to do
NEEDS_COST_BACKFILLYesThe model + modelProvider combination isn’t in the pricing table. The event is saved with usageCost: null.Go to the dashboard → Usage Events → “Needs attention” → map the model to a known one. Once mapped, cost is calculated retroactively and all future events with that model auto-resolve.
INTERNAL_ERRORNoSomething unexpected broke on our side during event processing.Safe to retry. If it keeps happening, contact support with the rawEventId from the response.
VALIDATION_ERRORNoA 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

{
  "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 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.