Skip to content

Docs / Reference

REST API

Authentication, the response envelope, every route, and how dry runs, refreshes and ceilings work.

The dashboard is built on this API, so anything you can do in the app you can script. The base URL is {API_URL}/api (http://localhost:4100/api on a default self-hosted install).

Authentication

Send an OpenSEO API key as Authorization: Bearer oseo_…. Create keys under Settings; a key is shown once and acts as you, so treat it like a password. Keys can be revoked at any time and cannot create further keys. The dashboard itself uses a session cookie and may only make changes from its own origin.

Responses

Every response is JSON: { "ok": true, "data": … } on success, or { "ok": false, "error": { "code", "message", "details?" } } with a 4xx or 5xx status. Codes are listed in Errors and troubleshooting. IDs are UUIDs; a malformed one is a 404.

Lookups

POST /projects/:projectId/lookups/:name runs one paid lookup with its input as the JSON body. Inputs are validated before anything is spent; omitted market fields default to the project's.

Lookup query parameters
ParameterEffect
?dryRun=1Return { estimate: { min, max } } and spend nothing. max is null for token-priced lookups.
?refresh=1Skip the cache and pay for fresh data.
?maxCostUsd=0.50Refuse with cost_ceiling_exceeded if the worst-case price is higher. Must be a positive number.
Terminal
# Price a lookup (free), then run it with a ceiling
curl -X POST "$API_URL/api/projects/$PROJECT/lookups/keyword_ideas?dryRun=1" \
  -H "Authorization: Bearer $OSEO_KEY" -H "Content-Type: application/json" \
  -d '{"keyword":"emergency plumber","limit":150}'

curl -X POST "$API_URL/api/projects/$PROJECT/lookups/keyword_ideas?maxCostUsd=0.10" \
  -H "Authorization: Bearer $OSEO_KEY" -H "Content-Type: application/json" \
  -d '{"keyword":"emergency plumber","limit":150}'
Reply
{
  "ok": true,
  "data": {
    "name": "keyword_ideas",
    "estimate": { "min": 0.03, "max": 0.08 },
    "data": { "seed": "emergency plumber", "keywords": [ ... ] },
    "costUsd": 0.08,
    "cached": false,
    "fetchedAt": "2026-10-15T09:30:00.000Z"
  }
}

cached: true results have costUsd: 0. If some parts of a multi-part lookup (the domain overview) fail, the parts that were paid for are returned with an errors list and are not cached. Names: keyword_ideas, keyword_metrics, serp, autocomplete, keyword_trends, domain_overview, domain_keywords, domain_pages, competitors_domain, competitors_serp, keyword_gap, backlinks_summary, backlinks_history, backlinks_list, backlinks_referring_domains, backlinks_anchors, backlinks_pages, link_intersect, onpage_check, lighthouse, maps_search, maps_place, maps_reviews, ai_compare. GET /lookups lists them with descriptions and cache times.

Account

Account routes
RouteNotes
GET /meUser and settings: whether a YepAPI key is set, its hint, the monthly limit.
PUT /settings/yepapi-key{ apiKey }. Verified without spending credits. Never returned.
DELETE /settings/yepapi-keyRemove the key.
PUT /settings/budget{ monthlyBudgetUsd: number | null }.
GET /balanceLive YepAPI balance. Free, and works when the monthly limit is reached.
GET /usageThis month's spend in total, by feature and by day, and the last 50 calls.
GET, POST /api-keys; DELETE /api-keys/:idList, create ({ name }; the key is in the reply once) and revoke. Creating needs a signed-in session.
GET /locations?q=&country=Location codes. Free.
GET /ai/modelsThe chat model catalog and the default set.

Projects

GET/POST /projects, GET/PATCH/DELETE /projects/:projectId. Fields: name, domain, locationCode, locationName, country, language, context (free-form notes for agents). When locationCode comes without country, the country (and a missing name) is looked up from the code; an unknown code is refused rather than guessed. Deleting a project deletes everything in it.

Saved keywords

Saved keyword routes
RouteNotes
GET /projects/:id/saved-keywords?q= &tag= &sort=keyword|volume|cpc|difficulty|createdAt &order= &limit= (max 500) &offset=. Returns { total, keywords, tags }.
POST /projects/:id/saved-keywords{ keywords: [{ keyword, volume?, cpc?, competition?, difficulty?, intent?, trend? }], tags?, locationCode?, language? }. Up to 1,000; saving again updates figures and merges tags.
PATCH /projects/:id/saved-keywords/tags{ ids, add?, remove? }.
POST /projects/:id/saved-keywords/delete{ ids }.
POST /projects/:id/saved-keywords/refreshPaid. Refreshes keywords in the project’s market and language. ?dryRun=1 returns { estimate: { keywords, otherMarkets, min, max } }.

Rank tracking

Rank tracking routes
RouteNotes
GET, POST /projects/:id/trackersCreate with { domain, locationCode or latitude + longitude, locationName, language?, devices?, depth? (10–100, by 10), schedule?, keywords? }.
GET, PATCH, DELETE /trackers/:trackerIdPATCH { name?, devices?, depth?, schedule? }. GET includes the price range of one check and the last 20 runs.
POST /trackers/:trackerId/keywords{ keywords }. Up to 1,000 per tracker.
POST /trackers/:trackerId/keywords/delete{ ids }. Removed keywords stop being checked; their history is kept, and adding one again restores it.
PATCH /trackers/:trackerId/keywords/:keywordId{ pinned?, tags? }.
POST /trackers/:trackerId/runsPaid. 202 with the queued run; 409 if one is already going.
GET /rank-runs/:runId; POST /rank-runs/:runId/cancelPoll progress and cost; cancel.
GET /trackers/:trackerId/results?device=Latest positions, changes, history and a summary.
GET /trackers/:trackerId/competitors?device=Domains most often in the top 10.
GET /trackers/:trackerId/keywords/:keywordId/history?device=Every check for one keyword and the competing results of the latest.

AI visibility

AI visibility routes
RouteNotes
GET /projects/:id/aiSettings, prompts, the active run and the estimate { answers, min, expected }.
PUT /projects/:id/ai/settings{ brand?, brandAliases?, domain?, competitors?, models?, webSearch?, schedule? }.
POST /projects/:id/ai/prompts; POST …/prompts/delete{ prompts, topic? } and { ids }. Up to 200 per project.
PATCH /projects/:id/ai/prompts/:promptId{ active?, topic? }.
POST /projects/:id/ai/suggest-promptsPaid (tokens). { description, count? }.
POST /projects/:id/ai/runsPaid. { maxCostUsd? }: refused if the likely cost is higher, and stopped early (status cancelled) before an answer that would likely cross it.
GET /projects/:id/ai/runs/:runId; POST …/cancelPoll and cancel.
GET /projects/:id/ai/resultsThe latest run in detail, the trend, and comparison: { status: comparable | incomplete | scope_changed | no_previous | no_data, mentionChange, citeChange, cells }. Prompts that name the brand and failed or empty answers are left out of rates.
GET /projects/:id/ai/prompts/:promptId/answersEvery stored answer for a prompt.

Site audit

Site audit routes
RouteNotes
GET, POST /projects/:id/auditsPOST { startUrl?, maxPages? (≤ 5,000), renderJs? (≤ 500 pages) }; ?dryRun=1 for the price. A plain crawl is free.
GET, DELETE /audits/:auditId; POST /audits/:auditId/cancelGET includes the summary and issue groups with explanations.
GET /audits/:auditId/issues?type= &severity= &limit= (≤ 1,000) &offset=.
GET /audits/:auditId/pages?q= &sort=issues|url|status|words|response|depth &limit= &offset=.
GET /audits/:auditId/pages/:pageIdEverything recorded for one page, and its issues.
GET /audit/issue-catalogEvery issue type with severity, explanation and fix.

Local SEO

Local SEO routes
RouteNotes
GET, POST /projects/:id/geo-gridsPOST { keyword, placeId, placeName, centerLat, centerLng, size (3|5|7|9), spacingM (100–20,000) }; ?dryRun=1 for the price ($0.07 a point).
GET, DELETE /geo-grids/:gridId; POST /geo-grids/:gridId/cancelGET includes every point. Points searched before a cancel stay billed.

Search Console and Analytics

Free: these read the user's own Google data and spend nothing from YepAPI. Connecting is done in the dashboard (GET /google/:provider/connect starts Google's consent screen and needs a signed-in session, not an API key). Search Console periods end three days ago; GA4 periods end yesterday.

