HTTP API
Four routes. Everything below is generated from the schemas the server validates production traffic against, so this page cannot describe an endpoint we do not serve or a field we do not accept. The same document is served live at api.marginfuse.com/openapi.json.
Authentication
A project API key as a bearer token: Authorization: Bearer mf_live_.... Keys are secrets and this is a server-side API, so never ship one anywhere a user can read it. Only the hash is stored, so a lost key is rotated, never recovered.
Every failure is safe
The official SDKs treat any non-2xx, timeout or transport error as fail-open: the application proceeds unprotected rather than breaking. A rate limit, an outage and a rejected payload all degrade measurement, never availability. Anything you build directly against this API should hold the same contract.
Errors share one envelope. error is a stable machine-readable code, while detail and issues are human-facing and may change, so branch on error alone.
Routes
Report usage for AI calls that already happened
Accepts up to 500 events per request. Each event is processed independently: the response carries one result per event, in request order, and a rejected event does not fail the batch.
Idempotent on eventId. Re-sending an event you already sent returns status duplicate and changes nothing, so a retry after an ambiguous network failure is always safe. To correct a figure you already reported, send a new eventId and set correctsEventId to the original.
Send costUsd when your provider tells you the real charge (gateways such as OpenRouter do). Otherwise omit it and MarginFuse prices the call from token counts, labeling the result as estimated rather than observed.
Request body
| Field | Type | Required |
|---|---|---|
| eventId | string, 1..256 chars | yes |
| customerId | string, 0..256 chars | |
| feature | string, 0..128 chars | |
| provider | string, 1..64 chars | yes |
| model | string, 1..128 chars | yes |
| requestedModel | string, 0..128 chars | |
| usage | object | |
| costUsd | string | |
| occurredAt | string, date-time | |
| outcome | "success" | "provider_error" | "app_cancelled" | "timeout" | |
| decisionId | string, 0..128 chars | |
| retryOfEventId | string, 0..256 chars | |
| correctsEventId | string, 0..256 chars |
200 response
| Field | Type | Required |
|---|---|---|
| results | object[] | yes |
Status codes
- 200Batch processed. Inspect each result for its individual status.
- 401Missing, malformed, or unrecognized API key.
- 422The body did not match the schema. issues lists up to 10 problems.
- 429Rate limit exceeded for this API key. These events are neither measured nor protected, but the SDK fails open so the caller keeps working.
Ask whether the next AI call should run
Evaluates the project's policies against this customer's current margin and returns an action to take before you call the provider:
- allow proceed with the model you asked for
- downgrade proceed, but use the model in the response instead
- topup_required do not call the provider; present the top-up path to the customer
- block do not call the provider
Policies run in dry-run before they run live, so this endpoint records what protection would have done against real traffic without acting on it. A dry-run policy always answers allow.
Report back with /v1/decisions/{id}/ack once you know what your application did. Savings can only be credited as realized on that confirmation.
Request body
| Field | Type | Required |
|---|---|---|
| customerId | string, 1..256 chars | yes |
| feature | string, 0..128 chars | |
| provider | string, 1..64 chars | yes |
| model | string, 1..128 chars | yes |
| expectedUsage | object |
200 response
| Field | Type | Required |
|---|---|---|
| id | string | yes |
| action | "allow" | "downgrade" | "topup_required" | "block" | yes |
| model | string | yes |
| provider | string | yes |
| topupContext | string | |
| degraded | boolean | yes |
| degradedReason | string |
Status codes
- 200A verdict. Enforce on action alone.
- 401Missing, malformed, or unrecognized API key.
- 422The body did not match the schema. issues lists up to 10 problems.
- 429Rate limit exceeded for this API key. Treat as unavailable and fail open: allow the call rather than failing the customer's request.
Report what your application did with a decision
Closes the loop on a decision. This is evidence, not bookkeeping: MarginFuse will not count an avoided provider call as realized savings without the application confirming the call did not occur.
presented_topup deliberately does not earn realized savings on its own. The customer may have paid and proceeded, so the call may well have happened.
Request body
| Field | Type | Required |
|---|---|---|
| acknowledgment | "proceeded_as_requested" | "used_downgrade_model" | "presented_topup" | "blocked_before_provider_call" | "failed_to_apply" | yes |
200 response
| Field | Type | Required |
|---|---|---|
| ok | boolean | yes |
Status codes
- 200Recorded.
- 401Missing, malformed, or unrecognized API key.
- 404No decision with this id belongs to the authenticated project.
- 422The body did not match the schema.
- 429Rate limit exceeded for this API key.
This document
The machine-readable contract, served by the same deployment that enforces it. Unauthenticated, so an SDK author or an agent can read the API before being given a key.
Status codes
- 200An OpenAPI 3.1 document.
Liveness, including a database round trip
Unauthenticated. Returns 503 when the database is unreachable, so a rotated credential or a dead pooler shows up here and not only as failing traffic.
200 response
| Field | Type | Required |
|---|---|---|
| ok | boolean | yes |
| db | boolean | yes |
Status codes
- 200Serving traffic.
- 503The database could not be reached.