# Logs

Every environment keeps its app's logs: what your code writes with `console.log` (and `info`, `warn`, `error`, `debug`), each request with its method, path, status and duration, uncaught exceptions with their stack, and cron and queue runs. Read them with the API, `flatstack logs`, or the `get_logs` MCP tool.

## What is kept

| Plan | Logs are kept for |
| --- | --- |
| Hobby | 3 days |
| Pro | 7 days |
| Studio | 30 days |
| Unclaimed sandbox | 1 day |

Lines appear within about 10 seconds. Older lines are deleted once a day; a read never returns lines older than your plan keeps, whatever `since` asks for.

Secrets are removed before anything is stored, so they can't be read back later:

- In objects you log, any field whose name contains `token`, `secret`, `password`, `key`, `authorization` or `cookie`, and fields named `body`, `html`, `text`, `prompt`, `messages` or `content`, become `[redacted]`.
- In text, `name=value` and `name: value` pairs with names like those, `Bearer` and `Basic` credentials, passwords in URLs (`postgres://user:…@host`), and well-known key formats (Stripe, AWS, GitHub, Slack, OpenAI and Anthropic keys, Flatstack tokens, JWTs, private keys) become `[redacted]`.
- Request query strings are never stored, only the path.
- A line longer than 8,192 characters is cut.

## GET /v1/projects/{projectId}/logs

Needs `logs:read`. Owners, admins, developers and viewers have it; deploy tokens can carry it.

| Query | Meaning |
| --- | --- |
| `environment` | `production` (the default) or `preview/<name>`. A deploy token for one environment reads only that one. |
| `since` | ISO 8601 (`2026-10-29T12:00:00Z`) or relative: `30s`, `15m`, `2h`, `7d`. Default `1h`. |
| `level` | The lowest level to return: `debug`, `info`, `warn` or `error`. `warn` returns warnings and errors. |
| `search` | Only lines whose message or path contains this text, ignoring case. |
| `limit` | Lines to return, 1 to 1000. Default 100. |

```json
{
  "environment": "production",
  "since": "2026-10-29T11:00:00.000Z",
  "until": "2026-10-29T12:00:00.000Z",
  "retentionDays": 7,
  "lines": [
    {
      "timestamp": "2026-10-29T11:58:02.114Z",
      "level": "error",
      "kind": "request",
      "message": "POST /api/checkout 500",
      "method": "POST",
      "path": "/api/checkout",
      "status": 500,
      "durationMs": 41,
      "deployId": "dep_…"
    }
  ],
  "hasMore": false,
  "warnings": []
}
```

Lines are newest first. `kind` is `log`, `request`, `exception`, `cron` or `queue`. A request is `warn` at 4xx and `error` at 5xx or when your code threw. `deployId` is the deploy (or rollback) that served the line, or `null` for the placeholder Worker and for lines logged before 30 October 2026, when deploys started being recorded on each line.

`hasMore: true` means more lines match than were returned: narrow `since` or raise `limit`. `warnings` says when `since` reached past your plan's retention, or when a search had too many logs to read in one go.

| Error | When |
| --- | --- |
| `400 validation_failed` | `since`, `level` or `limit` isn't one of the forms above |
| `403 forbidden` | The token has no `logs:read` |
| `404 not_found` | No such project or environment in your account, or another environment than a deploy token's |

## flatstack logs

```bash
flatstack logs                          # the last hour of production
flatstack logs --since 15m --level warn
flatstack logs --env preview/checkout --search "stripe" --limit 500
flatstack logs --json                   # the API's response, for scripts and agents
```

Live tail (`--tail`) and filtering by function and end user are coming.

## MCP

`get_logs` takes the same filters as the API: `projectId`, `environment`, `since`, `level`, `search`, `limit`. See [Connecting AI tools over MCP](../apps/mcp.md).