Logs · Reference
API
Send log lines from any app with one HTTPS request. The Laravel SDK uses this API; an app in another language can call it directly.
Base URL
https://services.my-beast.com/logsSend every request over HTTPS. The API has one endpoint, which takes a batch of lines. Reading lines back is done on your project's pages: Search and Live.
Authentication
Send the project's server secret for Logs as a bearer token: Authorization: Bearer sk_…. It is the same secret as MYBEAST_LOGS_SECRET, found or made as the quickstart says. A Logs key opens Logs only, and its project is the one the lines go to.
Server side only
Send lines
POST/v1/logs
The body is {"lines": [...]}, at most 1,000 lines and 4 MiB as sent, with Content-Type: application/json. Gzip it to send more at once, with Content-Encoding: gzip: unpacked, it may be up to 32 MiB. The Laravel SDK always does.
{
"lines": [
{
"id": "01JBFX3W2M4R8Y6T0Q9N7K5H3D",
"at": "2026-10-27T12:34:56.123456+00:00",
"level": "info",
"message": "Invoice 1042 sent",
"context": { "invoice": 1042 },
"channel": "app",
"environment": "production",
"release": "9c1e4b2",
"host": "web-1",
"request_id": "01JBFX3W2KQ7S3VZ0H5M9N2P4R",
"transaction": "POST /invoices/{invoice}/send"
}
]
}curl -X POST https://services.my-beast.com/logs/v1/logs \
-H "Authorization: Bearer $MYBEAST_LOGS_SECRET" \
-H "Content-Type: application/json" \
-d @batch.jsongzip -c batch.json > batch.json.gz
curl -X POST https://services.my-beast.com/logs/v1/logs \
-H "Authorization: Bearer $MYBEAST_LOGS_SECRET" \
-H "Content-Type: application/json" \
-H "Content-Encoding: gzip" \
--data-binary @batch.json.gz{
"accepted": 1,
"duplicates": 0,
"refused": []
}Scrub personal data before you send it: Logs scrubs every line again on arrival, but only with its own patterns. See Data and privacy.
A line
Each line in lines is an object with these fields:
| Field | Type | What it is |
|---|---|---|
idrequired | string | 16 to 40 letters, digits and dashes; the SDK mints a ULID. A line whose id the project has already isn't stored again, so sending a batch twice is harmless. |
atrequired | string | When the app wrote it: ISO 8601 with an offset, such as 2026-10-27T12:34:56.123456+00:00. More than 5 minutes ahead of the service's clock, it is placed at now. Older than its environment keeps lines, it is refused. |
levelrequired | string | One of debug, info, notice, warning, error, critical, alert, emergency. |
messagerequired | string | The line's text. Scrubbed again on arrival; over 32 KiB it is cut, not refused. |
environmentrequired | string | production, staging, …: lower-case letters, digits, - and _, up to 32. Lines are kept per environment. |
channeloptional | string | Letters, digits and ._:/-, up to 64. Left out, it is app. |
contextoptional | object or null | What the app passed along with the line. Every string is scrubbed and a value under a secret-looking key is removed. Over 64 KiB as JSON, it is replaced by a note. |
extraoptional | object or null | What the app's log processors added. Treated as context is. |
releaseoptional | string or null | The build that wrote it, such as a git commit. Up to 80 characters. |
hostoptional | string or null | The machine that wrote it. Up to 120 characters. |
request_idoptional | string or null | The same for every line of one request and the jobs it queued: letters, digits and ._:-, up to 64. One that doesn't fit is left off; the line still goes in. |
transactionoptional | string or null | What was running, without values: GET /invoices/{invoice}, job App\Jobs\SendInvoice, artisan invoices:remind. Up to 200 characters, scrubbed. |
Each line stands on its own: one that doesn't fit is refused and named, and the rest go in. What is only too long is cut, never refused.
The answer
A batch that is read answers 202 Accepted with three things:
accepted: how many lines were stored.duplicates: how many the project had already, by theirid, from a batch sent before. They aren't stored or counted again.refused: the lines that weren't taken, each with itsidand areason.
{
"accepted": 1,
"duplicates": 0,
"refused": [
{ "id": "01JBFX3W2N5S9Z7V1R0P8M6J4E", "reason": "at: older than the 14 days this environment keeps lines" },
{ "id": "01JBFX3W2P6T0A8W2S1Q9N7K5F", "reason": "monthly-limit" }
]
}A reason is a sentence that names the field at fault, for you to read, except monthly-limit: the project has used up its lines for this month, and the rest of the month's are refused until the 1st, UTC. See Lines a month. A refused line will be refused again, so don't send it twice.
Errors
When the whole batch isn't taken, the status says why and nothing of it is stored:
| Status | What it means | What to do |
|---|---|---|
| 202 | Taken: accepted lines stored, duplicates stored before, refused lines named with a reason. | Done. Don't send refused lines again: they'd be refused again. |
| 401 | No server secret, or one Logs doesn't know: revoked, mistyped, or another product's. | Keep the lines and fix the secret. |
| 402 | The project's subscription has ended: nothing new is taken in. | Keep the lines; renew the subscription at id. |
| 413 | More than 1,000 lines, or more than 4 MiB as sent or 32 MiB unpacked. A body marked gzip that doesn't start as gzip is answered 413 too. | Send the same lines in smaller batches. |
| 422 | The body isn't a batch: gzip that can't be read, not JSON, or not {"lines": [...]}. | Fix the body. The same body will fail again. |
| 429 | More than 1,200 uploads a minute with one key. | Wait as long as Retry-After says, then send the same lines. |
| 5xx | Something went wrong on our side, or the service is away. | Send the same lines again later. |
A 401, 402, 413 or 422 comes with JSON that has a message. A 402 adds a type and a link to the page where the subscription is renewed:
{
"message": "unauthenticated"
}{
"message": "Shop's subscription ended on 1.12.2026: nothing new is taken in or added. Renew it at id.",
"type": "subscription_ended",
"link": "https://id.my-beast.com/acme/-/subscription"
}Retrying
429,5xxand network errors: keep the lines and send the same batch again later, waiting longer after each failure. Their ids make it safe: a line stored the first time comes back as a duplicate.401and402: keep the lines, fix the secret or renew the subscription, then send them.413: send the same lines in smaller batches.422: the same body will fail again. Set it aside, so it doesn't hold up the lines after it, and fix what makes it.
The Laravel SDK does all of this for you. How lines are sent says how.