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

# List Audit Logs

> Retrieve the append-only audit trail — every successful config-grade mutation with its actor, route, entity and request payload, newest first.

Every successful configuration mutation made through the API is recorded as
one audit row with the actor, the route, the entity it touched and the request
payload. "Configuration" is an allowlist of resource prefixes: `plans`,
`billable-metrics`, `coupons`, `webhooks`, `wallets`, `usage-alerts`,
`dunning-campaigns`, `cancel-flows`, `settings`, `team`, `developer`,
`accounting`, `mandates`, `quotes` and `credit-notes`. Any `POST`, `PUT`,
`PATCH` or `DELETE` under one of those prefixes that returns `2xx` is captured;
money-movement and high-volume ingest routes (payments, usage events,
checkout) are deliberately not on the list because they already leave their
own durable records. The table is append-only: updates and deletes are
rejected at the database level, so history can only be added to, never
rewritten.

<Warning>
  `request_body` is stored verbatim — nothing is redacted. A credential
  submitted to an audited route is kept in the trail: for example the
  `access_token` posted to `POST /v1/accounting/connect-token/:provider` appears
  in that entry's `request_body`. Treat audit-log read access accordingly.
</Warning>

This is the trail for "who changed what, when". For the financial side of
the books — which postings were made — use the
[ledger](/api-reference/ledger/entries) and its
[transaction](/api-reference/ledger/transaction) pages; for recorded
reconciliation checks use
[List Reconciliation Runs](/api-reference/finance/reconciliation-runs-list).

## Query Parameters

| Parameter     | Type               | Required | Description                                                                             |
| ------------- | ------------------ | -------- | --------------------------------------------------------------------------------------- |
| `entity_type` | string             | No       | Filter to one resource segment, e.g. `plans`, `webhooks`, `wallets`.                    |
| `entity_id`   | string             | No       | Filter to one entity by its `:id` path parameter.                                       |
| `actor`       | string             | No       | Filter to one actor — a dashboard user id, or `api_key`.                                |
| `from`        | string (date-time) | No       | Only entries created at or after this RFC 3339 timestamp.                               |
| `to`          | string (date-time) | No       | Only entries created before this RFC 3339 timestamp (exclusive).                        |
| `limit`       | integer            | No       | Maximum entries to return. Default `100`, max `250`; larger values are capped at `250`. |
| `offset`      | integer            | No       | Number of entries to skip for pagination. Default `0`.                                  |

Results come back newest first. There is no total count in the response;
page by `limit`/`offset` until a page comes back short.

## Example Request

```bash theme={null}
curl "https://api.recurso.dev/v1/audit-logs?entity_type=plans&from=2026-08-01T00:00:00Z&limit=2" \
  -H "Authorization: Bearer $API_KEY"
```

## Response

```json theme={null}
{
  "data": [
    {
      "id": "2e7c4a91-6b3d-4f8e-a5c2-9d1f7b3e5a60",
      "tenant_id": "0b7a3f7e-4a2b-4c1d-9e8f-1a2b3c4d5e6f",
      "actor": "b9071c55-0e14-4720-80f3-665613ceb7de",
      "action": "PUT /v1/plans/:id/charges",
      "entity_type": "plans",
      "entity_id": "4f8a2c6e-1d3b-4e7a-9c5f-8b0d2e4a6c19",
      "status": 200,
      "request_body": "[{\"metric_id\":\"7d2b9f41-8c3e-4a5d-b6f0-1e4a7c9d2b58\",\"charge_model\":\"per_unit\",\"amounts\":{\"INR\":{\"unit_amount\":\"0.0035\"}}}]",
      "ip": "203.0.113.42",
      "created_at": "2026-08-21T11:42:17Z"
    },
    {
      "id": "9c1e5b73-4a2f-4d6c-8e0b-3f7a9c1d5e28",
      "tenant_id": "0b7a3f7e-4a2b-4c1d-9e8f-1a2b3c4d5e6f",
      "actor": "api_key",
      "action": "POST /v1/plans",
      "entity_type": "plans",
      "status": 201,
      "request_body": "{\"name\":\"Growth\",\"code\":\"growth-monthly\",\"interval_unit\":\"month\",\"interval_count\":1,\"amount\":499900,\"currency\":\"INR\"}",
      "ip": "198.51.100.17",
      "created_at": "2026-08-14T09:03:55Z"
    }
  ]
}
```

## Fields

| Field          | Type               | Description                                                                                                                                             |
| -------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | string (uuid)      | The audit entry's id                                                                                                                                    |
| `tenant_id`    | string (uuid)      | The tenant the mutation was made in                                                                                                                     |
| `actor`        | string             | Who performed the action: a dashboard user id, or `api_key` for API-key-authenticated calls                                                             |
| `action`       | string             | HTTP method plus route template, e.g. `PUT /v1/plans/:id/charges`                                                                                       |
| `entity_type`  | string             | First resource segment of the route (`plans`, `wallets`, …)                                                                                             |
| `entity_id`    | string             | The `:id` path parameter, when the route has one. Omitted on collection routes such as `POST /v1/plans`                                                 |
| `status`       | integer            | The response HTTP status. Only `2xx` mutations are recorded                                                                                             |
| `request_body` | string             | The raw request payload as a string, captured verbatim (no redaction) and truncated to 4 KB with a trailing `...`. Omitted when the request had no body |
| `ip`           | string             | Client IP the request came from. Omitted when unknown                                                                                                   |
| `created_at`   | string (date-time) | When the mutation was recorded                                                                                                                          |

