API
A read-only HTTP API over your own releases and their figures. It is on every plan, including Free, and it is versioned — anything that would break a working client goes in a new version rather than into this one.
Last updated
Getting started
Everything lives under https://earlygrooves.com/api/v1. Create a token in Settings → API, then send it as a bearer token:
Authorization: Bearer eg_live_…
- A token belongs to exactly one account and grants read only. There is no write scope.
- It is shown once, when you create it, and stored hashed. If you lose it, revoke it and make another.
- Revoking takes effect immediately.
Responses are JSON. A successful one wraps its payload in data.
Endpoints
| Endpoint | What it answers |
|---|---|
GET /releases | Your releases, newest first, with their streaming links and public URL. Takes status (published, draft or all), limit (1–100, default 25) and cursor. |
GET /releases/{id} | One release, in the same shape as a row of the list. |
GET /releases/{id}/stats | Views, unique visitors, clicks, click-through rate, clicks by platform and a daily series. Takes from and to as ISO dates — the last 30 days by default, up to 365 days apart. |
GET /me | Your handle, display name, plan and portfolio URL. |
Pagination is by cursor, not by page number
A list response carries nextCursor when there is more. Pass it back as cursor. Offsets are not offered on purpose: adding a release half-way through paging would shift every later page, and you would silently skip a row or read one twice.
Statistics are aggregates, and only aggregates
There is no per-visit row, no visitor identifier and no IP address anywhere in this API, because none of them exists to return — see our privacy notice. Figures exclude your own visits to your own pages, so they match your dashboard exactly.
Rate limits
| Plan | Limit |
|---|---|
| Free | 60 requests a minute, 5,000 a day |
| Premium | 300 requests a minute, 50,000 a day |
Both windows apply, and limits are counted per account rather than per address — three servers behind one connection share one budget, which is what the number on the plan comparison means.
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, all describing whichever window is the tighter one at that moment. Going over gets a 429 with Retry-After.
Errors
Failures come back in one shape:
{ "error": { "code": "not_found", "message": "…", "details": { } } }
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | A parameter is malformed. |
| 401 | unauthorized | No token, or one we do not recognise. |
| 403 | forbidden | A valid token, asking for something that is not yours. |
| 404 | not_found | No such resource. |
| 422 | unprocessable | Well-formed, but we cannot act on it. |
| 429 | rate_limited | Over a limit. Wait for the seconds in Retry-After. |
| 500 | internal | Ours. Please tell us. |
A release belonging to somebody else and a release that does not exist both answer the same way. That is deliberate: a distinguishable answer would let anybody with a token confirm which identifiers are real.
What v1 deliberately does not have
- Subscribers. Fan email addresses are not reachable with a bearer token, at all. A token that leaks must not also leak a mailing list. You can export your list from the subscribers page, which is behind a real sign-in.
- Anything that writes. Read-only is what every plan promises, and it is the half that cannot go wrong. Adding writes later would be a new scope rather than a change to this one.
- Newsletters, campaigns, press releases and social posts. Not part of the free API promise. Adding them later is additive and breaks nothing.
Stability
/api/v1 is stable. Adding a field to a response, adding an endpoint or adding an optional parameter can happen at any time, so parse leniently and ignore what you do not recognise. Anything that would break a working client — removing a field, changing a type, changing what a parameter means — goes into /api/v2, and we will tell account holders before it does.
Using it is subject to our terms.