Skip to content

REST API

Read projects, tracked keywords, groups, site totals, breakdowns, pages, queries, alerts and notes as JSON with a workspace API key.

The REST API returns the data you see in Serplyze as JSON, so you can feed a dashboard, a spreadsheet script or your own tools. It is read-only: every endpoint is a GET, and a key cannot change anything in the workspace.

For Google Sheets without any code, use live CSV links instead.

Plan

The API needs a paid plan. A key that belongs to a workspace on Free answers every request with 403 plan_required until the workspace is upgraded again. See Plans and billing.

Base URL

https://serplyze.com/api/v1

All paths below are relative to this base. Responses are JSON and are sent with Cache-Control: no-store.

Authentication

Send the key in the Authorization header as a bearer token. There is no other way to pass it: query-string keys are not read.

curl https://serplyze.com/api/v1/projects \
  -H "Authorization: Bearer ord_live_your_key"
  • A key starts with ord_live_ followed by 32 letters and digits.
  • A key belongs to one workspace and reads every project in it, whoever created the key.
  • A missing key, a malformed key or a revoked key gets 401 unauthorized with a WWW-Authenticate: Bearer header.

Create and revoke keys

  1. Open Settings → API and Sheets.
  2. Select Create API key, give it a name that says what it is for, and select Create key.
  3. Copy the key from the Copy your API key dialog. This is the only time it is shown: Serplyze stores a hash of the key and its first characters, not the key itself.
  • Only workspace owners and admins can create or revoke keys. Other members see the list without the controls.
  • The list shows each key's name, its first characters, when it was created and when it was last used.
  • To revoke, select Revoke next to the key and confirm with Revoke key. The key stops working at once and cannot be restored.
  • A workspace can have up to 20 active keys.

Rate limits

Each key may make 120 requests a minute. Every authenticated response carries three headers:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute (120).
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetSeconds until the current minute window ends.

Over the limit, the API answers 429 with code rate_limited and a Retry-After header holding the number of seconds to wait.

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit of 120 requests a minute reached."
  }
}
Limit

Requests without a valid key are limited separately: after 30 failed requests in a minute from one client address, further failed requests get 429 instead of 401.

Errors

Every error has the same shape: an error object with a machine-readable code and a message you can show to a person.

{
  "error": {
    "code": "invalid_request",
    "message": "`period` must be days (e.g. 28d, up to 486d) or months (e.g. 3m, up to 16m)."
  }
}
StatusCodeWhen
400invalid_requestA parameter is missing, malformed or out of range. The message names the parameter.
401unauthorizedNo key, or the key is not valid or was revoked.
403plan_requiredThe workspace is on Free.
404not_foundUnknown project or group id, or a project that has not finished setup.
409google_access_lostPages and queries only: Google access to the property was revoked. Connect Search Console again in the app.
429rate_limitedToo many requests. Wait for Retry-After seconds.
500server_errorSomething failed on our side. Try again later.
503unavailablePages and queries only: Search Console could not be reached. Sent with Retry-After: 60.

Ids

Project, group, page group and keyword ids are the short ids you see in app URLs: five letters and digits. Alert and note ids are UUIDs. Get project ids from GET /projects.

Date ranges

The keywords, site, breakdowns, pages and queries endpoints take the same range parameters. Ranges are inclusive.

ParameterTypeDefaultNotes
todate YYYY-MM-DDThe project's last synced day (syncedThrough), or yesterday (UTC) before the first syncLast day of the range.
fromdate YYYY-MM-DD27 days before to (a 28-day range)First day of the range. Must not be after to.
period1d to 486d, or 1m to 16mnoneA relative range that ends at to, in days or calendar months. Use it instead of from; sending both is a 400.
  • A range may span at most 486 days (about 16 months, which is what Search Console keeps).
  • Keyword data is limited to the keyword history of your plan. If from is earlier, it is moved to the first available day and the response says "clamped": true. If the whole range is earlier, you get a 400.
  • Site totals, breakdowns, pages and queries are not limited by plan history.
  • provisionalAfter in a response is the last final day. Later days are still being revised by Google and can change.

Pagination

List endpoints take limit and offset and return a paging object with total, limit and offset. Ask for the next page by adding limit to offset until offset reaches total.

EndpointDefault `limit`Maximum `limit``offset`
/projects/:id/keywords10005000yes
/projects/:id/pages, /projects/:id/queries100025000yes
/projects/:id/alerts100500no

The other endpoints return everything in one response.

