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

# Audit Log

> Query your organisation audit log through the Management API

# Audit Log

Read the same audit trail as **Settings → Audit log** in the Admin Tool. Each entry is one recorded action for the organisation that owns the API key: an API call, a configuration change, a webhook delivery, a GDPR export, or an order import once order-import audit logging is enabled.

<Note>
  This endpoint follows the same switch as recording Management and Public API requests. While that switch is off, an authenticated call returns **404** and the log is not read. Do not rely on this endpoint in production until audit logging is turned on for your organisation's Management API.
</Note>

**Permission:** `audit_log:read` (Audit log read). A key without it receives 403. A missing or invalid key receives 401.

## List audit log entries

```bash theme={null}
GET /v1/management/audit-logs
```

```bash theme={null}
curl -G "https://api.rxscale.com/v1/management/audit-logs" \
  -H "X-API-Key: your-api-key-here" \
  --data-urlencode "outcome=success_with_warnings" \
  --data-urlencode "created_from=1757836800" \
  --data-urlencode "created_to=1757923200" \
  --data-urlencode "limit=50" \
  --data-urlencode "page=0"
```

The organisation comes from the API key. There is no organisation parameter. A caller cannot read another organisation's log.

### Query parameters

All filters are optional and combined with AND. An unknown parameter, including `organisation_uid`, returns 400.

<ParamField query="log_type" type="string">
  One log type: `api_call`, `config_change`, `webhook_delivery`, `gdpr_export`, or `order_import`. Any other value returns 400.
</ParamField>

<ParamField query="event_type" type="string">
  One event type, such as `api_read`, `api_phi_read`, `api_write`, or `api_auth_failed`. Unknown values return 400.
</ParamField>

<ParamField query="source" type="string">
  Where the entry was recorded, such as `management_api_v1`, `public_api_v2`, or `admin_api_v1`.
</ParamField>

<ParamField query="success" type="boolean">
  `true` or `false`. Matches the stored success flag. For an order import, both `success` and `success_with_warnings` are stored as `success=true`.
</ParamField>

<ParamField query="outcome" type="string">
  `success`, `success_with_warnings`, or `rejected`.

  `success=true` includes warnings. `outcome=success` does not: it matches a successful entry that is not `success_with_warnings`. `outcome=rejected` matches a failed entry, or an entry whose payload outcome is `rejected`.

  Sending `success` and `outcome` together returns 400 when they disagree (`success=true` with `rejected`, or `success=false` with `success` or `success_with_warnings`).

  Warnings and refusals from order import appear only after order-import audit logging is enabled.
</ParamField>

<ParamField query="created_from" type="integer">
  Inclusive start, as unix seconds. `created_at` greater than or equal to this value.
</ParamField>

<ParamField query="created_to" type="integer">
  Exclusive end, as unix seconds. `created_at` less than this value. `created_from` later than `created_to` returns 400. Equal values match nothing.
</ParamField>

<ParamField query="entity_uid" type="string">
  Exact match, at most 255 characters. For a committed order import this is the internal order id. A refused import has no entity id and does not match this filter.
</ParamField>

<ParamField query="actor_user_uid" type="string">
  Exact match of the user who made a configuration change, GDPR export, or telemedicine-provider edit. At most 36 characters. API calls and order imports leave this empty and do not match.
</ParamField>

<ParamField query="api_key_identifier" type="string">
  Exact match of the API key identifier. This is not the secret, and it is a separate filter from `actor_user_uid`.
</ParamField>

<ParamField query="page" type="integer" default="0">
  Page number, starting at 0.
</ParamField>

<ParamField query="limit" type="integer" default="50">
  Page size. Minimum 1, maximum 200.
</ParamField>

Entries are newest first. When two entries share a timestamp, the higher id comes first.

There is no default time window. Omit both timestamps to page the newest entries, subject to the cap below.

### Pagination

The default page size is 50 and the maximum is 200. At most 10,000 matching entries are counted. When more match, `totalRegistries` is `10000`, `totalCapped` is `true`, and `totalPages` is based on that cap. Pages past that window are empty. Narrow `created_from` and `created_to`, or the other filters, to reach older entries.

### Rate limit

10 requests per second per IP address, shared with the rest of the Management API. A 429 is an HTML page that says `Too Many Requests`, not a JSON body, and it has no `Retry-After` header. See [Rate limits](/rate-limits).

### Example: order import with warnings

