> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroclick.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Settle usage

> Tell ZeroClick what a served request actually used: the zc-usage header for synchronous settlement, and asynchronous reports for work that finishes later.

An allowed request still has to settle: the payment finalizes against what the request actually used. Your revenue, credit burn, and analytics are computed from usage. The primary path is synchronous: stamp a `zc-usage` header on the successful response. ZeroClick records the usage as it returns the response to the agent. Reserve the [asynchronous report](#report-usage) for work that finishes after the response is gone.

## Settle on the response

`zc-usage` is a response header carrying a JSON array of what the request used:

```json theme={null}
[{ "serviceSlug": "product-watch", "meterSlug": "requests", "quantity": 1 }]
```

The SDK helpers build and validate it:

<CodeGroup>
  ```ts TypeScript theme={null}
  const response = Response.json({ watching: true });
  return zeroClick.withUsage(response, [
    { serviceSlug: "product-watch", meterSlug: "requests", quantity: 1 },
  ]);
  ```

  ```python Python theme={null}
  return to_fastapi(
      zeroclick.with_usage(
          ZcResponse.json({"watching": True}),
          [SyncUsageItem(service_slug="product-watch", meter_slug="requests", quantity=1)],
      )
  )
  ```

  ```go Go theme={null}
  // The Meter middleware attaches zc-usage for the quantities it declared,
  // and only on a 2xx; nothing to write in the handler.
  mux.Handle("/v1/product-watch", seller.Meter(sellers.PerRequest("requests", 1))(
  	http.HandlerFunc(productWatch)))
  ```
</CodeGroup>

Three rules govern the header:

* **2xx only.** A 4xx is the buyer's bad input and a 5xx is your failure; neither delivered anything, so neither bills. The Go middleware enforces this. In TypeScript and Python, call `withUsage` only on the success path.
* **ZeroClick strips it.** The agent never sees `zc-usage`: the proxy consumes it and removes it from the response.
* **Settle the actual quantity.** For a fixed `quantity`, that is what you declared. For a `maxQuantity` ceiling, it is anywhere from zero up to the ceiling. See [charge up to a maximum](/integrate/charge-up-to-a-maximum). The platform accepts `0` (a delivered response that produced nothing settles the ceiling at \$0). The TypeScript and Python helpers validate quantities as at least 1, so settle a zero-output ceiling by omitting that item.

A paid, delivered response always settles, even if your header is missing or malformed: fixed items settle at the authorized quantity, ceilings settle at zero. That guarantee protects the buyer's payment from a garbage header. It does not excuse the report: an unreported ceiling settles as if nothing was produced.

## Report usage

Some work outlives the response: a job that keeps extracting after you return `202`, tokens streamed over a connection that has already closed, a worker that settles from a queue. Report that usage asynchronously with `POST /v1/usage`, through `reportUsage`, `report_usage`, or `ReportUsage`, using the `usage:write` key ([split keys](/integrate/keys-and-secrets#split-read-and-write-keys) put it in the worker):

<CodeGroup>
  ```ts TypeScript theme={null}
  const result = await zeroClick.reportUsage({
    zcAgentId: "agt_x7f2kq93bh0d",
    idempotencyKey: "zcreq_8h2m4x0q9k1f_output_tokens", // derived, never random
    serviceSlug: "product-watch",
    meterSlug: "output_tokens",
    quantity: 4200,
  });
  console.log(result.recorded, result.duplicate);
  ```

  ```python Python theme={null}
  result = await zeroclick.report_usage(
      zc_agent_id="agt_x7f2kq93bh0d",
      idempotency_key="zcreq_8h2m4x0q9k1f_output_tokens",  # derived, never random
      service_slug="product-watch",
      meter_slug="output_tokens",
      quantity=4200,
  )
  ```

  ```go Go theme={null}
  _, err := seller.ReportUsage(sellers.BackgroundContext(r), sellers.ReportUsageInput{
  	AgentID:        zc.AgentID,
  	ServiceSlug:    "product-watch",
  	MeterSlug:      "output_tokens",
  	Quantity:       4200,
  	IdempotencyKey: zc.RequestID + "_output_tokens", // derived, never random
  })
  if err != nil {
  	// Delivered but unbilled. Log it; do not fail a request the buyer already has.
  	log.Printf("usage not reported: %v", err)
  }
  ```
</CodeGroup>

<Warning>
  In Go, report with `sellers.BackgroundContext(r)`, not `r.Context()`. Go cancels the request context the moment your handler returns, so a report on it is silently dropped. The work goes unbilled, with nothing in the logs.
</Warning>

**Derive the idempotency key.** Reports are idempotent per service on `idempotencyKey`, and the key is yours to construct: the SDKs never generate one. Derive it from stable facts, such as the request id plus the meter (`zcreq_8h2m4x0q9k1f_output_tokens`). A random key turns every retry into a double bill. `duplicate: true` means the key already landed and the stored event was replayed: a success, not an error. There are no automatic retries. Retry yourself with the same key.

On the wire, the call and its response:

```http Request theme={null}
POST /v1/usage HTTP/1.1
Host: api.zeroclick.io
Authorization: Bearer zc_…
Content-Type: application/json

{
  "zcAgentId": "agt_x7f2kq93bh0d",
  "idempotencyKey": "zcreq_8h2m4x0q9k1f_output_tokens",
  "serviceSlug": "product-watch",
  "meterSlug": "output_tokens",
  "quantity": 4200,
  "occurredAt": "2026-07-28T17:04:05.000Z"
}
```

```json Response theme={null}
{
  "recorded": true,
  "duplicate": false,
  "usageEvent": {
    "id": "use_2j8fq0w7xk4m",
    "zcAgentId": "agt_x7f2kq93bh0d",
    "zcRequestId": null,
    "idempotencyKey": "zcreq_8h2m4x0q9k1f_output_tokens",
    "meterSlug": "output_tokens",
    "quantity": 4200,
    "totalCostUsd": "0.042000",
    "source": "async",
    "occurredAt": "2026-07-28T17:04:05.000Z"
  }
}
```

`quantity` is an integer of at least 1 (maximum 2,147,483,647); `occurredAt` is an optional ISO timestamp for when the usage happened. A report can fail with `402` (`access_inactive`, `plan_expired`, `usage_exhausted`), `409` (`meter_not_priced`), or otherwise `404` for a missing resource. The SDKs surface these as typed errors carrying the status and reason. See [errors](/resources/errors). A failed report after delivery means work went unbilled. Log it with the reason rather than failing a request the buyer already received.

## Choose a path

* **Settle on the response** when you know the quantity before you respond: most requests, including ceilings whose actual you know by the end of the handler.
* **Report asynchronously** when the work finishes after the response, when settlement happens in a separate worker, or when there is no HTTP response to stamp (a connection upgraded away from HTTP, a queue consumer).
* **Combine them** for work you can't size up front: a fixed part settled on the response, the variable part reported when you know it. That is the pattern in [charge up to a maximum](/integrate/charge-up-to-a-maximum).
