{"openapi":"3.1.0","info":{"title":"Topics.so API","version":"1.0.0","description":"Read your posts and their metrics from Topics.so. Authenticate with a brand API key created in your dashboard.","contact":{"name":"Topics.so support","email":"support@topics.so","url":"https://topics.so/docs/api"}},"servers":[{"url":"https://topics.so/api"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/posts":{"get":{"operationId":"list-posts","summary":"List posts","description":"Every post across your connected accounts, newest first by default. Filter, search, sort, and page through them the same way the dashboard does. Each post carries its latest metrics.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"description":"Page number, starting at 1.","schema":{"type":"integer","default":1,"minimum":1}},{"name":"perPage","in":"query","required":false,"description":"Posts per page.","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"q","in":"query","required":false,"description":"Free-text search across title, caption, and cover text.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Only posts published on or after this date (yyyy-mm-dd).","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"Only posts published on or before this date (yyyy-mm-dd).","schema":{"type":"string"}},{"name":"platform","in":"query","required":false,"description":"Restrict to one platform.","schema":{"type":"string","enum":["instagram","linkedin","youtube"]}},{"name":"format","in":"query","required":false,"description":"Restrict to one content format.","schema":{"type":"string","enum":["carousel","image","reel","story","video"]}},{"name":"hashtag","in":"query","required":false,"description":"Restrict to posts carrying this hashtag, without the leading #.","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"Field to sort on.","schema":{"type":"string","enum":["published","views","impressions","clicks","comments","engagementRate"],"default":"published"}},{"name":"dir","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"List posts","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in one second.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current second.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time, in seconds, when the current window resets.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Requests allowed this calendar month.","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Requests left this calendar month.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostList"}}}},"400":{"description":"A query parameter is missing, malformed, or out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key was sent, or the key is unknown or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is valid but lacks a scope the endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id exists in this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests in one minute. Retry after the window resets. The monthly request quota is used up. It resets at the start of next month.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{id}":{"get":{"operationId":"get-post","summary":"Retrieve a post","description":"One post by id, with its full metric history. Snapshots are cumulative lifetime readings, oldest first, so a daily series is the difference between consecutive entries.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The post id, as returned by the list endpoint.","schema":{"type":"string"}}],"responses":{"200":{"description":"Retrieve a post","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in one second.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current second.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time, in seconds, when the current window resets.","schema":{"type":"integer"}},"X-Quota-Limit":{"description":"Requests allowed this calendar month.","schema":{"type":"integer"}},"X-Quota-Remaining":{"description":"Requests left this calendar month.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostDetail"}}}},"400":{"description":"A query parameter is missing, malformed, or out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key was sent, or the key is unknown or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is valid but lacks a scope the endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id exists in this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests in one minute. Retry after the window resets. The monthly request quota is used up. It resets at the start of next month.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key, sent as `Authorization: Bearer <key>`. Keys are secrets: call the API from your server, never from browser code."}},"schemas":{"PostMetrics":{"type":"object","description":"A post's numbers at one reading. A null means the platform does not report that metric, which is not the same as zero.","required":["views","impressions","reach","likes","comments","shares","saves","clicks","engagementRate","capturedAt"],"properties":{"views":{"type":"integer"},"impressions":{"type":"integer"},"reach":{"type":["integer","null"],"description":"Unique accounts reached. Null on platforms with no equivalent."},"likes":{"type":"integer"},"comments":{"type":"integer"},"shares":{"type":"integer"},"saves":{"type":["integer","null"],"description":"Instagram only."},"clicks":{"type":["integer","null"],"description":"Link clicks. Null where the post carries no link."},"engagementRate":{"type":"number","description":"(likes + comments + shares + saves) / impressions, as a fraction."},"capturedAt":{"type":"string","format":"date-time"}}},"Post":{"type":"object","required":["id","platform","format","title","url","caption","hashtags","thumbnailUrl","mediaUrls","publishedAt","metrics"],"properties":{"id":{"type":"string"},"platform":{"type":"string","enum":["instagram","linkedin","youtube"]},"format":{"type":"string","enum":["carousel","image","reel","story","video"]},"title":{"type":["string","null"],"description":"Null where the platform has no native title."},"url":{"type":"string","format":"uri"},"caption":{"type":"string"},"hashtags":{"type":"array","items":{"type":"string"},"description":"Normalized keys, without the leading #."},"thumbnailUrl":{"type":["string","null"],"format":"uri"},"mediaUrls":{"type":"array","items":{"type":"string","format":"uri"},"description":"Every image of the post in order, cover first."},"publishedAt":{"type":"string","format":"date-time"},"metrics":{"$ref":"#/components/schemas/PostMetrics"}}},"PostDetail":{"allOf":[{"$ref":"#/components/schemas/Post"},{"type":"object","required":["snapshots"],"properties":{"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/PostMetrics"},"description":"Cumulative lifetime readings, oldest first."}}}]},"Pagination":{"type":"object","required":["page","perPage","total","pageCount"],"properties":{"page":{"type":"integer"},"perPage":{"type":"integer"},"total":{"type":"integer"},"pageCount":{"type":"integer"}}},"PostList":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Post"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","notFound","validationError","rateLimitExceeded","quotaExceeded"]},"message":{"type":"string","description":"Human-readable detail. Branch on `code`, not on this."}}}}}}},"x-rateLimits":{"free":{"requestsPerSecond":1,"requestsPerMonth":500,"maxKeys":1},"creator":{"requestsPerSecond":5,"requestsPerMonth":25000,"maxKeys":5},"studio":{"requestsPerSecond":20,"requestsPerMonth":250000,"maxKeys":20}}}