From 13456113ed00314cd7a51c75c147c84c76fd8d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Conejo?= Date: Thu, 20 Aug 2026 14:17:00 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20observability=20facts=20=E2=80=94=20cal?= =?UTF-8?q?endar=20periods,=20zero-cost=20failed=20attempts,=20custom-prov?= =?UTF-8?q?ider=20pricing,=20alert=20delivery,=20request=20states,=20usage?= =?UTF-8?q?=20API=20shapes,=20Pro=20ranges,=20Limits=20card=20(issue=20#83?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observability.mdx | 71 +++++++++++++++++++++++++++------- providers/custom-providers.mdx | 2 +- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/observability.mdx b/observability.mdx index a173af5..6bbb74e 100644 --- a/observability.mdx +++ b/observability.mdx @@ -22,9 +22,9 @@ Manifest tracks two things, and the difference matters once fallback is involved | **Request** | One call from your agent to Manifest | Never. One call in, one Request. | | **Provider attempt** | One call from Manifest to a provider | Every fallback step and every [Autofix](/autofix) retry adds another | -A request that succeeds on the first try is one Request with one attempt. A request that fails twice before succeeding is still one Request, with three attempts. A request Manifest blocked itself, like a [hard limit](/llm-gateway#hard-limits) or a malformed body, is one Request with one attempt that never reached a provider. That attempt is listed under the model you asked for, or **No provider** when the request named none, and carries the error that stopped it. +A request that succeeds on the first try is one Request with one attempt. A request that fails twice before succeeding is still one Request, with three attempts. A request Manifest blocked itself, like a [hard limit](/llm-gateway#hard-limits) or a malformed body, is one Request with one attempt that never reached a provider. That attempt is listed under the model you asked for, or **No provider** when the request named none, and carries the error that stopped it. A request that is still running has no attempt yet and shows as **Pending**. A request whose caller disconnected shows as **Cancelled**, and it keeps the attempt that had already started, if there was one. -This is why the request count and the provider call count don't match, and why cost is attributed to attempts: a failed attempt can still burn tokens. +This is why the request count and the provider call count don't match. Manifest records cost and tokens on the attempt, not on the request. Only a successful attempt carries them. A failed attempt keeps its error and reads zero tokens with no cost. ## The dashboard @@ -39,13 +39,15 @@ This is why the request count and the provider call count don't match, and why c Per-provider usage, spend, and error rates. Useful for spotting one provider degrading. - Threshold rules per harness, plus email provider setup. + Threshold rules per harness. +The Overview counts finished requests only. The Requests log's **Failed** filter returns everything that did not succeed, including pending and cancelled requests. So the Overview's **Failed requests** card links to a list longer than the number on the card. +
-On the Free plan, the Overview covers up to 7 days of history; the 30-, 90-, and 365-day ranges need Pro. +On the Free plan, the 30-, 90- and 365-day ranges need Pro. The lock applies on the Overview and on the Requests log. The log's **All time** option is not locked, so you can still list every request you ever sent.
@@ -57,13 +59,16 @@ That's all metadata. To read the messages themselves, turn on [request logs](/re Local models record `cost = 0` with real token counts and latency, since - nothing was billed. Custom providers do the same, because Manifest can't - infer pricing for a model it doesn't know. + nothing was billed. You price a custom provider yourself: its connect form + takes **Input / 1M tokens** and **Output / 1M tokens** for each model, and + Manifest computes cost from those figures like any other provider. Leave both + prices empty and Manifest has nothing to compute from, so the model records + `cost = 0` too. ## Spend alerts -A limit rule watches one metric for one harness over a rolling period, and acts when the threshold is crossed. +A limit rule watches one metric for one harness over the current period, and acts when the threshold is crossed. The period is a calendar window on the clock of the server running Manifest. A day starts at midnight, a week starts on Monday, and a month starts on the 1st. An hour period starts at the top of the previous clock hour. The count restarts at the next boundary. | Field | Options | |---|---| @@ -89,7 +94,7 @@ Blocking is the gateway half of this and is covered in [hard limits](/llm-gatewa -Alerts are evaluated hourly, so an email can lag the threshold by up to an hour. Blocking is not: it's checked on every request. Each rule emails once per period, so a rule that stays over its threshold won't flood your inbox. +Alerts are evaluated hourly, so an email can lag the threshold by up to an hour. Blocking is not: it's checked on every request. Each rule emails once per period, so a rule that stays over its threshold won't flood your inbox. Manifest records the crossing before it sends the email, and it does not retry inside the period. A send that fails leaves that period without an email. The **Triggered** count on the Limits page still goes up, because it counts threshold crossings and not deliveries. ### Email delivery @@ -101,7 +106,7 @@ Alerts go to your account email. Check it's current in your profile settings.
-On the **Limits** page, under **Configure email provider**, pick Resend, Mailgun, or SendGrid. Enter the API key. Mailgun also asks for a sending domain. **Notification email** defaults to your account email; change it to send alerts elsewhere. **Test & Connect** sends a real test email and saves nothing if it fails. The saved provider appears as a card on the same page, where you can edit or remove it. +On the **Limits** page, under **Configure email provider**, pick Resend, Mailgun, or SendGrid. Enter the API key. Mailgun also asks for a sending domain. **Notification email** defaults to your account email; change it to send alerts elsewhere. **Test & Connect** sends a real test email and saves nothing if it fails. The saved provider appears as a card on the same page, where you can edit or remove it. Edit it and click **Send test email** to check the sender still works. Your workspace holds one provider, shared by every harness. Removing it stops alerts for all of them. @@ -116,14 +121,52 @@ Without an email provider from either path, alerts are skipped. Blocking rules s A harness can query its own numbers with its `mnfst_` key, which is useful for showing a budget in your own UI or stopping work before a limit blocks you. ```bash -curl https://app.manifest.build/api/v1/agent/usage \ +curl "https://app.manifest.build/api/v1/agent/usage?range=24h" \ -H "Authorization: Bearer mnfst_YOUR_KEY" ``` -| Endpoint | Returns | -|---|---| -| `GET /api/v1/agent/usage` | Token usage for the calling harness | -| `GET /api/v1/agent/costs` | Cost data for the calling harness | +| Endpoint | Returns | Default `range` | +|---|---|---| +| `GET /api/v1/agent/usage` | Token usage for the calling harness | `24h` | +| `GET /api/v1/agent/costs` | Cost data for the calling harness | `7d` | + +Both endpoints take a `range` parameter. It accepts `1h`, `6h`, `24h`, `7d`, `30d`, `90d` or `365d`, and any other value gets a `400`. The two defaults differ, so send `range` yourself whenever you compare the two responses. + +A range counts back from now, while a limit rule's period starts at a calendar boundary ([Spend alerts](#spend-alerts)). No range lines up with a rule's window, so read these numbers as how close you are, not as the figure the rule reads. + +`GET /api/v1/agent/usage` returns: + +```json +{ + "range": "24h", + "total_tokens": 128400, + "input_tokens": 96200, + "output_tokens": 32200, + "cache_read_tokens": 41000, + "cache_creation_tokens": 5200, + "cache_read_rate": 0.43, + "cache_write_rate": 0.05, + "message_count": 312, + "trend_pct": 12.4, + "agentName": "billing-bot" +} +``` + +`GET /api/v1/agent/costs` returns: + +```json +{ + "range": "7d", + "total_cost_usd": 18.42, + "trend_pct": -6.1, + "by_model": [ + { "model": "openai/gpt-4o", "cost_usd": 11.9, "input_tokens": 820000, "output_tokens": 140000 }, + { "model": "anthropic/claude-sonnet-4-6", "cost_usd": 6.52, "input_tokens": 210000, "output_tokens": 48000 } + ] +} +``` + +`message_count` counts successful requests only. A failed or in-flight request is not in it.
diff --git a/providers/custom-providers.mdx b/providers/custom-providers.mdx index fa177f9..c11aa96 100644 --- a/providers/custom-providers.mdx +++ b/providers/custom-providers.mdx @@ -62,4 +62,4 @@ User-supplied URLs are revalidated on every request to defend against SSRF. On M ## Cost tracking -Manifest can't infer pricing for unknown models. Custom-provider requests show up in the dashboard with `cost = 0` and `model = `. Token counts and latency are still recorded, so [hard limits](/llm-gateway#hard-limits) on token volume still work. +You price a custom provider's models yourself. The connect form takes **Input / 1M tokens** and **Output / 1M tokens** for each model, and Manifest computes cost from those figures like any other provider. Leave both prices empty and the model's requests show `cost = 0`. Token counts and latency are recorded either way, so [hard limits](/llm-gateway#hard-limits) on token volume work with or without prices.