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
The view your users get
The Analytics view is scoped to your app. Your users have the mirror image of it at Connected Apps, which shows them every integration they have connected — yours among them.
That matters for AUTH V2, where the daily allowance belongs to the user and is shared across every app they run. If a user reports that your app stopped syncing and your own numbers look normal, the cause is often another integration spending their allowance — which you cannot see and they can.
Ask them to open that page and sort by today’s requests. It also names the apps responsible, so the conversation moves straight to the right place.
See also
Get an API key
Developer dashboard — register a new app or jump to your existing apps’ Debug views.
Connected Apps
What your users see: their daily allowance, and every app spending it.
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.