Metrics

  • clicks and impressions are whole numbers.
  • ctr is a ratio from 0 to 1 with four decimals (0.0412 is 4.12%). It is null when there are no impressions.
  • position is the average position weighted by impressions, with one decimal. It is null when there are no impressions.

GET /projects

The projects of the workspace, oldest first. Projects that have not finished setup are not listed. No parameters.

{
  "projects": [
    {
      "id": "Ab12C",
      "domain": "example.com",
      "property": "sc-domain:example.com",
      "country": "deu",
      "language": "de",
      "syncStatus": "ok",
      "lastSyncedAt": "2026-03-14T05:12:44.000Z",
      "syncedThrough": "2026-03-12",
      "keywordHistoryFrom": "2025-09-13",
      "historyBackfilling": false,
      "trackedKeywords": 412
    }
  ]
}
  • country is the project's country filter as an ISO 3166-1 alpha-3 code in lower case, or null for all countries. language is a two-letter code or null.
  • syncStatus is one of pending, ok, warning, failed.
  • keywordHistoryFrom is the first day of keyword data this workspace's plan can read. historyBackfilling is true while older days inside that window are still loading.
  • trackedKeywords counts keywords that are not archived.

GET /projects/:id/keywords

Tracked keywords with their totals over a date range, sorted by query text.

ParameterTypeDefaultAllowed values
from, to, periodsee Date rangeslast 28 daysClamped to the plan's keyword history.
devicestringallall, desktop, mobile, tablet
dailyflagoff1 or true adds a daily array to every keyword.
groupgroup idnoneOnly keywords of this group: the members of a manual group, everything below a folder, or the matches of a rule group evaluated on the requested range. Unknown id: 404.
limitinteger10001 to 5000
offsetinteger00 or more
curl "https://serplyze.com/api/v1/projects/Ab12C/keywords?period=7d&device=mobile&daily=1&limit=1" \
  -H "Authorization: Bearer ord_live_your_key"
{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "range": {
    "from": "2026-03-06",
    "to": "2026-03-12",
    "device": "mobile",
    "clamped": false,
    "provisionalAfter": "2026-03-10"
  },
  "paging": { "total": 412, "limit": 1, "offset": 0 },
  "keywords": [
    {
      "id": "k7Qp2",
      "query": "running shoes",
      "source": "manual",
      "trackedSince": "2025-11-02T09:30:00.000Z",
      "intent": "commercial",
      "groupIds": ["Gr4Zx"],
      "metrics": { "clicks": 84, "impressions": 2310, "ctr": 0.0364, "position": 6.2 },
      "daily": [
        { "date": "2026-03-06", "clicks": 11, "impressions": 320, "ctr": 0.0344, "position": 6.4 },
        { "date": "2026-03-07", "clicks": 0, "impressions": 0, "ctr": null, "position": null }
      ]
    }
  ]
}
  • source says how the keyword was added: suggested, manual or import.
  • intent is an estimate from the query text: informational, commercial, transactional, navigational or unclassified.
  • groupIds lists the manual groups the keyword is a member of. Rule groups are not listed here; use the group parameter to resolve them.
  • A keyword with no data in the range has zero clicks and impressions and null for ctr and position.
  • daily has one entry per day the keyword has stored data for, in date order. Days with no impressions are included as zeros.

GET /projects/:id/groups

The keyword groups and folders of a project, in the order of the rank tracker. No parameters.

{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "groups": [
    { "id": "Fo1dR", "name": "Shoes", "kind": "folder", "parentId": null, "position": 0, "pinned": false, "rule": null, "keywordIds": null },
    { "id": "Gr4Zx", "name": "Running", "kind": "manual", "parentId": "Fo1dR", "position": 1, "pinned": true, "rule": null, "keywordIds": ["k7Qp2", "m3Lw9"] },
    {
      "id": "Ru1e5",
      "name": "Page one, contains trail",
      "kind": "rule",
      "parentId": "Fo1dR",
      "position": 2,
      "pinned": false,
      "rule": {
        "match": "all",
        "conditions": [
          { "field": "query", "op": "contains", "value": "trail" },
          { "field": "position", "op": "lte", "value": 10 }
        ]
      },
      "keywordIds": null
    }
  ]
}
  • kind is folder, manual or rule. parentId is the id of the folder the group sits in, or null.
  • keywordIds is filled for manual groups only (archived keywords are left out). For a rule group, call the keywords endpoint with group set to its id.
  • rule is the rule definition of a rule group and null otherwise.

GET /projects/:id/page-groups

The page groups (content groups) of a project. No parameters. The response holds the definitions, not the member pages: match them against the pages list yourself.

