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

# Key usage batches

> POST /v1/key_usage/batches reports requests made with the API keys that your account creation gate issued: the event fields, the limits, idempotency and the answer.

`POST /v1/key_usage/batches` records a batch of requests made with API keys that your account creation gate issued to your accounts. It needs the `assess` scope.

The Node and Python SDKs buffer usage and call it for you. [Report key usage](https://docs.noresponse.org/guides/key-usage.md) explains the buffer, sampling and the `shared_api_key` rule.

## The request

```ts title="TypeScript" theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
const clientIp = "81.2.69.160"; // the client address your gateway saw
const batch = await nr.keyUsage.record({
  events: [
    {
      keySha256: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      ip: clientIp,
      occurredAt: new Date().toISOString(),
      userAgent: "python-requests/2.32.3",
      model: "gpt-4o-mini",
      inputTokens: 812,
      outputTokens: 164,
    },
  ],
});
console.log(batch.id, batch.accepted, batch.ignored);
```

```python title="Python" theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
from datetime import datetime, timezone

from noresponse.models.shared import KeyUsageEventTypedDict

client_ip = "81.2.69.160"  # the client address your gateway saw

event: KeyUsageEventTypedDict = {
    "key_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "ip": client_ip,
    "occurred_at": datetime.now(timezone.utc).isoformat(),
    "user_agent": "python-requests/2.32.3",
    "model": "gpt-4o-mini",
    "input_tokens": 812,
    "output_tokens": 164,
}
batch = client.key_usage.record(events=[event])
print(batch.id, batch.accepted, batch.ignored)
```

```bash title="cURL" theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl https://api.noresponse.org/v1/key_usage/batches \
  -H "Authorization: Bearer $NORESPONSE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: usage-gw1-000418" \
  -d '{
    "events": [
      {
        "key_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "ip": "81.2.69.160",
        "occurred_at": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
        "user_agent": "python-requests/2.32.3",
        "model": "gpt-4o-mini",
        "input_tokens": 812,
        "output_tokens": 164
      }
    ]
  }'
```

A batch holds 1 to 500 events in `events`. The body is at most 256 KiB. A larger body answers 413 [`payload_too_large`](https://docs.noresponse.org/problems/payload-too-large).

### Event fields

**key\_sha256** `string` *required*

The SHA-256 of the UTF-8 bytes of the issued key, as 64 lower-case hex characters. It is the value that the assessment of the account sent as `issued_credential.sha256`.

**ip** `string` *required*

The client IP address of the request, IPv4 or IPv6, as your server saw it. At most 64 characters.

**occurred\_at** `string` *required*

When the request occurred, as RFC 3339.

**user\_agent** `string`

The `User-Agent` of the request, at most 512 characters, with no control characters.

**model** `string`

The model or product that the request used. At most 128 characters from letters, digits and `.`, `_`, `:`, `/`, `@`, `+`, `-`.

**input\_tokens** `integer`

A whole number from 0 to 1,000,000,000.

**output\_tokens** `integer`

A whole number from 0 to 1,000,000,000.

**sample\_rate** `number` (default: 1)

More than 0 and at most 1. The share of requests that your sampler records. For example, `0.1` means that the event stands for 10 requests in the request and token totals.

### What NoResponse refuses and ignores

NoResponse answers 400 [`invalid_request`](https://docs.noresponse.org/problems/invalid-request) in these cases:

* An unknown member, such as `tenant_id`.
* A raw key in `key_sha256`.

NoResponse does not refuse an event in these cases. It counts the event in `ignored`, and the event adds nothing:

* The `ip` is private, reserved or unreadable.
* The `occurred_at` is more than 24 hours old, or more than 5 minutes in the future.

### Idempotency

The `Idempotency-Key` is required. It works as it does on `POST /v1/assessments`, for 24 hours:

* The same key and body answers the same record with `Idempotent-Replayed: true`, and it counts nothing again.
* The same key with a different body answers 409 [`idempotency_key_reused`](https://docs.noresponse.org/problems/idempotency-key-reused).

Use a new key for each batch.

## The answer

The answer is 202 with the recorded batch.

**id** `string`

The id of the batch.

**received\_at** `string`

When NoResponse got the batch.

**accepted** `integer`

The events that NoResponse added to the usage of the key.

**ignored** `integer`

The events that added nothing.

```json title="Batch response" theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
{
  "id": "kub_01JA2ZE4M5N6P7Q8R9S0T1V2W3",
  "received_at": "2026-10-23T09:14:12Z",
  "accepted": 1,
  "ignored": 0
}
```

There is nothing to poll after the answer. When a key goes over the limit, the next read of each account that got the key carries `shared_api_key` in `risk.reasons`. See [Reason codes](https://docs.noresponse.org/reference/reason-codes.md#accounts).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.