api.simkl.com, plus a 7-day rollup of daily request totals. It’s the fastest way to confirm what your code is actually sending — without setting up your own logger.
Opening it
Developer dashboard
Open simkl.com/settings/developer/ while logged in. The page lists every app you’ve registered.
Your app's row
Click the app to land on its page (URL:
simkl.com/settings/developer/{APP_ID}/). This is also where your Client ID and Client Secret live.Debug link
Click Debug (top-right of the app card). The analytics view opens in a new tab.
The page is opened via a short-lived signed link. The link expires after 1 hour — close and re-open the Debug link from your developer dashboard for a fresh session. The dashboard page is what authorizes you; the analytics URL itself isn’t bookmarkable.
What you can do here
- Confirm your code is actually hitting Simkl. Issue a request from your app, hit Refresh — if it doesn’t appear, your client never reached us (wrong host, blocked by a proxy, or missing the required headers so we can’t attribute it to your app).
- Verify your sync loop matches the two-phase pattern the Sync guide requires. Apps that don’t follow it download the whole user library every poll and get rate-limited — sometimes blocked. The Debug view makes both halves easy to audit:
- Filter on
Path equals /sync/activities— this is the cheap “did anything change?” check that has to fire first on every poll. If it isn’t there, your loop is broken. - Filter on
Path contains /sync/all-itemsand look at the Query column for every row. Each one should includedate_from=<your_last_saved_timestamp>so Simkl returns only the delta. A/sync/all-itemsrow with nodate_frommeans your client is asking for the whole library — fix it before it gets throttled.
- Filter on
- See exactly what your OAuth library / SDK puts on the wire. Wrappers and middleware obscure this; the Debug view shows the literal
client_id,app-name,app-version, andUser-Agentyour library sent — useful for diagnosingempty_fieldandgrant_errorresponses. - See the HTTP status Simkl returned, even when your client only shows a generic error. Some HTTP libraries surface “Network error” or “Bad Request” without the actual status code. The Edge and Origin columns show
200/401/403/412etc. for every request — pair the code with /conventions/errors to map it back to the meaning. Response bodies aren’t recorded in the Debug view, but forGETrequests the Link column re-fires the same URL in a new browser tab so you can read the live response body.POST/PUT/DELETEbodies still have to come from your own logging. - Spot retry loops, runaway timers, and duplicate calls. The red Burst column and High Traffic Burst Detected banner surface “N requests in 1 second from the same IP” patterns automatically — find which path your code is over-firing before the auto-blocker does.
- Tell edge cache hits from real backend responses. When
Edge=200andOriginis blank, the response came from the edge cache without touching the backend — useful for understanding why a stale value sometimes persists after a write. - Compare staging vs prod, or app A vs app B. Filter on
User agentorClient IPto slice traffic by deployment / machine and confirm each environment is sending what you think it is. - Export a CSV of the current filtered view to drop into a spreadsheet, share with support, or attach to a bug report.
Controls
What each column shows
Burst detector
When 3+ requests from the same IP land in the same second, the page surfaces a red High Traffic Burst Detected banner at the top of the table plus aX% BURST badge near the event count. The Burst column highlights the specific rows that fired it.
The three usual culprits:
- Retry loops without backoff — fix with exponential backoff + jitter.
- Parallel requests — fire calls sequentially, awaiting each response before issuing the next. Don’t
Promise.allthe same endpoint, don’t let a new poll tick start before the previous one returned. - UI effects re-firing every render — gate your
useEffect/ equivalent on a stable dependency.
client_id throttled or temporarily blocked. Fix the loop before the auto-blocker fires — recovery isn’t automatic.
Filters
The Filter results… box does a free-text match across all visible columns. + Add filter opens a composer. Pick a column, an operator (equals, not equals, contains, not contains, starts with, ends with, in, not in), and a value. The value field autocompletes from what’s actually in the data. Stack multiple filters — they AND together.
A few recipes:
- Just failures →
Origin contains 4(catches 4xx) - One endpoint →
Path equals /sync/history - One country →
Country equals US - One machine →
Client IP equals e-JgphVReMb9(click the IP in any row to filter to its sibling requests)
Headers required to show up
See also
Get an API key
Developer dashboard — register a new app or jump to your existing apps’ Debug views.
Required headers
The
client_id / app-name / app-version / User-Agent reference.Rate limits
Per-app daily quotas, per-IP throttling, the 20-second write lock.
Errors
What each error envelope means when the Debug view shows a 4xx / 5xx row.