Skip to content

List logs

Every call made to this API with one of your keys — including the one that fetched this list.

http
GET /v1/logs

Requires a Full access API key.

Query parameters

ParameterTypeDescription
limitnumberHow many to return. Default 20, min 1, max 100.
afterstringReturn logs after this id. Cannot be combined with before.
beforestringReturn logs before this id. Cannot be combined with after.
methodstringOnly calls with this method — GET, POST, PATCH, DELETE. Case-insensitive.
response_statusnumberOnly calls we answered with this status, e.g. 422.
endpointstringOnly calls to paths starting with this. /v1/emails also matches /v1/emails/batch.

Newest first. Cursor-paginated — there is no total, because a keyset paginator never counts the table, and this is the largest table you have.

Example

bash
# every call that failed validation
curl "https://api.sendgrail.com/v1/logs?response_status=422" \
  -H "Authorization: Bearer $SENDGRAIL_API_KEY"
js
const res = await fetch(
  'https://api.sendgrail.com/v1/logs?response_status=422',
  { headers: { Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}` } },
);

const { data, has_more } = await res.json();

Response

json
{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "object": "log",
      "id": "a816c847-153d-4be8-a43e-f05391ff01e6",
      "created_at": "2026-07-14T20:20:00+00:00",
      "endpoint": "/v1/contacts",
      "method": "POST",
      "response_status": 422,
      "latency_ms": 13,
      "ip": "127.0.0.1",
      "user_agent": "curl/8.7.1",
      "api_key": { "id": "c7e0321e-95f2-478f-8af1-bbe1f705e5ce", "name": "production" }
    }
  ]
}

The request and response bodies are not in the list — fetch one log with Retrieve a log to see them. A page of a hundred bodies would be megabytes you didn't ask for.

Everything is logged, including reads

A GET is logged the same as a POST, so this endpoint appears in its own output. That is deliberate: "who read my contacts, and when" is a question worth being able to answer, and a log that only recorded writes could not answer it.

Finding the call that went wrong

You rarely want to page through logs. You usually have a failure and want that call — and an error already tells you where it is. Its request_id is the log id:

bash
curl "https://api.sendgrail.com/v1/logs/$REQUEST_ID" \
  -H "Authorization: Bearer $SENDGRAIL_API_KEY"

The filters are for the other case: a sweep. "Show me everything that 422'd this week", or "everything that touched /v1/emails".

Retention

Logs are kept for 30 days, and their request/response bodies for 7. After 7 days a log still tells you what was called, when, from where, and what status it returned — it just no longer carries the bodies, which are the bulk of the weight. After 30 days the row goes too.

This is what keeps the table, and the queries over it, from growing without bound — every call you make adds a row to it, forever, otherwise.

If you need a call's body kept longer than a week, copy it out when you make it.

Transactional email on your own domain.