An app can report errors and crashes on its own with one request, so you hear about a problem before anyone files a report. The web widget, the Swift package and the Godot addon use this endpoint; anything else that can send HTTPS can use it directly.
Request
curl -X POST https://snagfrog.com/api/v1/errors \
-H "Authorization: Bearer snag_pk_…" \
-H "Content-Type: application/json" \
-d '{
"message": "Cannot read properties of undefined (reading \"id\")",
"stack": "TypeError: …\n at save (notes.js:42:11)",
"kind": "error",
"version": "1.4.2",
"os": "macOS 15.1",
"context": { "screen": "editor" }
}' | Field | Type | What it is |
|---|---|---|
message | string, required | The error message. Identical errors are grouped by kind, message and place in the code. |
stack | string | The stack trace, as text. |
kind | "error" | "crash" | Defaults to "error". |
version | string | Your app's version, for example "1.4.2". The group shows which versions it appeared in. |
url | string | Where it happened, for web pages. |
os | string | The operating system. |
context | object | Key and value pairs of your own: up to 12 keys, 200 characters each. |
app | string | The app's slug, in place of the Authorization header. Used by web pages; see "Who may send". |
Response
{ "ok": true, "group": "…", "new": true } | Status | Meaning |
|---|---|
202 | Accepted. "ok" is false when the occurrence was dropped (allowance or flood limits); "new" is true for an error not seen before. |
400 | No message, the body is not JSON, or the message is only "Script error.", which names nothing. |
401 | No key or slug, an unknown app, or the app only accepts errors sent with its key. |
403 | The request came from a website that is not on the app's list of error sources. |
413 | The body is larger than 64,000 bytes. |
429 | Too many requests: more than 30 in 10 minutes from one address, or 1,500 an hour for one app. |
The endpoint answers requests from any origin (CORS), so a web page can call it directly.
What happens to an error
- It joins its group in the app's Errors tab, with a count, a 14-day history and the latest stack.
- You can resolve, ignore or reopen a group. A resolved error that happens again reopens itself.
- The workspace is emailed about new errors and ones that came back, as one digest an hour, never one email per error.
- Claude can work through them with the
list_errorsandupdate_errortools.
Limits
- Occurrences a month: 1,000 on the trial, 10,000 on Indie, 100,000 on Studio. Past that they are dropped until the month ends.
- One day may use at most a tenth of the month.
- An app gains at most 100 new distinct errors an hour.
The endpoint is public, and these limits keep a flood of junk from costing a workspace its month.
Who may send
By default, anyone who knows an app's slug can send errors for it, as with its public report page. An app's Settings → Error capture narrows that: accept only requests that carry the app key, and, for browsers, only the websites you list. Neither is a lock, since a key in a web page can be read by anyone; they stop the cheap case and the limits above bound the rest.
Questions
Is the app key a secret?
No. It starts with snag_pk_ and is publishable: it identifies the app when sending errors and when a native app starts a report. It cannot read anything. You can rotate it from the dashboard.
Are individual occurrences stored?
No. Identical errors (same kind, message and place in the code) form one group. SnagFrog keeps the group's counters, the versions it appeared in and its latest sample, and nothing else about the visitor.
What happens when the monthly allowance is used up?
Further occurrences are dropped until the month ends, and the response says so with "ok": false. The allowance is 1,000 occurrences a month on the trial, 10,000 on Indie and 100,000 on Studio.
Does SnagFrog symbolicate stack traces or read source maps?
No. The stack is stored and shown as it was sent. SnagFrog groups and counts errors; it is not a crash analytics product.
More reference pages
Start a free 30-day trial · All docs · Guides · Compare with other tools · Changelog