{"openapi":"3.1.0","info":{"title":"PortyLink API","version":"1.0.0","description":"Read Google Analytics 4 and Search Console data for the sites in your PortyLink organization. Create an API key in the dashboard and send it as `Authorization: Bearer <key>`. All endpoints are read-only."},"servers":[{"url":"/api/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key such as plk_live_..."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid","unauthenticated","forbidden","not_found","conflict","rate_limited","unavailable","internal"]},"message":{"type":"string"},"retryAfter":{"type":"integer","description":"Seconds to wait before retrying (also sent as Retry-After)."}}}}}}},"paths":{"/sites":{"get":{"operationId":"listSites","summary":"Sites this key can read","responses":{"200":{"description":"Sites with the providers each has linked.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"domain":{"type":"string"},"providers":{"type":"array","items":{"type":"string","enum":["ga4","gsc"]}}}}}}}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports":{"get":{"operationId":"listReports","summary":"Available reports","responses":{"200":{"description":"The report catalog."},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sites/{siteId}/reports/{report}":{"get":{"operationId":"runReport","summary":"Run a report for a site","description":"- `gsc.overview`: Clicks, impressions, CTR and average position from Google Search, per day, with totals.\n- `gsc.queries`: The searches the site appeared for, by clicks.\n- `gsc.pages`: The site's pages that appeared in Google Search, by clicks.\n- `gsc.countries`: Clicks and impressions by searcher country (ISO 3166-1 alpha-3).\n- `gsc.devices`: Clicks and impressions on desktop, mobile and tablet.\n- `ga4.overview`: Active users, sessions, engaged sessions, engagement rate, views and key events, per day, with totals.\n- `ga4.channels`: Sessions and users by default channel group (Organic Search, Direct, Paid Search, ...).\n- `ga4.sources`: Sessions and users by source and medium, e.g. google / organic.\n- `ga4.landing_pages`: Pages where sessions started, by sessions.\n- `ga4.pages`: Pages by views, with users and total engagement time (seconds).","parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"report","in":"path","required":true,"schema":{"type":"string","enum":["gsc.overview","gsc.queries","gsc.pages","gsc.countries","gsc.devices","ga4.overview","ga4.channels","ga4.sources","ga4.landing_pages","ga4.pages"]}},{"name":"range","in":"query","description":"A preset ending yesterday in the source's time zone. Ignored when start and end are given.","schema":{"type":"string","enum":["last_7_days","last_28_days","last_90_days","last_365_days"],"default":"last_28_days"}},{"name":"start","in":"query","description":"YYYY-MM-DD, with end.","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","description":"YYYY-MM-DD, with start.","schema":{"type":"string","format":"date"}},{"name":"limit","in":"query","description":"Rows per page; each report has its own maximum.","schema":{"type":"integer","minimum":1}},{"name":"offset","in":"query","description":"Use meta.paging.nextOffset from the previous page.","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Rows as objects keyed by column, totals for by-day reports, and metadata on the date range, freshness and paging."},"400":{"description":"The request is invalid, e.g. a bad date range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key may not read this provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown site, or the site has no source linked for this report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by PortyLink or by Google's quota. Retry after `retryAfter` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Google couldn't be reached or the connected account lost access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}