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: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
message field — it usually names the specific field that’s wrong. Fix it and retry.
Example:
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-keyheader 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
- Double-check the header is exactly
x-api-key: mf_sk_... - Copy a fresh key from the dashboard
- Re-export it:
export MF_API_SECRET_KEY="..." - Try again
403 section below.
A revoked key comes back labeled. If the key you sent was revoked in the dashboard, the response body carries "code": "key_revoked" and a message that says when it was revoked. The code lets you spot a revoked key in your own code without reading the message text. The fix is to create a new key in Build → API keys and update whatever app still sends the old one.
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. POST/PUT/PATCH/DELETE with a publishable key are rejected. Use a secret key (mf_sk_*) for writes. - You used a publishable key (
mf_pk_*) to read your organization’s own data. A publishable key can reach only two identity endpoints:/v1/verifyand its own/v1/me/key. Every other authenticated read (customers, invoices, analytics like revenue, cost, and MRR, credit balances, and your AI-spend / Spend Controls reads) is secret-key-only, because that data is your org’s books, not the public info a browser-embeddable key is meant for. The message says the endpoint needs a secret key. Use a secret key (mf_sk_*). - 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/DELETEon/v1/control/caps) need a key that belongs to an owner or finance user. The two credit-pool writes (POST /v1/subscriptions/:id/credit-topupandPOST /v1/subscriptions/:id/credit-alerts-pause) need owner, admin, or finance, the same gate as every other write that moves money. These write denials name the allowed roles in the message (a logged-in dashboard user without the role gets a generic forbidden instead); use a key from someone with that role, or ask them to make the change. - You used an ingest-only key on a route it can’t reach. An ingest-only secret key can send usage records, confirm which organization it belongs to (
/v1/verify), and read your spend controls (the caps, your current spend, and coverage). Every other authenticated route (customers, invoices, revenue, payouts, credit balances, and the rest) answers403. This denial does not list roles: its message explains what an ingest-only key can reach and tells you to use a key with a higher role. Mint one in the dashboard under Build → API keys. - You hit an endpoint that requires a human user session (not an API key). These include:
GET /v1/users/mePOST /v1/invitations/accept/:token- Team invitation endpoints
- A few org management actions
- If the response message mentions a publishable key, or says the endpoint needs a secret key, 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/planIdyou reference doesn’t exist - The URL itself is wrong (e.g., typo in the resource name)
- 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
externalIdthat already exists in this org - Creating an agent with an
agentCodethat already exists in this org - Creating a signal with a name that already exists for that agent
- Reusing a credit-topup
idempotencyKeywith different units, or on a different subscription (reuse a key only to retry the exact same top-up; see Credit Balances) - Deleting an agent/customer/plan that still has dependent records (e.g., deleting a customer that has an active subscription)
- 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
endDateearlier than thestartDate - Billing cycle math that doesn’t work (e.g.,
billingCycle: "custom"withoutcustomCycleDays) - An
agentIdthat belongs to a different org than the customer
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-Afterto see how long to wait
The other kind of 429: too many failed key attempts
If the body carries"code": "too_many_auth_failures", this isn’t about your request rate. It means too many failed API-key attempts came from your network in a short window. That’s a sign the key you’re sending is wrong, not that you’re calling too fast.
How to fix: Stop retrying, wait a few minutes, and check the key you’re sending in Build → API keys. A key that MarginFront accepts clears the block.
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
- Retry once after a few seconds — transient errors are common
- If it persists, email
[email protected]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)
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
[email protected]
"code": "auth_unavailable", MarginFront couldn’t check your API key because of a problem on its side, not because your key is bad. Your key is probably fine. Wait a moment and try again; it clears on its own.
”I get a response but no body”
A few endpoints return an empty body on success (typicallyDELETE 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’smessage 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 (withcost: 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
Example: a batch with one success and one backfill
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[email protected] with:
- The exact curl command you ran (API key REDACTED)
- The full response status and body
- What you expected to happen

