All pages

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

post/v1/events

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

EventsRequest fields
FieldTypeRequired
eventIdstring, 1..256 charsyes
customerIdstring, 0..256 chars
featurestring, 0..128 chars
providerstring, 1..64 charsyes
modelstring, 1..128 charsyes
requestedModelstring, 0..128 chars
usageobject
costUsdstring
occurredAtstring, date-time
outcome"success" | "provider_error" | "app_cancelled" | "timeout"
decisionIdstring, 0..128 chars
retryOfEventIdstring, 0..256 chars
correctsEventIdstring, 0..256 chars

200 response

EventsResponse fields
FieldTypeRequired
resultsobject[]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.
post/v1/decisions

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

DecideRequest fields
FieldTypeRequired
customerIdstring, 1..256 charsyes
featurestring, 0..128 chars
providerstring, 1..64 charsyes
modelstring, 1..128 charsyes
expectedUsageobject

200 response

Decision fields
FieldTypeRequired
idstringyes
action"allow" | "downgrade" | "topup_required" | "block"yes
modelstringyes
providerstringyes
topupContextstring
degradedbooleanyes
degradedReasonstring

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.
post/v1/decisions/{id}/ack

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

AckRequest fields
FieldTypeRequired
acknowledgment"proceeded_as_requested" | "used_downgrade_model" | "presented_topup" | "blocked_before_provider_call" | "failed_to_apply"yes

200 response

AckResponse fields
FieldTypeRequired
okbooleanyes

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.
get/openapi.jsonno auth

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.
get/healthno auth

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

Health fields
FieldTypeRequired
okbooleanyes
dbbooleanyes

Status codes

  • 200Serving traffic.
  • 503The database could not be reached.
NextWhat it sendsEvery field on the wire, and why prompts cannot be among them.