{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "pageGroups": [
    {
      "id": "Pg7Ty",
      "name": "Blog",
      "position": 0,
      "rule": { "match": "any", "conditions": [{ "op": "starts_with", "value": "/blog/" }] },
      "pinnedUrls": ["/guides/size-chart"]
    }
  ]
}

GET /projects/:id/site

Clicks, impressions, CTR and average position of the whole property per day, plus the totals of the range. This covers every query, including the ones Search Console anonymises, and is not limited by plan history.

ParameterTypeDefaultAllowed values
from, to, periodsee Date rangeslast 28 days
devicestringallall, desktop, mobile, tablet
countrystringnoneThree-letter country code (ISO 3166-1 alpha-3, for example deu). Cannot be combined with a device other than all.
{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "range": { "from": "2026-03-11", "to": "2026-03-12", "device": "all", "country": null, "provisionalAfter": "2026-03-10" },
  "totals": { "clicks": 1930, "impressions": 61200, "ctr": 0.0315, "position": 14.8 },
  "daily": [
    { "date": "2026-03-11", "clicks": 980, "impressions": 30900, "ctr": 0.0317, "position": 14.6 },
    { "date": "2026-03-12", "clicks": 950, "impressions": 30300, "ctr": 0.0314, "position": 15.0 }
  ]
}
  • daily stops at the last synced day. A stored day without data is returned as zeros; days that are not synced yet are left out.
  • With country, range also has countryMonthlyThrough: the last day that is stored as monthly totals (or null). Days up to it are the month's total spread evenly over its days, and only the project's top 20 countries have data there.

GET /projects/:id/breakdowns

Totals over a range split by country, device and search appearance, and the brand / non-brand split. Parameters: from, to, period (see Date ranges).

{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "range": {
    "from": "2026-02-13",
    "to": "2026-03-12",
    "availableFrom": "2024-11-20",
    "availableThrough": "2026-03-12",
    "provisionalAfter": "2026-03-10",
    "countryMonthlyThrough": "2025-12-31"
  },
  "countries": [
    { "country": "deu", "clicks": 18400, "impressions": 512000, "ctr": 0.0359, "position": 12.1 },
    { "country": "aut", "clicks": 2100, "impressions": 70300, "ctr": 0.0299, "position": 13.4 }
  ],
  "devices": [
    { "device": "mobile", "clicks": 14900, "impressions": 431000, "ctr": 0.0346, "position": 11.8 },
    { "device": "desktop", "clicks": 6800, "impressions": 189000, "ctr": 0.036, "position": 13.9 }
  ],
  "appearances": [
    { "appearance": "PRODUCT_SNIPPETS", "clicks": 3100, "impressions": 98000, "ctr": 0.0316, "position": 9.7 }
  ],
  "brand": {
    "brand": { "clicks": 5200, "impressions": 21000, "ctr": 0.2476, "position": 1.4 },
    "nonBrand": { "clicks": 12800, "impressions": 455000, "ctr": 0.0281, "position": 13.2 },
    "anonymous": { "clicks": 4100, "impressions": 151000 }
  }
}
  • availableFrom and availableThrough are the days the breakdowns cover for this project. Both are null before the first collection.
  • Countries and devices are sorted by clicks, appearances by impressions. Rows without clicks and impressions are left out.
  • Search appearances overlap, because one result can carry several. They do not add up to the site total.
  • brand uses the project's brand terms. anonymous is the site total minus brand and non-brand: the queries Search Console hides.
  • Countries up to countryMonthlyThrough are monthly totals: the top 20 countries plus a row with country set to other for the rest. A range that covers part of such a month gets that month spread evenly over its days.

GET /projects/:id/pages

The pages of the property with their totals over a range, most clicks first. This is the same list as the Pages tab of the Traffic screen.

ParameterTypeDefaultAllowed values
from, to, periodsee Date rangeslast 28 daysto must be before today (UTC).
countrystringnoneThree-letter country code, for example deu.
limitinteger10001 to 25000
offsetinteger00 or more
{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "range": { "from": "2026-02-13", "to": "2026-03-12", "country": null },
  "source": { "kind": "gsc", "fetchedAt": "2026-03-14T06:02:11.000Z", "truncated": false, "capped": false },
  "paging": { "total": 3184, "limit": 2, "offset": 0 },
  "pages": [
    { "url": "https://example.com/running-shoes", "clicks": 1420, "impressions": 38900, "ctr": 0.0365, "position": 7.3 },
    { "url": "https://example.com/blog/trail-guide", "clicks": 960, "impressions": 41200, "ctr": 0.0233, "position": 11.9 }
  ]
}
  • source.kind is gsc (Search Console API) or bigquery when the project has a working BigQuery connection. fetchedAt is when the list was fetched from Google.
  • source.truncated is true when Google had more rows than the list holds.
  • Lists are fetched from Google on demand and then cached, shared with the app. The first request for a range can take up to a minute on a large site; repeat requests are fast.
