# Pricing and admission

Your policy decides what to charge and whether to accept new work. The SDK supplies engine metrics and tracks Canopy activity, but it does not impose a slot budget or automatically reject requests at a concurrency threshold.

## Publish standing prices

`standingPrice(context)` returns your current terms. It can return a value or a promise. The SDK publishes the input and output rates after restoring a connection and every 30 seconds by default.

Standing prices describe your offer. Request selection uses valid, time-limited RFQ quotes. Cache terms belong to individual quotes; the standing-price wire message sends only the model, input rate, output rate, and currency.

## Accept or decline a request

`quote(context)` receives the same activity snapshot plus `rfq`, the request for quote. Return one of:

```ts
// Decline new work.
{ accept: false }

// Commit to serving it at these prices if selected before expiry.
{ accept: true, terms: { inputPerMTok: "0.35", outputPerMTok: "0.61" } }
```

The callback can return a promise, but it must finish before `rfq.deadline`. Throwing, returning invalid terms, or missing the deadline declines the request.

The RFQ includes a canonical model ID, token estimates, streaming and tool requirements, and structured-output requirements. It never includes prompt content. `endpoint` identifies the native protocol; its omission means Chat Completions. See the [RFQ fields](/providers/protocol#rfqrequest).

Only accept requests whose terms you can honor. Sending a quote makes a binding offer; `onAward` is a notification, not another admission decision.

## Read activity counts

Both policy callbacks receive:

| Field                | Meaning                                                                          |
| -------------------- | -------------------------------------------------------------------------------- |
| `activeRequests`     | Awarded Canopy requests until completion or release, including engine queue time |
| `idleCachedSessions` | Unexpired cache commitments with no active Canopy request                        |
| `pendingQuotes`      | Binding quotes awaiting award, release, or expiry                                |
| `metrics`            | Fresh engine metrics, or `undefined`                                             |
| `collectedAt`        | UTC timestamp for those metrics, or `undefined`                                  |

Do not add `activeRequests` to `metrics.runningRequests`. They can count the same work. Aggregate engine metrics cannot identify which Canopy requests are running rather than queued.

Pending quotes are not active requests, but they can become active if selected. Include them when deciding whether you can promise more work. The [getting started policy](/providers/getting-started#configure-your-provider) uses the larger of the active and engine-running counts, then adds pending quotes. This is an example policy, not a physical scheduling guarantee.

## Offer cached input

Add a cached input rate and retention terms to an accepted quote:

```ts
const terms = {
  inputPerMTok: "0.35",
  cachedInputPerMTok: "0.175",
  outputPerMTok: "0.61",
  cache: {
    ttlSeconds: 300,
    minCacheableTokens: 1024,
  },
};
```

A cache offer promises availability at these prices through its expiry. Successful cacheable requests refresh that expiry. Follow-ups can skip the RFQ, keep the original terms, and overlap under the same commitment.

New-quote limits do not stop those follow-ups. Account for possible cached traffic when deciding how much new work to accept. A cache commitment does not reserve a dedicated execution slot; your engine still owns scheduling and physical cache retention.

`idleCachedSessions` counts a commitment once, only while none of its requests are active. It is not a count of tokens or occupied GPU slots.

The wire format allows cache TTLs from 1 to 86,400 seconds. Schema validity alone does not guarantee that routing will treat an offer as useful caching. See [How routing works](/routing) for cache selection rules and [Lifecycle and caching](/providers/lifecycle) for retention obligations.

## Price format

Use decimal USD strings per million tokens. Prices allow one to six integer digits and up to six fractional digits. Zero is allowed; negative values, exponent notation, and JSON numbers are not.

`inputPerMTok` and `outputPerMTok` are required. `cachedInputPerMTok`, `cache`, `currency`, and `expiresAt` are optional. If supplied, `currency` must be `"USD"`. Use a UTC timestamp for `expiresAt` to bound how long the quote remains available for selection. This is separate from an awarded request's execution deadline.
