Appearance
Retrieve a log
A single API call you made, with the exact body you sent and the exact body we answered with.
http
GET /v1/logs/{id}Requires a Full access API key.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The log id. This is the same value as the request_id on an error and the X-Request-Id header on every response. |
Example
bash
curl "https://api.sendgrail.com/v1/logs/$LOG_ID" \
-H "Authorization: Bearer $SENDGRAIL_API_KEY"js
const res = await fetch(`https://api.sendgrail.com/v1/logs/${id}`, {
headers: { Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}` },
});
const log = await res.json();Response
json
{
"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": "dev-test"
},
"request_body": {
"email": "not-an-email"
},
"response_body": {
"message": "The email field must be a valid email address.",
"code": "validation_error",
"errors": { "email": ["The email field must be a valid email address."] },
"request_id": "a816c847-153d-4be8-a43e-f05391ff01e6"
}
}request_body and response_body are whatever that call carried, so their shape depends on the endpoint that was called. They appear only here — the list leaves them out, because a page of a hundred bodies would be megabytes you didn't ask for.
An error tells you where its own log is
Every failure carries a request_id, and that is the log id:
json
{
"message": "The email field must be a valid email address.",
"code": "validation_error",
"request_id": "a816c847-153d-4be8-a43e-f05391ff01e6"
}So the debugging loop is closed without you having to search for anything: take the request_id from the error, GET /v1/logs/{that id}, and you are looking at exactly what you sent and exactly what we said. The same value is on the X-Request-Id header of every response, including successful ones — log it on your side and any call you ever made is one request away.
Fields
| Field | Type | Description |
|---|---|---|
object | string | Always "log". |
id | string | The log id — and the request_id of that call. |
created_at | string | ISO 8601 timestamp of when the call arrived. |
endpoint | string | The path called, e.g. /v1/emails. |
method | string | GET, POST, PATCH or DELETE. |
response_status | number | The HTTP status we answered with. |
latency_ms | number | How long we took, in milliseconds. |
ip | string | The IP the call came from. |
user_agent | string | null | The caller's User-Agent, if it sent one. It is not required. |
api_key | object | null | The key used: its id and name. Never the key itself. |
request_body | object | string | null | The body you sent. Retrieve only. |
response_body | object | string | null | The body we answered with. Retrieve only. |
Very large bodies are truncated when recorded. A truncated body can no longer be parsed as JSON, so it comes back as a string rather than an object — you see what was actually stored instead of nothing at all.
What is not in a log
Never the API key. A log names the key by id, so you can tell which key made a call, but the token itself is not recorded and cannot be recovered from here.
Attachment bytes. A base64 attachment on a send is replaced with [base64 omitted] before the body is stored — otherwise every log of a send would carry a copy of the file.
Logs are read-only
There is no endpoint to edit or delete a log. A log is evidence: if a key could erase its own trail, the trail would be worth nothing precisely when it mattered. Logs age out on a retention schedule instead — see List logs.