Google routes
RouteNotes
GET /projects/:id/google{ configured, redirectUris, gsc, ga4, accounts }.
GET /google/accounts; DELETE /google/accounts/:accountIdYour Google grants. Deleting one disconnects every project using it and returns { projects }.
GET /projects/:id/google/gsc/sites?accountId=Search Console properties, with verified: false for ones you cannot read.
PUT /projects/:id/google/gsc{ accountId, siteUrl }. Unverified properties are refused.
GET /projects/:id/google/ga4/properties?accountId=GA4 properties in the account.
PUT /projects/:id/google/ga4{ accountId, propertyId: "properties/123" }.
DELETE /projects/:id/google/:providerDisconnect gsc or ga4 from the project.
GET /projects/:id/search-performance?range=last_7_days|last_28_days|last_3_months &device= &country= (alpha-3) &pageText= &pageMatch=contains|equals &queryText= &queryMatch=. Totals against the previous period, daily rows, striking distance, countries.
GET /projects/:id/search-performance/tableThe same filters plus ?dimension=query|page &page= &size=25|50|100. Returns { rows, hasMore }.
GET /projects/:id/search-console/rows?dimensions=query,page (up to 3) &minPosition= &maxPosition= &minImpressions= &limit= (≤ 1,000) and the filters above.
POST /projects/:id/search-console/inspect{ urls } (1 to 10). Index status per URL.
GET /projects/:id/analytics/overview?range= &channel=organic|all. Sessions, engagement, key events and revenue against the previous period, plus a daily series.
GET /projects/:id/analytics/landing-pages?range= &channel= &limit= (≤ 1,000).
GET /projects/:id/search-opportunities?range=. Pages at positions 4 to 20 with a 0 to 100 score and its parts.

Free tools (public)

GET /public/tools lists the tools and markets. POST /public/tools/:tool runs one, with no account, behind the guards described in Run the free tools. These routes are meant for the site, not for scripts: use an API key and the lookups above instead.