Limit

From the Search Console API a list holds up to 25,000 pages or 50,000 queries per range. With a working BigQuery connection it holds up to 100,000 rows.

GET /projects/:id/queries

Every query of the property with its totals over a range, tracked or not, most clicks first. Parameters, source, paging and limits are the same as for pages.

{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "range": { "from": "2026-02-13", "to": "2026-03-12", "country": "deu" },
  "source": { "kind": "gsc", "fetchedAt": "2026-03-14T06:02:11.000Z", "truncated": false, "capped": false },
  "paging": { "total": 18250, "limit": 2, "offset": 0 },
  "queries": [
    { "query": "example shoes", "clicks": 2210, "impressions": 6400, "ctr": 0.3453, "position": 1.2, "branded": true, "trackedKeywordId": null },
    { "query": "running shoes", "clicks": 340, "impressions": 9800, "ctr": 0.0347, "position": 6.1, "branded": false, "trackedKeywordId": "k7Qp2" }
  ]
}
  • branded is true when the query matches the project's brand terms.
  • trackedKeywordId is the id of the tracked keyword for this query, or null when you do not track it.

GET /projects/:id/alerts

The alerts of a project, newest first: the built-in checks and your own alert rules, with the same title and text as the Alerts feed.

ParameterTypeDefaultAllowed values
statusstringallopen, resolved, all
sincedate YYYY-MM-DDnoneOnly alerts created on or after this day (UTC).
limitinteger1001 to 500
{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "alerts": [
    {
      "id": "7b0c2f8e-4a1d-4c3b-9e52-1f6a8d3b5c70",
      "type": "keyword_drop",
      "kind": "Rank drop",
      "severity": "warning",
      "title": "“running shoes” dropped from #4.2 to #9.8",
      "body": "Average position, 7 days to Mar 10, compared with the 7 days before (−5.6 positions).",
      "date": "2026-03-10",
      "keywordId": "k7Qp2",
      "url": "https://serplyze.com/Ab12C?kw=k7Qp2",
      "createdAt": "2026-03-13T05:14:02.000Z",
      "resolvedAt": null
    }
  ]
}
  • type is the machine name of the check, for example keyword_drop, keyword_lost, traffic_drop, rule, url_mismatch or url_changed. kind is its label in the app.
  • severity is info, warning or critical.
  • date is the day the alert is about, when the alert names one. keywordId is set for keyword alerts. url opens the alert's subject in the app.
  • resolvedAt is null while the alert is open.

GET /projects/:id/notes

The chart notes that apply to a project, oldest first: workspace notes, project notes and notes on a group, a keyword or a page.

ParameterTypeDefaultAllowed values
fromdate YYYY-MM-DDnoneKeep notes (and note ranges) that end on or after this day.
todate YYYY-MM-DDnoneKeep notes that start on or before this day. from must not be after to.
clientVisiblebooleanall notestrue returns only notes marked as visible to clients, as reports show them. false returns all notes.
{
  "project": { "id": "Ab12C", "domain": "example.com" },
  "notes": [
    {
      "id": "c1d4e9a2-6b7f-4e0a-8c15-2a9f3b7d6e41",
      "scope": "keyword",
      "date": "2026-02-20",
      "endDate": null,
      "category": "content",
      "text": "Rewrote the buying guide intro",
      "clientVisible": true,
      "author": "Sam Example",
      "group": null,
      "keyword": { "id": "k7Qp2", "query": "running shoes" },
      "page": null,
      "createdAt": "2026-02-20T10:02:31.000Z",
      "updatedAt": "2026-02-20T10:02:31.000Z"
    }
  ]
}
  • scope is workspace, project, group, keyword or page. group and keyword are objects with an id and a name or query; page is the page URL. The ones that do not apply are null.
  • endDate is the last day of a range note and null for a single day.
  • category is one of launch, content, technical, migration, campaign, other.

What the API does not do

Limit

The API is read-only and has no webhooks. It does not add keywords, edit groups, create notes or resolve alerts, and it has no endpoints for reports, backlinks, content findings, forecasts or AI search data. Use exports for those screens.