## Errors

| Status | Code                | When                                                             | Fix                                                                                |
| ------ | ------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `400`  | `validation_failed` | `from` or `to` is not an RFC 3339 timestamp                      | Use the form `2026-08-01T00:00:00Z`                                                |
| `401`  | `unauthorized`      | No `Authorization` bearer token (and no session cookie) was sent | Send `Authorization: Bearer $API_KEY`                                              |
| `401`  | `invalid_api_key`   | A bearer token was sent but is not a valid key                   | Check the key in [Developer keys](/api-reference/developer/list-keys)              |
| `401`  | `key_mode_mismatch` | The key's live/test mode does not match this API host            | Use a key for the right mode — see [Authentication](/api-reference/authentication) |

<Note>
  The spec entry for this operation declares only `200` and `401`, so the
  playground on this page does not list the `400`. The handler does return
  `400 validation_failed` for a malformed `from` or `to`; the row above
  documents runtime behaviour.
</Note>

Errors use the standard envelope — see [Errors](/api-reference/errors).


## OpenAPI

````yaml GET /v1/audit-logs
openapi: 3.1.0
info:
  title: Recurso API
  version: 1.0.0
  description: |
    The Recurso billing engine REST API.

    Authenticate by passing your API key as a bearer token:

        Authorization: Bearer <api_key>

    Obtain an API key by registering a tenant via `POST /auth/register`.
    All authenticated endpoints live under the `/v1` prefix. Mutating
    endpoints support idempotency via the `Idempotency-Key` header.
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://billing.example.com
    description: >-
      Example deployment — substitute the base URL of your own Recurso
      deployment.
security:
  - bearerAuth: []
tags:
  - name: System
    description: Health, version, and API metadata
  - name: Auth
    description: Tenant registration
  - name: Plans
    description: Product catalog plans
  - name: Customers
    description: Customer management
  - name: Subscriptions
    description: Subscription lifecycle
  - name: Invoices
    description: Invoices, PDFs, and Indian GST e-invoicing
  - name: Coupons
    description: Discounts
  - name: Usage
    description: Metered usage events
  - name: Credit Notes
    description: Customer credits
  - name: Quotes
    description: Quote-to-invoice lifecycle
  - name: Webhooks
    description: Webhook endpoint management and event feed
  - name: Analytics
    description: Revenue analytics
  - name: Checkout
    description: Public hosted checkout for invoices
  - name: Payments
    description: Payment order creation
  - name: Inbound Webhooks
    description: Receivers for payment-gateway callbacks (Razorpay, Stripe)
  - name: Customer Portal
    description: Customer-facing portal — magic-link auth and session-scoped data
  - name: Developer
    description: API key management
  - name: Account
    description: Tenant account settings
  - name: Finance
    description: Ledger accounts, entries, reconciliation, and revenue recognition
  - name: Settings
    description: Tax (GST) and e-invoicing (IRP) configuration
  - name: Consents
    description: Consent records for RBI-compliant recurring billing
  - name: Referrals
    description: Customer referral program
  - name: Gifts
    description: Gift subscriptions
  - name: Mandates
    description: UPI Autopay mandates
  - name: Offline Payments
    description: Virtual accounts and manually recorded payments
  - name: Organizations
    description: Multi-entity organizations grouping several tenants
  - name: Accounting
    description: QuickBooks / Xero accounting integrations
  - name: Churn
    description: Churn risk scoring and alerts
  - name: Cancel Flows
    description: Configurable retention flows shown at cancellation time
  - name: Dunning
    description: Dunning analytics and multi-channel dunning campaigns
paths:
  /v1/audit-logs:
    get:
      tags:
        - Account
      summary: List the append-only audit trail
      description: >-
        Every successful config-grade mutation (plans, metrics, charges,
        coupons, webhooks, wallets, alerts, team, settings, ...) is recorded
        with actor, route, entity, and the request payload. The table is
        immutable: updates and deletes are rejected at the database level.
      operationId: listAuditLogs
      parameters:
        - name: entity_type
          in: query
          schema:
            type: string
        - name: entity_id
          in: query
          schema:
            type: string
        - name: actor
          in: query
          schema:
            type: string
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 250
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Audit entries, newest first
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuditLog'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  schemas:
    AuditLog:
      type: object
      description: One immutable record of a config-grade mutation.
      properties:
        id:
          type: string
          format: uuid
        actor:
          type: string
          description: Dashboard user id, or api_key
        action:
          type: string
          description: METHOD + route template
        entity_type:
          type: string
        entity_id:
          type: string
        status:
          type: integer
        request_body:
          type: string
          description: Truncated to 4KB
        ip:
          type: string
        created_at:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error detail.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
              examples:
                - validation_failed
                - unauthorized
                - forbidden
                - not_found
                - conflict
                - rate_limited
                - internal_error
                - invalid_api_key
                - key_mode_mismatch
                - over_refund
                - invoice_not_paid
                - invoice_already_paid
            message:
              type: string
              description: Human-readable explanation.
  responses:
    Unauthorized:
      description: Missing or invalid credentials (API key or session cookie).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Tenant API key obtained from `POST /auth/register` or `POST
        /v1/developer/keys`.

````