Skip to main content

Signals (Metrics)

A signal — also called a metric — is what you measure and bill on. It’s the unit of work you charge for. Examples:
  • A chatbot agent might have a signal called messages (charge per message)
  • A document analyzer might have pages_processed (charge per page)
  • An image generator might have images_generated (charge per image)
  • A video service might have minutes_transcoded (charge per minute)
You define the signal once, attach it to an agent, and then every time your agent does the thing, you log a usage event against that signal. MarginFront keeps count, applies your pricing rules, and rolls it up into an invoice.

Signal types: usage vs volume

Every signal has a type that controls how it’s billed:
TypeAlso known asBills based onExample
usageOUTCOMEResults actually deliveredCharge per successfully analyzed document
volumeACTIVITYAttempts made, successful or notCharge per API call regardless of outcome
Pick usage when you want to align pricing with customer value (they only pay when they get something). Pick volume when you want to charge for the resources consumed regardless of result (they pay for every attempt because every attempt costs you money). Most usage-based pricing for AI products uses usage. When in doubt, start there.

Create a signal

In plain English: Define a new billable metric for an agent. You do this once per metric, up front — not every time the agent works. Method & URL:
POST /v1/signals
Required fields:
  • name (string, max 255 chars) — Human-readable name. Shows up in reports and on invoices. Examples: "Messages Processed", "Documents Analyzed", "Images Generated".
  • agentId (UUID string) — The internal ID of the agent this signal belongs to. Get it from the agent’s create response. (Note: this is the agent’s id, not its agentCode.)
Optional fields:
  • shortName (string, max 255 chars) — A machine-friendly version of the name. Used as the key in some API responses. Example: "messages", "docs_analyzed". If you don’t provide one, it’s derived from name.
  • type ("usage" | "volume", default "usage") — See the explanation above.
Example curl call:
curl -X POST https://api.marginfront.com/v1/signals \
  -H "x-api-key: mf_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Messages Processed",
    "shortName": "messages",
    "agentId": "b47e12fa-abcd-4567-8901-234567890abc",
    "type": "usage"
  }'
What you get back (201 Created):
{
  "id": "f12d34ab-cdef-0123-4567-89abcdef0123",
  "name": "Messages Processed",
  "shortName": "messages",
  "type": "usage",
  "agentId": "b47e12fa-abcd-4567-8901-234567890abc",
  "createdAt": "2026-04-10T14:30:00.000Z"
}
Common errors:
  • 400 Bad Request — Missing name or agentId, or agentId isn’t a valid UUID format, or type isn’t "usage" or "volume".
  • 404 Not Found — The agentId you provided doesn’t match any agent in this org.
  • 409 Conflict — A signal with that name or shortName already exists for this agent.
When to call this: Once, during setup, for each metric you want to bill on. Don’t call this every time a usage event happens — that’s what the usage/record endpoint is for.

Read a signal

Method & URL:
GET /v1/signals/{signalId}

List signals

Method & URL:
GET /v1/signals
Useful query parameters (check the controller for the full list):
  • agentId — filter to signals for a specific agent

Update a signal

Method & URL:
PATCH /v1/signals/{signalId}
Fields: Any field from create except agentId (you can’t move a signal to a different agent — create a new one instead).
Careful with renaming. If you change a signal’s shortName after you’ve started logging usage events against it, any reports that grouped by shortName will split across the old and new names. Rename sparingly.

Delete a signal

Method & URL:
DELETE /v1/signals/{signalId}
What it does: Permanently removes the signal. All historical usage events tied to this signal become orphaned — they still exist, but they’ll stop showing up in most reports.
Usually you don’t want to delete. For ongoing operations, prefer to just stop logging events against an old signal. Deletion is mostly for cleaning up test data or correcting mistakes during setup.

Bulk create signals

Method & URL:
POST /v1/signals/bulk
When to use it: If you’re setting up many signals at once (e.g., spinning up a new agent with a dozen metrics), the bulk endpoint is faster than calling create in a loop. Check the controller for the exact request shape.

Using the Node SDK

Signals aren’t exposed as a separate resource in the @marginfront/sdk npm package. The SDK references signals by name (signalName) when you log usage events:
await mf.usage.record({
  customerExternalId: "acme-001",
  agentCode: "cs-bot-v2",
  signalName: "messages", // ← matches the signal's shortName
  model: "gpt-4o",
  inputTokens: 523,
  outputTokens: 117,
});
To create signals, use the HTTP API above. If you log a usage event with a signalName MarginFront has never seen, it auto-creates a minimal signal for you — convenient for prototyping, but you’ll want to go back and set the right name and type afterwards.

How signals fit into the whole billing chain

Customer  ─┐

Agent ─────┼──── Signal ──── Pricing plan ──── Subscription
           │     (metric)    (how to price)    (per customer)

Usage event (logged per signal, per customer)
  • Customer: who’s being billed
  • Agent: what’s doing the work
  • Signal: what you’re measuring
  • Pricing plan: how much each unit of that signal costs
  • Subscription: ties a customer to a pricing plan
  • Usage event: a single measurement (customer X used signal Y, quantity Z)
When an invoice runs, MarginFront totals the usage events for each customer, applies the pricing plan from their subscription, and produces line items per signal. That’s the whole pipeline.