{
    "openapi": "3.1.0",
    "info": {
        "title": "MetricPeek REST API",
        "version": "v1",
        "description": "The MetricPeek REST API gives a script or a server the work an AI assistant does over MCP, for one team: reports, social accounts, posts, media, brand voices, hashtag lists and hashtag search. Every endpoint runs the MCP tool named in its `x-metricpeek-tool`, with the same access checks, limits and prices.\n\n**Authentication.** Send a team API key in the `Authorization` header as a Bearer token. Keys start with `mp_`, and only the team owner creates them, in Settings → API & MCP. An AI assistant's OAuth token is not accepted here. Every failure to authenticate gets the same `401` with the code `unauthorized`.\n\n**Access levels.** A key holds up to four capabilities: read, write, publish and spend. Each operation names the one it needs in `x-metricpeek-capability`, and a key without it is refused with `403`. An operation that sometimes needs a second one lists it, with the condition, in `x-metricpeek-also-requires`. Operations that spend the team's credits state their price in `x-metricpeek-price`. An operation that also commits the team to a cost charged again later, such as tracking a report, states it in `x-metricpeek-recurring-price`: `price` is what each repetition costs, and `description` says in words when it repeats, roughly what it adds up to and how it stops.\n\n**Responses.** A success is a JSON envelope: `data`, `meta` (with the time in the team's timezone and in UTC) and, when they apply, `page`, `billing`, `untrusted_content` and `notes`. A field with nothing in it is left out. Text written by the accounts being analysed, such as captions, biographies and comments, is in `untrusted_content` and never in `data`.\n\n**Errors.** Every refusal is `application/problem+json` (RFC 9457) with `type`, `title`, `status`, `detail`, `code` and sometimes `context`. Branch on `code`, which is stable within v1. `type` is a link to that code's entry in the error documentation and may move; `title` and `detail` are sentences for people.\n\n**Idempotency.** Writes take an `Idempotency-Key` header. Repeating a call with the same key returns the first result instead of doing the work again, and says so with `Idempotent-Replayed: true`. Operations that spend credits require the header.\n\n**Pagination.** Lists take `cursor` and `limit`. When more items exist, `page.has_more` is true and the `Link` header carries the next page with `rel=\"next\"`.\n\n**Background work.** An operation answering `202` started a job. Its `Location` is the job's address, which reports the job until `data.finished` is true.\n\n**Limits.** A key shares its limits with its own MCP use: requests per minute per key, a burst limit per connection, the team's daily allowance of calls and the daily credit ceilings. A `429` carries `Retry-After`.\n\n**Versioning.** Within v1 changes are additive only: new endpoints, new optional fields and new error codes. A renamed path, field or code would be a new version.",
        "contact": {
            "name": "MetricPeek support",
            "url": "https://dev.metricpeek.com/support"
        }
    },
    "externalDocs": {
        "description": "REST API reference",
        "url": "https://dev.metricpeek.com/docs/api-reference"
    },
    "servers": [
        {
            "url": "https://dev.metricpeek.com/api/v1",
            "description": "MetricPeek"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "tags": [
        {
            "name": "Account",
            "description": "The team this key works for, its capabilities, credit balance and plan limits."
        },
        {
            "name": "Jobs",
            "description": "Background work started by other endpoints."
        },
        {
            "name": "Reports",
            "description": "Profile and hashtag analytics reports."
        },
        {
            "name": "Social accounts",
            "description": "The social accounts connected to the team."
        },
        {
            "name": "Posts",
            "description": "Drafts, scheduled posts and published posts."
        },
        {
            "name": "Media",
            "description": "The team's media library and the ways files get into it."
        },
        {
            "name": "Brand voices",
            "description": "The team's brand voices."
        },
        {
            "name": "Hashtag lists",
            "description": "The team's own hashtag lists and the ready-made ones."
        },
        {
            "name": "Hashtags",
            "description": "Hashtag search."
        }
    ],
    "paths": {
        "/me": {
            "get": {
                "operationId": "me",
                "summary": "Show the team and access of this API key",
                "description": "Returns the person this API key acts for, the team it works in, that person's role in the team, the capabilities the key has, and the team's timezone and content language. The capabilities are the effective ones, after the key's access level, the person's role and the operator's switches, so an operation that needs a listed capability is not refused for lack of it. The credit balance and the plan limits are not part of this answer and come from `GET /credits` and `GET /limits`. This call never counts against the team's daily allowance of free calls.",
                "tags": [
                    "Account"
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "whoami",
                "x-metricpeek-capability": "read"
            }
        },
        "/credits": {
            "get": {
                "operationId": "credits",
                "summary": "Get the team's credit balance",
                "description": "Returns the team's credit balance, the credits used today by the whole team and the part of them spent through API keys and MCP connections, both counted in the team's timezone, and the daily credit limits that apply to this key and to the team. Beside them come the credits reserved today, for this key and for all the team's keys and connections together: a profile report whose tracking was switched on through the API or MCP counts one refresh against the daily credit limits until midnight in the team's timezone, although nothing is charged for it. The balance is left out, rather than sent as zero, when the role of the person the key acts for may not see it. This call changes nothing, credits cannot be bought through the API, and it never counts against the team's daily allowance of free calls.",
                "tags": [
                    "Account"
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_credit_balance",
                "x-metricpeek-capability": "read"
            }
        },
        "/limits": {
            "get": {
                "operationId": "limits",
                "summary": "Get the team's plan limits",
                "description": "Returns the team's current plan and its limits: connected social accounts, posts a month, seats, storage, how long analytics data stays available, how many profiles can be compared and how many free calls a day the plan allows, together with whether the plan includes API and MCP access. A limit of `-1` means unlimited, while `0` means none. The answer also says how many free calls the team has left today. This call changes nothing and never counts against that allowance.",
                "tags": [
                    "Account"
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_plan_limits",
                "x-metricpeek-capability": "read"
            }
        },
        "/jobs/{job_id}": {
            "get": {
                "operationId": "jobsShow",
                "summary": "Get the status of a background job",
                "description": "Returns the state of a background job started by another endpoint, named in the `Location` of its `202` answer and in its `data.job_id`: `data.state` is `pending`, `running`, `succeeded`, `partially_succeeded` or `failed`, and `data.finished` turns true once the job has ended. A finished job names what it produced, such as the report, the new media item, the hashtags of a search with the credits it finally charged, the files of a profile media download, each file of an upload batch, or each account's result of a publication as the network confirms it. Long results come a page at a time, and text written by the accounts being analysed is in `untrusted_content`. A job of another team answers `404` like one that does not exist, and asking about a job never counts against the team's daily allowance of free calls.",
                "tags": [
                    "Jobs"
                ],
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "description": "The job id returned by the tool that started the work, in the form \"<type>:<uuid>\", where type is one of profile_report, hashtag_report, social_post, hashtag_search, media_import or content_fetch.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[a-z_]{1,32}:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page of a finished hashtag search or profile media download.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many hashtags of a finished search, or items of a finished profile media download, to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_job_status",
                "x-metricpeek-capability": "read"
            }
        },
        "/reports": {
            "get": {
                "operationId": "reportsIndex",
                "summary": "List reports",
                "description": "Lists the analytics reports the team already has, newest first, profile reports and hashtag reports alike. Each entry gives the report's id, kind, subject and status, when its data was last refreshed and until when it stays available, but not the report contents, which `GET /reports/{type}/{id}` returns. Filters narrow the list by kind, platform, status, tracking, or a part of the profile name or hashtag. Listing reports charges nothing.",
                "tags": [
                    "Reports"
                ],
                "parameters": [
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "description": "Return only this kind of report. Left out, both kinds are returned.",
                        "schema": {
                            "enum": [
                                "profile",
                                "hashtag"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "platform",
                        "in": "query",
                        "required": false,
                        "description": "Return only reports about this platform.",
                        "schema": {
                            "enum": [
                                "instagram",
                                "tiktok"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Return only reports in this state.",
                        "schema": {
                            "enum": [
                                "pending",
                                "ready",
                                "error"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "tracked_only",
                        "in": "query",
                        "required": false,
                        "description": "When true, return only reports whose periodic refreshing is on.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "query",
                        "in": "query",
                        "required": false,
                        "description": "Return only reports whose profile name or hashtag contains this text.",
                        "schema": {
                            "maxLength": 200,
                            "type": "string"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many reports to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_reports",
                "x-metricpeek-capability": "read"
            },
            "post": {
                "operationId": "reportsStore",
                "summary": "Create a report",
                "description": "Creates a new analytics report and answers `202` with a `Location` pointing at the job, which names the report once it is ready to read with `GET /reports/{type}/{id}`. A `profile` report analyses one public Instagram or TikTok profile, given as a username, an @handle or a profile link, and a `hashtag` report analyses one Instagram hashtag. A report the team already has, or one still being generated, is returned without a charge, and a report whose generation fails is refunded. An `Idempotency-Key` is required, and a repeat with the same key returns the same report without a second charge.\n\nThe key also needs the `write` capability. Always: the new report becomes an item the team keeps.",
                "tags": [
                    "Reports"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Required on this endpoint. A key of your choosing: a new one for each new request, and the same one when a request is repeated. A repeat with the same key never charges twice.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "description": "\"profile\" for an Instagram or TikTok profile, \"hashtag\" for an Instagram hashtag.",
                                        "enum": [
                                            "profile",
                                            "hashtag"
                                        ],
                                        "type": "string"
                                    },
                                    "platform": {
                                        "description": "instagram or tiktok. Hashtag reports exist for instagram only. Defaults to instagram.",
                                        "enum": [
                                            "instagram",
                                            "tiktok"
                                        ],
                                        "type": "string"
                                    },
                                    "subject": {
                                        "description": "The username (profile) or the hashtag (hashtag).",
                                        "minLength": 1,
                                        "maxLength": 200,
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "type",
                                    "subject"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "$ref": "#/components/responses/PaymentRequired"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "create_report",
                "x-metricpeek-capability": "spend",
                "x-metricpeek-also-requires": [
                    {
                        "capability": "write",
                        "when": "Always: the new report becomes an item the team keeps."
                    }
                ],
                "x-metricpeek-price": "0-10 credits"
            }
        },
        "/reports/{type}/{id}": {
            "get": {
                "operationId": "reportsShow",
                "summary": "Get a report",
                "description": "Returns one report of the team. By default the answer is a summary: the profile or hashtag analysed, the headline engagement figures, the 0-100 score with its components, the top hashtags and words, the best time to post, and when the data was measured. Larger parts are added only when named in `sections`, the summary's `sections_available` lists the ones this report holds, and a long section comes a page at a time. Captions, biographies and other text written by the analysed account are in `untrusted_content`, and reading a report charges nothing, because it was paid for when it was created.",
                "tags": [
                    "Reports"
                ],
                "parameters": [
                    {
                        "name": "type",
                        "in": "path",
                        "required": true,
                        "description": "Which kind of report to read: profile or hashtag.",
                        "schema": {
                            "enum": [
                                "profile",
                                "hashtag"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The report id, as `GET /reports` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "sections",
                        "in": "query",
                        "required": false,
                        "description": "Names of the larger parts to include, as listed in the summary's `sections_available`. Left out, only the summary is returned.",
                        "schema": {
                            "items": {
                                "type": "string"
                            },
                            "type": "array"
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page of a paged section.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many items of a paged section to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_report",
                "x-metricpeek-capability": "read"
            }
        },
        "/social-accounts": {
            "get": {
                "operationId": "socialAccountsIndex",
                "summary": "List connected social accounts",
                "description": "Lists the social accounts connected to the team, with their id, platform, username, whether the account is active and the state of its connection. By default only accounts with a live connection are listed, and `active_only=false` adds the others, which is where an account that needs reconnecting shows up. The ids are the ones posts, account analytics and `GET /connect-link` take. Nothing is connected or disconnected here.",
                "tags": [
                    "Social accounts"
                ],
                "parameters": [
                    {
                        "name": "platform",
                        "in": "query",
                        "required": false,
                        "description": "Return only accounts on this platform, for example instagram, tiktok, facebook, linkedin, pinterest, bluesky, threads, youtube or x.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "active_only",
                        "in": "query",
                        "required": false,
                        "description": "When true (the default) only accounts whose connection is live are returned.",
                        "schema": {
                            "default": true,
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many accounts to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_social_accounts",
                "x-metricpeek-capability": "read"
            }
        },
        "/social-accounts/{account_id}/analytics": {
            "get": {
                "operationId": "socialAccountsAnalytics",
                "summary": "Get the analytics of a connected account",
                "description": "Returns how the posts of one connected account performed over a period of days: reach, engagement, the best performing post, the publishing cadence and the breakdown by content format. The figures are the ones the analytics screen of the app shows, computed from the account's posts as they were collected from the network in the background, so this call reaches no network and covers connected accounts only. An empty answer says why in `notes`, for example that nothing has been collected yet or that no post falls inside the period. A team whose plan does not include social analytics is refused.",
                "tags": [
                    "Social accounts"
                ],
                "parameters": [
                    {
                        "name": "account_id",
                        "in": "path",
                        "required": true,
                        "description": "The id of a connected account, as `GET /social-accounts` returns it.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "period_days",
                        "in": "query",
                        "required": false,
                        "description": "How many days back to measure. Defaults to 30.",
                        "schema": {
                            "default": 30,
                            "minimum": 1,
                            "maximum": 365,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_account_analytics",
                "x-metricpeek-capability": "read"
            }
        },
        "/posts": {
            "get": {
                "operationId": "postsIndex",
                "summary": "List posts",
                "description": "Lists the team's posts, newest first, with their status, their scheduled or published time in the team's timezone and in UTC, the accounts they target and the start of their text. Filters narrow the list by status, by account and by a time range, and a time without a UTC offset is read in the team's timezone. The full post, with its media, its per-account results and its per-account overrides, comes from `GET /posts/{id}`.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Return only posts in this status.",
                        "schema": {
                            "enum": [
                                "draft",
                                "scheduled",
                                "processing",
                                "published",
                                "partially_published",
                                "failed"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "account_id",
                        "in": "query",
                        "required": false,
                        "description": "Return only posts targeting this connected account.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "description": "ISO 8601 lower bound on the scheduled or published time. Without a UTC offset it is read in the team's timezone.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "description": "ISO 8601 upper bound on the scheduled or published time. Without a UTC offset it is read in the team's timezone.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many posts to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_posts",
                "x-metricpeek-capability": "read"
            },
            "post": {
                "operationId": "postsStore",
                "summary": "Create a post draft",
                "description": "Creates a draft post for one or more of the team's connected accounts, with its text and, optionally, media from the Media Library, and answers `201` with the post and a `Location` pointing at it. Nothing is sent to any network until the post is scheduled or published. A text longer than MetricPeek accepts is refused rather than shortened. A retry with the same `Idempotency-Key` returns the draft the first call created instead of making a second one.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the draft the first call created instead of creating another.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "caption": {
                                        "description": "The text of the post.",
                                        "type": "string"
                                    },
                                    "account_ids": {
                                        "description": "Ids of the connected accounts the post is for, as `GET /social-accounts` returns them.",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "type": "array"
                                    },
                                    "media_ids": {
                                        "description": "Ids of media library items to attach, in order.",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "type": "array"
                                    }
                                },
                                "required": [
                                    "caption",
                                    "account_ids"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created. The `Location` header is the address of `GET /posts/{id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "create_post_draft",
                "x-metricpeek-capability": "write"
            }
        },
        "/posts/{id}": {
            "get": {
                "operationId": "postsShow",
                "summary": "Get a post",
                "description": "Returns one post of the team in full: its whole text, its media, every account it targets with that account's own result, and the per-account overrides of text or media that show what goes out on each network. Post text written by the team is returned as data. A failed account carries an `error_code` and a short explanation, while the network's own error text is in `untrusted_content`.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of one post. Given, the response carries that post in full instead of the index.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_posts",
                "x-metricpeek-capability": "read"
            },
            "patch": {
                "operationId": "postsUpdate",
                "summary": "Change a draft or scheduled post",
                "description": "Changes the text, publish time, media or target accounts of a draft or scheduled post and returns the post as stored, with the validation problems found after the change. Fields left out stay as they are, while `media_ids` and `account_ids` replace the whole set. A change to a scheduled post is passed to the publishing provider at once and goes out as changed, and a change to a post the client approved sends it back to the client for approval. Published, publishing and failed posts cannot be changed, and neither can the template of a recurring series.\n\nThe key also needs the `publish` capability. When the post is scheduled: the change is sent to the platform provider and goes out without anyone pressing Publish.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the post, as `GET /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's result instead of applying the change again.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "caption": {
                                        "description": "The new text of the post.",
                                        "type": "string"
                                    },
                                    "scheduled_at": {
                                        "description": "The new publish time in ISO 8601, for example 2026-09-29T15:00 or 2026-09-29T13:00:00Z. Without a UTC offset it is read in the team's timezone.",
                                        "type": "string"
                                    },
                                    "media_ids": {
                                        "description": "The complete list of media library item ids the post should carry, in order.",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "type": "array"
                                    },
                                    "account_ids": {
                                        "description": "The complete list of connected account ids the post should go to.",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "type": "array"
                                    }
                                },
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "update_post",
                "x-metricpeek-capability": "write",
                "x-metricpeek-also-requires": [
                    {
                        "capability": "publish",
                        "when": "When the post is scheduled: the change is sent to the platform provider and goes out without anyone pressing Publish."
                    }
                ]
            }
        },
        "/posts/{post_id}/validation": {
            "get": {
                "operationId": "postsValidation",
                "summary": "Check a post against the platform rules",
                "description": "Checks a post against the rules of every network it targets and returns the problems found, each with a code and whether it blocks publishing. The answer also says whether the post is waiting for the client's approval and how much of the team's monthly post allowance is left, and `account_id` limits the check to one of the post's accounts. Nothing is changed and nothing is sent to any network.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "post_id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the post, as `GET /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "account_id",
                        "in": "query",
                        "required": false,
                        "description": "Check the post only for this one of its accounts.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "validate_post",
                "x-metricpeek-capability": "read"
            }
        },
        "/media": {
            "get": {
                "operationId": "mediaIndex",
                "summary": "List media library files",
                "description": "Lists the files in the team's Media Library with their id, name, file name, type, size and folder, together with how much of the team's storage is used. Filters narrow the list by folder, with `0` for files in no folder, by images or video, and by a part of the name. The ids are the ones posts take as `media_ids`, and nothing is uploaded here.",
                "tags": [
                    "Media"
                ],
                "parameters": [
                    {
                        "name": "folder_id",
                        "in": "query",
                        "required": false,
                        "description": "Return only files in this folder. Pass 0 for files in no folder at all.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "kind",
                        "in": "query",
                        "required": false,
                        "description": "Return only images, or only video.",
                        "schema": {
                            "enum": [
                                "image",
                                "video"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "query",
                        "in": "query",
                        "required": false,
                        "description": "Return only files whose name or file name contains this text.",
                        "schema": {
                            "maxLength": 200,
                            "type": "string"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many files to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_media",
                "x-metricpeek-capability": "read"
            }
        },
        "/brand-voices": {
            "get": {
                "operationId": "brandVoicesIndex",
                "summary": "List brand voices",
                "description": "Returns a short entry for every brand voice of the team and the default voice in full: the business description, tone, point of view, audiences, emoji policy, words and phrases to use and to avoid, custom instructions, content language, and one ready-assembled text block that holds all of it. The text block is the same one the app itself uses when it writes captions and carousels. Another voice in full comes from `GET /brand-voices/{id}`, and the material a voice was researched from is never returned.",
                "tags": [
                    "Brand voices"
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_brand_voice",
                "x-metricpeek-capability": "read"
            }
        },
        "/brand-voices/{id}": {
            "get": {
                "operationId": "brandVoicesShow",
                "summary": "Get a brand voice",
                "description": "Returns one brand voice of the team in full: the business description, tone, point of view, audiences, emoji policy, words and phrases to use and to avoid, custom instructions and content language, plus the ready-assembled text block the app itself writes with. The material the voice was researched from is never returned.",
                "tags": [
                    "Brand voices"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The id of one of the team's brand voices. Left out, every voice is returned in short form together with the full text block of the default one.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_brand_voice",
                "x-metricpeek-capability": "read"
            }
        },
        "/hashtag-lists": {
            "get": {
                "operationId": "hashtagListsIndex",
                "summary": "List hashtag lists",
                "description": "Lists the hashtag lists the team can use: its own lists and the ready-made lists that come with MetricPeek. Each entry gives the list's name, platform, how many hashtags it holds and whether it is ready-made, and `scope` picks the team's own lists, the ready-made ones or both. One list with its hashtags comes from `GET /hashtag-lists/{list_id}`.",
                "tags": [
                    "Hashtag lists"
                ],
                "parameters": [
                    {
                        "name": "scope",
                        "in": "query",
                        "required": false,
                        "description": "Which lists to return: the team's own, the ready-made ones, or both (the default).",
                        "schema": {
                            "default": "all",
                            "enum": [
                                "own",
                                "system",
                                "all"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "platform",
                        "in": "query",
                        "required": false,
                        "description": "Return only lists tied to this platform.",
                        "schema": {
                            "enum": [
                                "instagram",
                                "tiktok"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "The cursor returned in `page.cursor` of a previous call, to read the next page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many lists to return, 1 to 50. Defaults to 20.",
                        "schema": {
                            "minimum": 1,
                            "maximum": 50,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Link": {
                                "$ref": "#/components/headers/Link"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_hashtag_lists",
                "x-metricpeek-capability": "read"
            },
            "post": {
                "operationId": "hashtagListsStore",
                "summary": "Create a hashtag list",
                "description": "Creates a hashtag list owned by the team and answers `201` with the new list and a `Location` pointing at it. Hashtags given in `hashtags` go in at once, the answer names any that were refused, and a hashtag MetricPeek does not know yet is added and gets its figures later. Each call without an `Idempotency-Key` creates another list, so a retry sends the same key to get the first list back. Nothing is charged, a team at its plan's limit of saved lists is refused, and ready-made lists cannot be created or changed this way.",
                "tags": [
                    "Hashtag lists"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's result instead of creating a second list.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "description": "The name of the list, 1-50 characters.",
                                        "minLength": 1,
                                        "maxLength": 50,
                                        "type": "string"
                                    },
                                    "platform": {
                                        "description": "The platform whose metrics the list is about. Left out, the list is not tied to a platform.",
                                        "enum": [
                                            "instagram",
                                            "tiktok"
                                        ],
                                        "type": "string"
                                    },
                                    "color": {
                                        "description": "A six-digit hex colour for the list, for example #4F46E5.",
                                        "pattern": "^#[0-9A-Fa-f]{6}$",
                                        "type": "string"
                                    },
                                    "icon": {
                                        "description": "A Remix icon token for the list, for example ri-folder-fill.",
                                        "pattern": "^ri-[a-z0-9-]+$",
                                        "type": "string"
                                    },
                                    "hashtags": {
                                        "description": "Hashtag names to put in the new list, with or without a leading #.",
                                        "items": {
                                            "type": "string"
                                        },
                                        "type": "array"
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created. The `Location` header is the address of `GET /hashtag-lists/{list_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "create_hashtag_list",
                "x-metricpeek-capability": "write"
            }
        },
        "/hashtag-lists/{list_id}": {
            "get": {
                "operationId": "hashtagListsShow",
                "summary": "Get a hashtag list with its hashtags",
                "description": "Returns one hashtag list the team can use, one of its own or a ready-made one, with its hashtags and, for each hashtag, its size, tier and score. Each size says whether it was measured on the network or estimated from the hashtags that appear alongside it.",
                "tags": [
                    "Hashtag lists"
                ],
                "parameters": [
                    {
                        "name": "list_id",
                        "in": "path",
                        "required": true,
                        "description": "The id of one visible list. Given, the response carries that list with its hashtags instead of the index.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "list_hashtag_lists",
                "x-metricpeek-capability": "read"
            }
        },
        "/connect-link": {
            "get": {
                "operationId": "connectLink",
                "summary": "Get a link to connect or reconnect a social account",
                "description": "Returns a link to a page in MetricPeek where the person the key acts for connects a new social account to the team, when `platform` is given, or reconnects an account whose connection stopped working, when `account_id` is given. Nothing is connected until that person confirms in a signed-in browser, and the page asks before it switches the browser to this team. For a new connection the answer also gives `slots_left`: how many more accounts the plan allows, `0` when none is free and `null` when the plan sets no limit. An account whose connection still works is refused, because reconnecting it would disconnect it first.",
                "tags": [
                    "Social accounts"
                ],
                "parameters": [
                    {
                        "name": "platform",
                        "in": "query",
                        "required": false,
                        "description": "The platform to connect a new account on. Give this or account_id, not both.",
                        "schema": {
                            "enum": [
                                "instagram",
                                "facebook",
                                "tiktok",
                                "tiktok_business",
                                "youtube",
                                "linkedin",
                                "x",
                                "threads",
                                "pinterest",
                                "bluesky"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "account_id",
                        "in": "query",
                        "required": false,
                        "description": "The id of a connected account whose connection stopped working (`GET /social-accounts` shows it inactive, with connection_probe refused, or with account_status needs_reauth), to reconnect it. Give this or platform, not both.",
                        "schema": {
                            "minimum": 1,
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "get_connect_link",
                "x-metricpeek-capability": "read"
            }
        },
        "/hashtag-lists/{list_id}/hashtags": {
            "post": {
                "operationId": "hashtagListsHashtags",
                "summary": "Add hashtags to a list",
                "description": "Adds hashtags to one of the team's own lists and answers with the hashtags added, the ones already in the list and the ones refused. A hashtag already in the list stays as it is, so repeating the same call changes nothing further. Ready-made lists cannot be changed, and nothing is charged.",
                "tags": [
                    "Hashtag lists"
                ],
                "parameters": [
                    {
                        "name": "list_id",
                        "in": "path",
                        "required": true,
                        "description": "The id of one of the team's own lists, as `GET /hashtag-lists` returns it.",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's result.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hashtags": {
                                        "description": "Hashtag names to add, with or without a leading #.",
                                        "minItems": 1,
                                        "items": {
                                            "type": "string"
                                        },
                                        "type": "array"
                                    },
                                    "subcategory": {
                                        "description": "An optional grouping label stored alongside each hashtag in this list.",
                                        "maxLength": 100,
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "hashtags"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "add_hashtags_to_list",
                "x-metricpeek-capability": "write"
            }
        },
        "/media/imports": {
            "post": {
                "operationId": "mediaImports",
                "summary": "Import a file from a public link",
                "description": "Starts downloading one image or video from a public https address into the team's Media Library and answers `202` with a `Location` pointing at the job, which gives the new media id once the download has finished. Only public https addresses are fetched, and private, local and numeric addresses are refused before anything is downloaded. The file type is read from the downloaded bytes, not from the address or the name, and the file uses the team's storage, not credits. A file that is already in the library returns the existing media item instead of a second copy.",
                "tags": [
                    "Media"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's job instead of starting another download.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "url": {
                                        "description": "The public https address of the image or video.",
                                        "maxLength": 2048,
                                        "type": "string"
                                    },
                                    "filename": {
                                        "description": "A name for the file in the library. Only the part before the extension is used; the extension follows the file's real type.",
                                        "maxLength": 120,
                                        "type": "string"
                                    },
                                    "folder_id": {
                                        "description": "The id of one of the team's media library folders, as `GET /media` returns it. Left out, the file is not put in a folder.",
                                        "type": "integer"
                                    }
                                },
                                "required": [
                                    "url"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "upload_media_from_url",
                "x-metricpeek-capability": "write"
            }
        },
        "/posts/{post_id}/cancel": {
            "post": {
                "operationId": "postsCancel",
                "summary": "Cancel a scheduled post",
                "description": "Takes a scheduled post off the schedule: its scheduled copy is withdrawn from the publishing provider and the post becomes a draft again, with its content kept. A recurring series is paused, not deleted. A post that is already publishing or published cannot be cancelled, and cancelling a draft changes nothing. When the provider does not release the scheduled copy, the post stays scheduled and the call is refused, so a cancelled post never goes out.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "post_id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the scheduled post, as `GET /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's result.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "cancel_scheduled_post",
                "x-metricpeek-capability": "write"
            }
        },
        "/uploads": {
            "post": {
                "operationId": "uploadsStore",
                "summary": "Create upload links for files",
                "description": "Opens an upload batch for the team's Media Library and answers `201` with a `Location` pointing at the batch's job, which reports each file as it arrives or is refused. Every file described in `files` by its SHA-256 hash and size gets its own `PUT` link under `data.upload.uploads`, with a one-time secret that accepts exactly the described bytes, once, before the expiry time in the answer, and `data.upload_page_url` is a page where a signed-in person can upload the files in a browser instead. The secrets appear in this answer only, and a repeat with the same `Idempotency-Key` returns the same batch with new secrets while the earlier ones stop working. Uploads use the team's storage, not credits, with the size limits stated in `data.limits`, and a file already in the library returns the existing media item.",
                "tags": [
                    "Media"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's batch instead of creating another.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "files": {
                                        "description": "Optional. The files that will be uploaded, in the order they should keep, each described by the lowercase hex SHA-256 of its bytes, its size in bytes, its MIME type and its name.",
                                        "maxItems": 10,
                                        "items": {
                                            "additionalProperties": false,
                                            "properties": {
                                                "sha256": {
                                                    "type": "string"
                                                },
                                                "size_bytes": {
                                                    "minimum": 1,
                                                    "type": "integer"
                                                },
                                                "mime_type": {
                                                    "type": "string"
                                                },
                                                "file_name": {
                                                    "type": "string"
                                                }
                                            },
                                            "type": "object",
                                            "required": [
                                                "sha256",
                                                "size_bytes"
                                            ]
                                        },
                                        "type": "array"
                                    },
                                    "folder_id": {
                                        "description": "The id of one of the team's media library folders, as `GET /media` returns it. Left out, the files are not put in a folder.",
                                        "type": "integer"
                                    },
                                    "replaces_batch": {
                                        "description": "Optional. The job id of an earlier batch from this connection that the new batch replaces. Its files that are still waiting are closed and `GET /jobs/{job_id}` reports them as replaced by the new batch. Ignored when a file of that batch has already arrived.",
                                        "type": "string"
                                    }
                                },
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "create_upload_links",
                "x-metricpeek-capability": "write"
            }
        },
        "/hashtags/search": {
            "post": {
                "operationId": "hashtagsSearch",
                "summary": "Search hashtags",
                "description": "Searches Instagram or TikTok for hashtags matching a keyword or a hashtag and answers `202` with a `Location` pointing at the job, which returns the hashtags found with their measured or estimated post count, posting rate, tier and 0-100 score, and the hashtags that often appear alongside them. A keyword the team has already paid for on the same platform, wherever it was bought, is served from stored data without a charge while the team's data window on that search is live, and a search that finds nothing is refunded. An `Idempotency-Key` is required, and a repeat with the same key and the same query returns the same job without a second charge. Nothing is saved to a hashtag list.",
                "tags": [
                    "Hashtags"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Required on this endpoint. A key of your choosing: a new one for each new request, and the same one when a request is repeated. A repeat with the same key never charges twice.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "query": {
                                        "description": "A keyword (\"bakery kraków\") or a hashtag (\"#sourdough\"), 2-100 characters. A leading # is optional; the order of words in a phrase does not change the price.",
                                        "minLength": 2,
                                        "maxLength": 100,
                                        "type": "string"
                                    },
                                    "platform": {
                                        "description": "Which platform to search. Defaults to instagram.",
                                        "enum": [
                                            "instagram",
                                            "tiktok"
                                        ],
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "query"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "$ref": "#/components/responses/PaymentRequired"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "search_hashtags",
                "x-metricpeek-capability": "spend",
                "x-metricpeek-price": "0-5 credits"
            }
        },
        "/reports/{type}/{id}/refresh": {
            "post": {
                "operationId": "reportsRefresh",
                "summary": "Refresh a report",
                "description": "Fetches current data for a report the team already has and moves its data window forward, answering `202` with a `Location` pointing at the job, which says when the refresh has finished. A refresh that could not fetch anything is refunded. The same person can refresh a report again only after a short interval, shared with the app, and an earlier call is refused with `429` and `Retry-After`. An `Idempotency-Key` is required, and a repeat with the same key returns the same refresh without a second charge.",
                "tags": [
                    "Reports"
                ],
                "parameters": [
                    {
                        "name": "type",
                        "in": "path",
                        "required": true,
                        "description": "Which kind of report the id names.",
                        "schema": {
                            "enum": [
                                "profile",
                                "hashtag"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The report id, as `GET /reports` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Required on this endpoint. A key of your choosing: a new one for each new request, and the same one when a request is repeated. A repeat with the same key never charges twice.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "$ref": "#/components/responses/PaymentRequired"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "refresh_report",
                "x-metricpeek-capability": "spend",
                "x-metricpeek-price": "5 credits"
            }
        },
        "/reports/{type}/{id}/tracking": {
            "put": {
                "operationId": "reportsTracking",
                "summary": "Turn report tracking on or off",
                "description": "Turns the automatic refreshing of a report on or off, or changes how often it runs, and answers at once with the report's tracking state, including the next automatic refresh and what each one charges. While tracking is on, every automatic refresh is charged to the team with no further call, until tracking is turned off. Switching tracking on charges a hashtag report at once and a profile report nothing at once, while switching it off or changing the frequency of a tracked report charges nothing. Switching tracking on through the API counts one automatic refresh against the key's and the team's daily credit limits until midnight in the team's timezone. For a hashtag report that is the charge made at once, and for a profile report, where nothing is charged at once, it is counted all the same and switching tracking off again gives it back. The team's plan limits how many reports can be tracked at the same time, and an `Idempotency-Key` is required, although a repeated switch is recognised as the same switch and charged once.",
                "tags": [
                    "Reports"
                ],
                "parameters": [
                    {
                        "name": "type",
                        "in": "path",
                        "required": true,
                        "description": "Which kind of report the id names.",
                        "schema": {
                            "enum": [
                                "profile",
                                "hashtag"
                            ],
                            "type": "string"
                        }
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "The report id, as `GET /reports` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Required on this endpoint. A key of your choosing: a new one for each new request, and the same one when a request is repeated. A repeat with the same key never charges twice.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "enabled": {
                                        "description": "true to track the report, false to stop tracking it.",
                                        "type": "boolean"
                                    },
                                    "frequency_days": {
                                        "description": "How often the automatic refresh runs, in days: 1, 3 or 7. Defaults to 3.",
                                        "enum": [
                                            1,
                                            3,
                                            7
                                        ],
                                        "type": "integer"
                                    }
                                },
                                "required": [
                                    "enabled"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "$ref": "#/components/responses/PaymentRequired"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    }
                },
                "x-metricpeek-tool": "set_report_tracking",
                "x-metricpeek-capability": "spend",
                "x-metricpeek-price": "0-5 credits",
                "x-metricpeek-recurring-price": {
                    "price": "5 credits",
                    "description": "While tracking is on, every automatic refresh charges the team 5 credits, for either type of report: about 150 credits a month when it runs daily, about 50 every 3 days, about 20 weekly, until it is turned off."
                }
            }
        },
        "/profile-media-downloads": {
            "post": {
                "operationId": "profileMediaDownloadsStore",
                "summary": "Download media from a public profile",
                "description": "Downloads media of the kind named in `type` from a public Instagram or TikTok profile and answers `202` with a `Location` pointing at the job, which returns what was fetched a page at a time, with a cursor for the next page where there is one. By default the files are saved into the Downloads folder of the team's Media Library and use the team's storage, while `save_to_library` set to false returns only their descriptions, and the networks' own media links are never returned. The charge is per call, whatever the profile turns out to hold, and a fetch that fails because the profile does not exist, is private or could not be read is refunded. An `Idempotency-Key` is required, and a repeat with the same key returns the same job without a second charge.\n\nThe key also needs the `write` capability. When save_to_library is true, the default: the files are saved into the team's media library.",
                "tags": [
                    "Media"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Required on this endpoint. A key of your choosing: a new one for each new request, and the same one when a request is repeated. A repeat with the same key never charges twice.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 255
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "platform": {
                                        "description": "instagram or tiktok.",
                                        "enum": [
                                            "instagram",
                                            "tiktok"
                                        ],
                                        "type": "string"
                                    },
                                    "type": {
                                        "description": "What to fetch. Instagram: profile, avatar, posts, reels, stories, highlights. TikTok: profile, avatar, posts, playlists.",
                                        "enum": [
                                            "profile",
                                            "avatar",
                                            "posts",
                                            "reels",
                                            "stories",
                                            "highlights",
                                            "playlists"
                                        ],
                                        "type": "string"
                                    },
                                    "username": {
                                        "description": "The account: a username, an @handle or a link to the profile.",
                                        "minLength": 1,
                                        "maxLength": 200,
                                        "type": "string"
                                    },
                                    "cursor": {
                                        "description": "The next_cursor of an earlier finished fetch of the same profile, to get its next page. Only profile, posts and reels have further pages.",
                                        "maxLength": 512,
                                        "type": "string"
                                    },
                                    "limit": {
                                        "description": "How many files to take from what was fetched, 1-50 (1 for an avatar). Defaults to 12.",
                                        "minimum": 1,
                                        "maximum": 50,
                                        "type": "integer"
                                    },
                                    "save_to_library": {
                                        "description": "Save the files into the team's media library. Defaults to true.",
                                        "type": "boolean"
                                    }
                                },
                                "required": [
                                    "platform",
                                    "type",
                                    "username"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "$ref": "#/components/responses/PaymentRequired"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "download_profile_media",
                "x-metricpeek-capability": "spend",
                "x-metricpeek-also-requires": [
                    {
                        "capability": "write",
                        "when": "When save_to_library is true, the default: the files are saved into the team's media library."
                    }
                ],
                "x-metricpeek-price": "1-4 credits"
            }
        },
        "/posts/{post_id}/schedule": {
            "post": {
                "operationId": "postsSchedule",
                "summary": "Schedule a post",
                "description": "Schedules a draft for a given time on every account it targets, and the publishing provider publishes it then with no further confirmation. A time without a UTC offset is read in the team's timezone, a date without a time is refused, and the answer gives the time in the team's timezone and in UTC. A post waiting for the client's approval, a post with blocking validation problems and a team that has used its monthly post allowance are refused. A post that is already scheduled is moved with `PATCH /posts/{id}` rather than here, and a repeat with the same `Idempotency-Key` returns the first result.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "post_id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the draft, as `GET /posts` or `POST /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. Repeating the call with the same key returns the first call's result instead of scheduling again.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "scheduled_at": {
                                        "description": "When to publish, in ISO 8601 with a time, for example 2026-09-29T15:00 (team timezone) or 2026-09-29T13:00:00Z. A date without a time is refused.",
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "scheduled_at"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK.",
                        "headers": {
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "schedule_post",
                "x-metricpeek-capability": "publish"
            }
        },
        "/posts/{post_id}/publish": {
            "post": {
                "operationId": "postsPublish",
                "summary": "Publish a post now",
                "description": "Sends a draft or scheduled post to every account it targets, to be published now, and answers `202` with a `Location` pointing at the job. Each network confirms its account separately, and the job reports each account's result as it arrives, so the `202` means the post was handed over, not that it was published. A post waiting for the client's approval, a post with blocking validation problems and a team that has used its monthly post allowance are refused, each with its own code. A post that was already sent is never sent again, with or without an `Idempotency-Key`, and the answer then says `already_submitted`.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "post_id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the post, as `GET /posts` or `POST /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. Repeating the call with the same key never sends the post twice and returns already_submitted.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "publish_post",
                "x-metricpeek-capability": "publish"
            }
        },
        "/posts/{post_id}/retry": {
            "post": {
                "operationId": "postsRetry",
                "summary": "Retry the failed accounts of a post",
                "description": "Sends a post again to the accounts where publishing failed, or to the listed ones among them, and answers `202` with a `Location` pointing at the job, which reports each account's new result. Accounts that did not fail, that have used up their retries or that were retried moments ago are skipped and named with the reason. A post waiting for the client's approval is refused. A repeated call never sends an account twice.",
                "tags": [
                    "Posts"
                ],
                "parameters": [
                    {
                        "name": "post_id",
                        "in": "path",
                        "required": true,
                        "description": "The uuid of the post, as `GET /posts` returns it.",
                        "schema": {
                            "type": "string",
                            "pattern": "^(?:[A-Za-z0-9-]{1,64})$"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "description": "A key of your choosing. A second call with the same key from this connection within 24 hours returns the first call's result.",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "account_ids": {
                                        "description": "Retry only these of the post's accounts. Left out, every account where publishing failed is retried.",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "type": "array"
                                    }
                                },
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. The `Location` header is the address of `GET /jobs/{job_id}` for this result.",
                        "headers": {
                            "Location": {
                                "$ref": "#/components/headers/Location"
                            },
                            "Idempotent-Replayed": {
                                "$ref": "#/components/headers/IdempotentReplayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Envelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableContent"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalServerError"
                    },
                    "502": {
                        "$ref": "#/components/responses/BadGateway"
                    }
                },
                "x-metricpeek-tool": "retry_failed_accounts",
                "x-metricpeek-capability": "publish"
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "MetricPeek API key",
                "description": "A team API key, starting with `mp_`, created by the team owner in Settings → API & MCP."
            }
        },
        "headers": {
            "Location": {
                "description": "The address of the item this call created, or of the job it started.",
                "schema": {
                    "type": "string",
                    "format": "uri"
                }
            },
            "Link": {
                "description": "Present when more items exist: the address of the next page, with `rel=\"next\"`.",
                "schema": {
                    "type": "string"
                }
            },
            "IdempotentReplayed": {
                "description": "Present on a repeated call with an `Idempotency-Key` that was already used: the answer is the first call's result and nothing was done again.",
                "schema": {
                    "type": "string",
                    "enum": [
                        "true"
                    ]
                }
            },
            "RetryAfter": {
                "description": "How many seconds to wait before repeating the call.",
                "schema": {
                    "type": "integer",
                    "minimum": 1
                }
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Unauthorized. `code` is one of: `unauthorized`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "PaymentRequired": {
                "description": "Payment Required. `code` is one of: `insufficient_credits`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "Forbidden": {
                "description": "Forbidden. `code` is one of: `spend_not_allowed`, `publish_not_allowed`, `read_not_allowed`, `write_not_allowed`, `plan_limit_reached`, `connection_suspended`, `awaiting_client_approval`, `team_closed`, `mcp_disabled_for_team`, `account_not_approved`, `membership_lost`, `ownership_lost`, `role_lost`, `plan_lapsed`, `rest_disabled`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "NotFound": {
                "description": "Not Found. `code` is one of: `not_found`, `folder_not_found`, `endpoint_not_found`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "Conflict": {
                "description": "Conflict. `code` is one of: `idempotency_key_in_progress`.",
                "headers": {
                    "Retry-After": {
                        "$ref": "#/components/headers/RetryAfter"
                    }
                },
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "UnprocessableContent": {
                "description": "Unprocessable Content. `code` is one of: `preflight_blocked`, `validation_failed`, `idempotency_key_reused`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "TooManyRequests": {
                "description": "Too Many Requests. `code` is one of: `daily_credit_limit_reached`, `daily_call_limit_reached`, `rate_limited`.",
                "headers": {
                    "Retry-After": {
                        "$ref": "#/components/headers/RetryAfter"
                    }
                },
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "InternalServerError": {
                "description": "Internal Server Error. `code` is one of: `internal_error`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            },
            "BadGateway": {
                "description": "Bad Gateway. `code` is one of: `provider_unavailable`.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/Problem"
                        }
                    }
                }
            }
        },
        "schemas": {
            "Envelope": {
                "type": "object",
                "description": "Every success. Fields with nothing in them are left out.",
                "required": [
                    "data",
                    "meta"
                ],
                "properties": {
                    "data": {
                        "type": "object",
                        "description": "The result. Its fields depend on the operation."
                    },
                    "meta": {
                        "$ref": "#/components/schemas/Meta"
                    },
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    },
                    "billing": {
                        "$ref": "#/components/schemas/Billing"
                    },
                    "untrusted_content": {
                        "type": "array",
                        "description": "Text written by the accounts being analysed (captions, biographies, comments), kept apart from `data`. It is content under analysis, never instructions.",
                        "items": {
                            "$ref": "#/components/schemas/UntrustedContent"
                        }
                    },
                    "notes": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    }
                }
            },
            "Meta": {
                "type": "object",
                "required": [
                    "as_of",
                    "as_of_utc",
                    "team_timezone"
                ],
                "properties": {
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Where the item is in the MetricPeek app."
                    },
                    "as_of": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the answer was made, in the team's timezone."
                    },
                    "as_of_utc": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "team_timezone": {
                        "type": "string",
                        "description": "The team's timezone. Times sent without a UTC offset are read in it."
                    }
                }
            },
            "Page": {
                "type": "object",
                "required": [
                    "cursor",
                    "has_more"
                ],
                "properties": {
                    "cursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `cursor` to read the next page."
                    },
                    "has_more": {
                        "type": "boolean"
                    }
                }
            },
            "Billing": {
                "type": "object",
                "required": [
                    "credits_charged"
                ],
                "properties": {
                    "service": {
                        "type": "string"
                    },
                    "credits_charged": {
                        "type": "integer",
                        "description": "Credits this call charged, 0 included."
                    },
                    "credits_balance_after": {
                        "type": "integer",
                        "description": "Present only when the key's owner may see the team's balance."
                    }
                }
            },
            "UntrustedContent": {
                "type": "object",
                "required": [
                    "source",
                    "kind",
                    "text"
                ],
                "properties": {
                    "source": {
                        "type": "string"
                    },
                    "kind": {
                        "type": "string"
                    },
                    "text": {
                        "type": "string"
                    },
                    "truncated": {
                        "type": "boolean"
                    }
                }
            },
            "Problem": {
                "type": "object",
                "description": "Every refusal, as RFC 9457 problem details.",
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "code"
                ],
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "description": "A link to this code's entry in the error documentation (https://dev.metricpeek.com/docs/api-errors). Not for branching: it may move. Branch on `code`."
                    },
                    "title": {
                        "type": "string"
                    },
                    "status": {
                        "type": "integer"
                    },
                    "detail": {
                        "type": "string"
                    },
                    "code": {
                        "$ref": "#/components/schemas/ErrorCode"
                    },
                    "context": {
                        "type": "object",
                        "description": "Codes and identifiers only, never a value the caller sent, for example `reason`, `argument`, `scope` or `retry_after_seconds`.",
                        "additionalProperties": {
                            "type": [
                                "string",
                                "integer",
                                "number",
                                "boolean",
                                "null"
                            ]
                        }
                    }
                }
            },
            "ErrorCode": {
                "description": "The stable, machine-readable reason of a refusal. The list is open: a new code may be added within v1, so handle an unknown code by its HTTP status. `x-metricpeek-errors` gives the status and title of every known code.",
                "anyOf": [
                    {
                        "type": "string",
                        "enum": [
                            "spend_not_allowed",
                            "publish_not_allowed",
                            "read_not_allowed",
                            "write_not_allowed",
                            "daily_credit_limit_reached",
                            "daily_call_limit_reached",
                            "plan_limit_reached",
                            "insufficient_credits",
                            "preflight_blocked",
                            "not_found",
                            "validation_failed",
                            "connection_suspended",
                            "rate_limited",
                            "awaiting_client_approval",
                            "folder_not_found",
                            "provider_unavailable",
                            "idempotency_key_reused",
                            "idempotency_key_in_progress",
                            "internal_error",
                            "team_closed",
                            "mcp_disabled_for_team",
                            "account_not_approved",
                            "membership_lost",
                            "ownership_lost",
                            "role_lost",
                            "plan_lapsed",
                            "unauthorized",
                            "rest_disabled",
                            "endpoint_not_found",
                            "method_not_allowed"
                        ]
                    },
                    {
                        "type": "string"
                    }
                ]
            }
        },
        "x-metricpeek-errors": [
            {
                "code": "spend_not_allowed",
                "status": 403,
                "title": "This connection is not allowed to spend the team's credits."
            },
            {
                "code": "publish_not_allowed",
                "status": 403,
                "title": "This connection is not allowed to publish."
            },
            {
                "code": "read_not_allowed",
                "status": 403,
                "title": "This connection is not allowed to read this team's data."
            },
            {
                "code": "write_not_allowed",
                "status": 403,
                "title": "This connection is not allowed to create or change anything in this team."
            },
            {
                "code": "daily_credit_limit_reached",
                "status": 429,
                "title": "The daily credit limit for this connection or team has been reached. It resets at local midnight in the team's timezone."
            },
            {
                "code": "daily_call_limit_reached",
                "status": 429,
                "title": "The daily limit of billable calls for this team has been reached. It resets at local midnight in the team's timezone."
            },
            {
                "code": "plan_limit_reached",
                "status": 403,
                "title": "The team's current plan does not include this."
            },
            {
                "code": "insufficient_credits",
                "status": 402,
                "title": "The team does not have enough credits for this action."
            },
            {
                "code": "preflight_blocked",
                "status": 422,
                "title": "The post did not pass validation and was not sent anywhere."
            },
            {
                "code": "not_found",
                "status": 404,
                "title": "No such item is available to this connection."
            },
            {
                "code": "validation_failed",
                "status": 422,
                "title": "The arguments did not match what this tool accepts."
            },
            {
                "code": "connection_suspended",
                "status": 403,
                "title": "This connection is suspended and cannot act right now."
            },
            {
                "code": "rate_limited",
                "status": 429,
                "title": "Too many calls from this connection in the last minute. Retry after the number of seconds in retry_after_seconds."
            },
            {
                "code": "awaiting_client_approval",
                "status": 403,
                "title": "This post needs the client's approval before it can be published."
            },
            {
                "code": "folder_not_found",
                "status": 404,
                "title": "No such folder is available to this connection."
            },
            {
                "code": "provider_unavailable",
                "status": 502,
                "title": "The provider did not confirm the post. Nothing is known to have been published; check the post before publishing it again."
            },
            {
                "code": "idempotency_key_reused",
                "status": 422,
                "title": "This idempotency_key was already used from this connection in the last 24 hours for a call with different arguments. Nothing was done. Use a new key for a different request."
            },
            {
                "code": "idempotency_key_in_progress",
                "status": 409,
                "title": "A call with this idempotency_key is still running. Nothing was started twice. Retry in a few seconds to get its result."
            },
            {
                "code": "internal_error",
                "status": 500,
                "title": "The call failed on the server. Part of it may already have taken effect, so check its result before repeating it."
            },
            {
                "code": "team_closed",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "mcp_disabled_for_team",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "account_not_approved",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "membership_lost",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "ownership_lost",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "role_lost",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "plan_lapsed",
                "status": 403,
                "title": "This API key may not act for its team right now."
            },
            {
                "code": "unauthorized",
                "status": 401,
                "title": "The request is not authenticated."
            },
            {
                "code": "rest_disabled",
                "status": 403,
                "title": "The REST API is switched off."
            },
            {
                "code": "endpoint_not_found",
                "status": 404,
                "title": "No such endpoint."
            },
            {
                "code": "method_not_allowed",
                "status": 405,
                "title": "Method not allowed."
            }
        ]
    }
}
