# 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, each attachment as its filename, size and SHA-256 (an attachment fetched by `path` as that URL's origin only), and custom headers as their names only. Inbox API requests keep no body, and an MCP request keeps only its method and tool name. 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.