```json theme={null}
{
  "data": [
    {
      "uid": "3f1c2a10-9b8e-4d7c-a6f5-111111111111",
      "organisation_uid": "org_acme",
      "created_at": 1757836800,
      "log_type": "order_import",
      "event_type": "order_import",
      "source": "shopify_webhook",
      "success": true,
      "outcome": "success_with_warnings",
      "actor_user_uid": null,
      "api_key_identifier": null,
      "http_method": null,
      "route": null,
      "request_path": null,
      "client_ip": null,
      "user_agent": null,
      "duration_ms": null,
      "execution_id": "pubsub-msg-1001",
      "target_url": null,
      "response_status_code": null,
      "response_body": null,
      "error_message": "SKU is not in the shop catalogue (sku: sku_uid)",
      "entity_uid": "ord_1001",
      "notification_subscription_uid": null,
      "payload_version": null,
      "attempt_number": null,
      "permanent_failure": null,
      "payload": {
        "outcome": "success_with_warnings",
        "shop_identifier": "example.myshopify.com",
        "external_order_id": "5234567890",
        "external_fulfillment_id": "6123456789",
        "shopify_topic": "orders/updated",
        "triggered_at": "2026-10-06T12:00:00+00:00",
        "codes": [
          {
            "code": "items_missing",
            "message": "SKU is not in the shop catalogue (sku: sku_uid)",
            "external_line_id": "998877",
            "sku_uid": "sku_uid"
          }
        ]
      }
    }
  ],
  "totalRegistries": 1,
  "totalPages": 1,
  "totalCapped": false
}
```

`order_import` entries exist only after order-import audit logging is enabled.

### Example: rejected API call

```json theme={null}
{
  "uid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "organisation_uid": "org_acme",
  "created_at": 1757836900,
  "log_type": "api_call",
  "event_type": "api_auth_failed",
  "source": "management_api_v1",
  "success": false,
  "outcome": "rejected",
  "actor_user_uid": null,
  "api_key_identifier": "acme_live",
  "http_method": "GET",
  "route": "/v1/management/patients/<patient_uid>",
  "request_path": "/v1/management/patients/pat_1",
  "client_ip": "203.0.113.7",
  "user_agent": "partner-integration/2.1",
  "duration_ms": 12,
  "execution_id": "11111111-2222-3333-4444-555555555555",
  "target_url": null,
  "response_status_code": 403,
  "response_body": "{\"code\": \"permission_denied\", \"description\": \"API key lacks required permission: patient:read\"}",
  "error_message": "permission_denied: patient:read",
  "entity_uid": null,
  "notification_subscription_uid": null,
  "payload_version": null,
  "attempt_number": null,
  "permanent_failure": null,
  "payload": null
}
```

`outcome` is `rejected` because `success` is false, even when `payload` is null.

## What is included

Every entry has the same fields. A field is null when nothing was stored.

Included: the event type, outcome, IP address, user agent, API key identifier (never the secret), route template, request path with credential segments already redacted and query names only, the failed error body (at most 2,000 characters), the error message, the related entity id, and the event payload as it was stored.

## What is never included

Request bodies, query-parameter values, successful response bodies, API key secrets, customer name or address, and the legacy `meta_data` bag.

The response also omits internal columns `expires_at`, `deleted_at`, and `updated_at`.

## Errors

| Status | When | Body |
| - | - | - |
| 200 | The query is valid. An empty page is still 200. | `data`, `totalRegistries`, `totalPages`, `totalCapped` |
| 400 | Invalid query, unknown parameter, unknown enum, `created_from` after `created_to`, or `success` disagreeing with `outcome` | `{"error": {"<field>": ["<message>"]}}` |
| 401 | Missing `X-API-Key` | `{"code": "api_key_missing", "description": "API key header is expected"}` |
| 401 | Unknown key or wrong secret | `{"code": "invalid_api_key", "description": "Invalid API key"}` |
| 403 | The key lacks `audit_log:read` | `{"code": "permission_denied", "description": "..."}`. Branch on `code`. |
| 404 | Audit logging is turned off | `{"code": "not_found", "description": "Not found"}` |
| 405 | Any method other than `GET` | `{"error": "Method not allowed"}` |
| 429 | More than 10 requests per second from the client IP | HTML `Too Many Requests`. Not JSON. |

## Retention

This endpoint does not change how long entries are kept. Successful API reads that return no patient data are kept for 90 days. Reads of patient data, writes, failed requests, and this audit-log read itself are kept for 1 year. See [Audit log](/for-telemedicine-providers/audit-log).


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