# Logs

Every request made with this team's credentials, with the status it answered and how long it took. This is where a send that went wrong is diagnosed.

## Who can read them

Both endpoints need a `full_access` key. A `sending_access` key is refused with `401 restricted_api_key` — the key is valid, it simply does not reach this endpoint. A revoked key of either kind is `403 restricted_api_key` instead, which is a different problem with the same name; the status is what tells them apart, and the [errors page](https://www.rasket.com/docs/errors) lists both.

## What is in a log

- The request line — method, endpoint, response status, latency and the `User-Agent` you sent.
- `request_id`, which is also in the `message` of any `500` you received, so an error you are holding leads straight to the log of the call that produced it.
- `api_key_id`, so a request can be traced to the credential that made it, and `email_id` when the request created one.

> Stored bodies are measurements, not your message. A body naming a password, token or key was dropped whole rather than partly hidden, and a send's content was replaced by its size and digest. Nothing here can give you back what you sent.

## Where a request came from

| source | Means |
| --- | --- |
| `api` | Your own code, calling this API with a key. |
| `dashboard` | A write made from the dashboard. The dashboard calls this same API, so its writes are logged too. |
| `sdk:<name>` | One of our client libraries, which names itself here. |

Reading this endpoint is itself a request, and is logged. The newest entry on a fresh call is usually that call.

## How long they are kept

Logs are kept for your plan's retention window — 30 days on the free plan — and are then deleted. A request older than that is gone rather than empty, and a log belonging to another team answers `404 not_found`, the same answer as one that never existed.

## Endpoints

### `GET /logs`

Every request made with this team's credentials, newest first.

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `status` | integer | The response status recorded on the request, such as `422`. |
| `source` | string | Where the request came from: `api`, `dashboard`, or `sdk:<name>` for one of our clients. |
| `search` | string | Text in the endpoint (`/emails/{email_id}`) or in the path that was requested, so an email ID finds the requests that named it. Case-insensitive, 1–200 characters. |
| `limit` | integer | How many logs to return, 1–100. Defaults to 20. |

#### Query parameters, less common

| Field | Type | Description |
| --- | --- | --- |
| `exclude_source` | string | Leave out one source, spelled as `source` is. `dashboard` shows every request your own code made. |
| `user_agent` | string | An exact User-Agent, as it was sent. Not a substring match. |
| `start_date` | string | ISO 8601 instant, inclusive. A calendar date alone is `422 invalid_parameter` — send `2026-09-09T00:00:00.000Z`. |
| `end_date` | string | ISO 8601 instant, inclusive. Must be at or after `start_date`, or the request is `422 invalid_parameter`. |
| `after` | string | Return the page that follows this log ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this log ID. Mutually exclusive with `after`. |

List request logs:

```sh
curl -X GET "https://api.rasket.com/logs?status=422&limit=20" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/logs?status=422&limit=20", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/logs?status=422&limit=20",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "0198f4c1-0000-7000-8000-000000000000",
      "created_at": "2026-09-09T10:16:44.902Z",
      "endpoint": "/emails",
      "method": "POST",
      "response_status": 422,
      "user_agent": "acme-billing/1.0",
      "source": "api",
      "request_id": "req_0198f4c1000070008000000000000000",
      "latency_ms": 41,
      "api_key_id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75"
    }
  ]
}
```

- Needs a full-access key. A sending-only key is refused with `401 restricted_api_key`.
- Writes made from the dashboard go through this same API and are logged with `source: dashboard`, so the list is every write to your data rather than only the ones your code made.
- Reading this endpoint records a request of its own, so the newest entry on a fresh call is usually that call.
- Logs are kept for your plan's retention window — 30 days on the free plan — and then deleted. A request older than that is gone, not empty.

### `GET /logs/{log_id}`

One request in full, with the bodies that were stored for it.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `log_id` (required) | string | The ID of the log. |

Retrieve a request log:

```sh
curl -X GET "https://api.rasket.com/logs/0198f4c1-0000-7000-8000-000000000000" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/logs/0198f4c1-0000-7000-8000-000000000000", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/logs/0198f4c1-0000-7000-8000-000000000000",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "log",
  "id": "0198f4c1-0000-7000-8000-000000000000",
  "created_at": "2026-09-09T10:16:44.902Z",
  "endpoint": "/emails",
  "method": "POST",
  "response_status": 422,
  "user_agent": "acme-billing/1.0",
  "source": "api",
  "request_id": "req_0198f4c1000070008000000000000000",
  "latency_ms": 41,
  "api_key_id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
  "request_body": {
    "from": "billing@example.com",
    "subject": "Invoice",
    "html_bytes": 812
  },
  "response_body": {
    "statusCode": 422,
    "name": "missing_required_field",
    "message": "to is required."
  },
  "email_id": null
}
```

- `request_body` is the body as it was stored, not as it was sent. A body naming a password, token or key was dropped whole rather than partially hidden; a send's message content was replaced by its measurements, so `html` and `text` appear as byte counts and each attachment as its filename, size and SHA-256. There is no way to recover the original.
- `response_body` is present for a failed request only. A successful call returns `null` — you already have that body.
- `email_id` links the log to the email the request created, when it created one. A batch send creates many and links none.
- A log belonging to another team is `404 not_found`, the same answer as a log that never existed.
