{"openapi":"3.1.0","info":{"title":"SolSEO customer API","version":"3.0.14","description":"The read API a SolSEO customer reaches with a key they minted themselves. Nothing on this surface writes. The prose version, including what every refusal means, is at /AGENTS.md."},"servers":[{"url":"https://solseo.com.au"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/my/sites":{"get":{"summary":"List your sites","description":"List the sites on your account with keyword and competitor counts, last check and last crawl.","operationId":"solseo_list_sites","security":[{"bearerAuth":[]}],"x-scope":"sites:read","parameters":[{"name":"status","in":"query","required":false,"description":"Which sites to include.","schema":{"type":"string","enum":["active","archived","all"],"default":"active"}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}":{"get":{"summary":"One site","description":"One site by id: settings, counts, last check and last crawl. Another account's id answers 404.","operationId":"solseo_get_site","security":[{"bearerAuth":[]}],"x-scope":"sites:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id, from the sites list.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}/keywords":{"get":{"summary":"A site's keywords","description":"Keywords on a site with the latest position, the change against yesterday and 30 days ago, tags and the working-on flag. Paged, 200 at most.","operationId":"solseo_list_keywords","security":[{"bearerAuth":[]}],"x-scope":"keywords:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"How many rows to return.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"How many rows to skip. Page three is offset 100 at limit 50.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"q","in":"query","required":false,"description":"Match the keyword text.","schema":{"type":"string"}},{"name":"tag","in":"query","required":false,"description":"One tag.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Narrow to paused, active, or the ones not found on the page.","schema":{"type":"string","enum":["active","paused","not_found"]}},{"name":"working_on","in":"query","required":false,"description":"Only the keywords marked as being worked on.","schema":{"type":"boolean"}},{"name":"sort","in":"query","required":false,"description":"Priority keywords sort first whatever this says.","schema":{"type":"string","enum":["position","change","best","keyword"],"default":"position"}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/keywords/{id}/history":{"get":{"summary":"One keyword, day by day","description":"Daily positions for one keyword, 30 days by default and 365 at most. A day with no check is absent from the array rather than null.","operationId":"solseo_keyword_history","security":[{"bearerAuth":[]}],"x-scope":"keywords:read","parameters":[{"name":"id","in":"path","required":true,"description":"The keyword id, from the keyword list.","schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"description":"How far back to read, counting today.","schema":{"type":"integer","minimum":1,"maximum":365,"default":30}}],"responses":{"200":{"description":"The answer. Held up to 60 seconds server side.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}/movers":{"get":{"summary":"Biggest risers and fallers","description":"The biggest risers and fallers on a site over 7 or 30 days.","operationId":"solseo_movers","security":[{"bearerAuth":[]}],"x-scope":"keywords:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id.","schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"description":"The window, 7 days or 30.","schema":{"type":"integer","enum":[7,30],"default":7}}],"responses":{"200":{"description":"The answer. Held up to 60 seconds server side.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}/audit":{"get":{"summary":"The health score","description":"The site's health score, the previous one, and when each audit ran.","operationId":"solseo_health","security":[{"bearerAuth":[]}],"x-scope":"audits:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}/findings":{"get":{"summary":"Open audit findings","description":"Open findings on a site from the latest audit: code, severity, page count, and the reason and fix text. Muted findings only when asked for.","operationId":"solseo_findings","security":[{"bearerAuth":[]}],"x-scope":"audits:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id.","schema":{"type":"integer"}},{"name":"severity","in":"query","required":false,"description":"One severity. design/07 named three values that have never been stored; these five are what is in the table.","schema":{"type":"string","enum":["critical","high","medium","low","info"]}},{"name":"include_muted","in":"query","required":false,"description":"Include findings somebody muted.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The answer. Held up to 60 seconds server side.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/sites/{id}/search-console":{"get":{"summary":"Search Console figures","description":"Clicks, impressions, CTR and average position over 7, 28 or 90 days against the period before it. 404 when the site has no property connected.","operationId":"solseo_search_console","security":[{"bearerAuth":[]}],"x-scope":"sites:read","parameters":[{"name":"id","in":"path","required":true,"description":"The site id.","schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"description":"The window.","schema":{"type":"integer","enum":[7,28,90],"default":28}}],"responses":{"200":{"description":"The answer. Held up to 60 seconds server side.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/alerts":{"get":{"summary":"The alert feed","description":"The last alert events on the account: rank drops, downtime, indexation loss, audit regressions and finished reports.","operationId":"solseo_alerts","security":[{"bearerAuth":[]}],"x-scope":"alerts:read","parameters":[{"name":"limit","in":"query","required":false,"description":"How many events.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"site_id","in":"query","required":false,"description":"Narrow to one site.","schema":{"type":"integer"}},{"name":"kind","in":"query","required":false,"description":"One alert kind.","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/reports":{"get":{"summary":"Reports and their share links","description":"Reports on the account or one site, with period, status, whether a share link exists and its hint. The address of a share link is never returned: only its digest is stored, so nothing can reconstruct it.","operationId":"solseo_reports","security":[{"bearerAuth":[]}],"x-scope":"reports:read","parameters":[{"name":"site_id","in":"query","required":false,"description":"Narrow to one site.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"How many reports.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/my/agents.md":{"get":{"summary":"How to drive this API, for your account","description":"The public AGENTS.md with your own account spliced in: your account id, your sites and their ids, the limits in force on your plan, and which scopes this key holds. Never your key.","operationId":"solseo_agents_md","security":[{"bearerAuth":[]}],"x-scope":"sites:read","parameters":[],"responses":{"200":{"description":"The answer.","headers":{"ETag":{"description":"A weak validator over the body. Send it back as If-None-Match.","schema":{"type":"string"}},"Cache-Control":{"description":"Always private, max-age=0, must-revalidate on this surface.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Your If-None-Match matched. No body. Counts against the rate limit."},"401":{"description":"unauthorized: no bearer, or one this hub does not know.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"scope_required or account_required. Always about the credential, never about the row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: not yours, or not there. It does not mean deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed: a parameter was outside its range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited. Carries Retry-After and the three X-RateLimit headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A key minted at /app/settings/api. Scopes have no hierarchy and no implication."}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"The code to branch on. Stable."},"message":{"type":"string","description":"A sentence for a person. The wording changes."}}}}}}