---
title: "Audit log"
description: "QuotaStack writes down every change and never edits it. Read the record back here."
---

# Audit log

QuotaStack writes every change to a log. No one can edit it.

The audit log answers one question: who changed this, and when.

QuotaStack writes an entry as each change happens. Nobody can edit an entry
afterwards, and nobody can delete one. That is what makes the log worth
reading.

Each entry holds a verb and a thing. `action` is the bare verb, such as
`granted`. `entity_type` is what it happened to, such as
`credit_transaction`. Read the two together.

The verb is not the dotted name a [webhook](/docs/concepts/webhooks) uses. Do
not match `credit.granted` here.

Every filter matches the whole value, exactly. Nothing partial, and case
matters.

The log is a record you read later. A webhook reaches you at the time. Use the
log for a question about the past.

## Which call do I want?

- You need to know who changed something, and when → `GET /v1/audit-log`
- You want to be told about changes as they happen → `listWebhookEvents`
  The audit log is a record you read later. A [webhook](/docs/concepts/webhooks) reaches you at the time. Use the log to answer a question about the past.

1 of 1 operations documented in full.

## List audit log entries

`GET /v1/audit-log`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Nothing. A read has no side effects.
- **Fires:** No webhooks.

This call returns what changed, newest first. QuotaStack writes an entry for a change and never edits it afterwards. Read `action` together with `entity_type`. `action` holds a bare verb, such as `created` or `granted`. The two together make the sentence: `granted` plus `credit_transaction`. The verb is not the dotted name a [webhook](/docs/concepts/webhooks) uses. Every filter matches the whole value exactly. There is no partial match, and case matters. `from` takes in the moment you name. `to` stops one tick before it. A timestamp QuotaStack cannot read returns `400`, and so does a bad cursor.

### Query parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `action` | string | optional | Keep only entries with this verb, such as `created` or `granted`. If you leave it out: You get every verb. |
| `entity_type` | string | optional | Keep only entries about this kind of thing, such as `customer` or `subscription`. If you leave it out: You get every kind. |
| `actor_type` | string | optional | Keep only entries made by this kind of actor. If you leave it out: You get them all. `api` — Somebody called the API with one of your keys. `system` — A QuotaStack job did it. Renewals and expiry sweeps land here. `admin` — A person did it from the dashboard. |
| `from` | string (date-time) | optional | Return entries made at this moment or later. An RFC 3339 timestamp. If you leave it out: You start at the oldest entry. |
| `to` | string (date-time) | optional | Return entries made before this moment. The moment itself is left out. If you leave it out: You run up to the newest entry. |
| `cursor` | string | optional | Opaque pagination cursor from the previous page's `pagination.next_cursor`. Omit to start at the first page. |
| `limit` | integer | optional | Page size. Default 20, max 100. |

### Response 200

```json
{
  "data": [
    {
      "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a81",
      "environment": "live",
      "actor_id": "system",
      "actor_type": "system",
      "action": "granted",
      "entity_type": "credit_transaction",
      "entity_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
      "before_state": null,
      "after_state": {
        "delta": 500000,
        "type": "adjustment"
      },
      "created_at": "2026-07-28T09:00:00Z"
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `data` | array | required |  |
| `data.actor_id` | string | required | Who did it. The value is an API key ID, the name of a QuotaStack job, or a dashboard user ID. Read `actor_type` to know which of the three you are holding. |
| `data.actor_type` | string | required | What kind of actor made the change. `api` — Somebody called the API with one of your keys. `system` — A QuotaStack job did it. Renewals and expiry sweeps land here. `admin` — A person did it from the dashboard. |
| `data.action` | string | required | The verb, on its own. `created`, `updated`, `deleted`, `granted`, `adjusted`, and `imported` are the ones you will see. Pair it with `entity_type` to know what happened. |
| `data.entity_type` | string | required | What kind of thing changed, such as `customer`, `subscription`, or `credit_transaction`. |
| `data.entity_id` | string (uuid) | required | Which one changed. Look it up with the matching read call for that kind. |
| `data.before_state` | object | optional | What the thing looked like beforehand. If you leave it out: A create has nothing to show here, so the field reads `null`. |
| `data.after_state` | object | optional | What the thing looked like afterwards. If you leave it out: A delete has nothing to show here, so the field reads `null`. |
| `pagination` | object | required | Cursor-based pagination envelope. Returned on every list endpoint. No `total` — cursor-only. |

### Errors

- `400` — Malformed request.
- `401` — Missing or invalid authentication.
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Audit log](/docs/concepts/audit-log)
- [Webhooks](/docs/concepts/webhooks)
