{
  "openapi": "3.1.0",
  "info": {
    "title": "SocialKit API",
    "version": "0.1.0",
    "summary": "The social primitive your agent calls.",
    "description": "SocialKit is the agent-native API for understanding and acting on social. It knows how each platform's feed ranks content and exposes that as primitives an agent or a developer can call: score, rewrite, plan, generate, publish.\n\nThis spec is the single source of truth for the REST surface. The MCP server at mcp.socialkit.sh wraps the same capability core, and the SDKs are generated from this document. Every example here is executed in CI; if it is in the docs, it runs.\n",
    "contact": {
      "name": "SocialKit",
      "email": "hello@socialkit.sh",
      "url": "https://socialkit.sh"
    }
  },
  "servers": [
    {
      "url": "https://api.socialkit.sh",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Intelligence",
      "description": "Score, rewrite, plan, generate. The knowledge primitives."
    },
    {
      "name": "Account",
      "description": "Sign up and manage API keys."
    },
    {
      "name": "Memory",
      "description": "Brands and voices. Reusable context that conditions generation: a brand is workspace context, a voice is a writing profile. Pass their ids to generate and plan.\n"
    },
    {
      "name": "Channels",
      "description": "Connected social accounts (OAuth pipes). Connect via the platform's OAuth flow, report health, refresh tokens, and disconnect. Tokens are encrypted at rest and never returned. LinkedIn is the only platform live today.\n"
    },
    {
      "name": "Media",
      "description": "Uploaded images, video, and PDF carousels referenced by posts at publish time. Bytes are stored in object storage; the API returns metadata and a content download endpoint.\n"
    },
    {
      "name": "Posts",
      "description": "The content lifecycle: draft, schedule, publish. Posts move through a strict state machine (draft -> scheduled/publishing -> published/failed). Publishing is exactly-once: a replayed or concurrent publish returns 409.\n"
    },
    {
      "name": "Scheduling",
      "description": "Bulk-schedule a week of posts and read the cross-channel calendar of scheduled and published content. Due posts are published by a per-minute cron through the same exactly-once path as publish-now.\n"
    },
    {
      "name": "Webhooks",
      "description": "Signed event callbacks (post.published, post.failed, post.scored, channel.disconnected). The signing secret is returned once at creation. Deliveries are retried with backoff; inspect them per endpoint.\n"
    },
    {
      "name": "Metrics",
      "description": "Engagement for published posts. Each poll appends a time-series snapshot; a per-minute cron sweeps the least-recently-polled posts. Roll snapshots up into account analytics, and correlate predicted scores with measured engagement in the calibration dataset.\n"
    }
  ],
  "paths": {
    "/v1/score": {
      "post": {
        "operationId": "scorePost",
        "summary": "Score a post against the platform's ranking model",
        "description": "Grades a draft against the platform's feed-ranking behavior and returns an overall score (0-100), a per-dimension breakdown, ranked signals (what helps, what leaks reach), the detected hook archetype, and the format multiplier. LinkedIn is the only platform live today.\n",
        "tags": [
          "Intelligence"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScoreRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal text-only post",
                  "value": {
                    "post": "Most onboarding flows fail at step one. We cut ours from 11 fields to 3 and activation jumped 40% in a week."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Score computed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScoreResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the rate-limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rewrite": {
      "post": {
        "operationId": "rewritePost",
        "summary": "Rewrite a post to score higher, with an honest before/after",
        "description": "Scores the original, rewrites it to lift the weak dimensions without inventing facts, then re-grades the rewrite. Returns both scores and the list of changes made. The after-score is reported as measured; it is never massaged to look like an improvement.\n",
        "tags": [
          "Intelligence"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScoreRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Rewrite a weak draft",
                  "value": {
                    "post": "We are excited to announce that our team has been working hard on some new features we think you will really love."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rewrite produced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RewriteResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/v1/generate": {
      "post": {
        "operationId": "generatePost",
        "summary": "Generate auto-scored post drafts from a brief",
        "description": "Drafts up to three native LinkedIn posts from a brief, scores each, and returns them sorted by overall score (best first).\n",
        "tags": [
          "Intelligence"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "One draft from a topic",
                  "value": {
                    "brief": "A shipping receipt about cutting onboarding to 3 fields",
                    "count": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Candidates generated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerateResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/v1/plan": {
      "post": {
        "operationId": "planContent",
        "summary": "Produce a content calendar of post ideas",
        "description": "Returns a calendar of post ideas spread across content pillars and hook archetypes. Each item is an outline (hook, angle, format, archetype), not a finished draft. Use generate to expand an item into a post.\n",
        "tags": [
          "Intelligence"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "A five-slot week",
                  "value": {
                    "brief": "AI-native engineering and build-in-public receipts",
                    "count": 5,
                    "cadence": "weekdays"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plan produced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/v1/account": {
      "post": {
        "operationId": "createAccount",
        "summary": "Create an account and its first API key",
        "description": "Public endpoint. Creates an account for an email and returns the first API key in plaintext. The plaintext is shown exactly once; store it now. Rate limited per IP.\n",
        "tags": [
          "Account"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAccountRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateAccountResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "An account already exists for that email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "listKeys",
        "summary": "List the account's API keys",
        "description": "Returns key metadata only. Secrets are never returned.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Keys listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createKey",
        "summary": "Issue a new API key",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateKeyResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/keys/{id}/rotate": {
      "post": {
        "operationId": "rotateKey",
        "summary": "Revoke a key and issue a replacement",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "201": {
            "description": "Key rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateKeyResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/keys/{id}": {
      "delete": {
        "operationId": "revokeKey",
        "summary": "Revoke an API key",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/brands": {
      "get": {
        "operationId": "listBrands",
        "summary": "List the account's brands",
        "tags": [
          "Memory"
        ],
        "responses": {
          "200": {
            "description": "Brands listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createBrand",
        "summary": "Create a brand",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandCreateRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "A minimal brand",
                  "value": {
                    "name": "SocialKit",
                    "description": "The social primitive your agent calls.",
                    "audience": "Founders and developers shipping in public.",
                    "themes": [
                      "shipping receipts",
                      "AI-native engineering"
                    ],
                    "linkPolicy": "Never link out in the body."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Brand created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/brands/{id}": {
      "get": {
        "operationId": "getBrand",
        "summary": "Fetch a brand",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BrandId"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateBrand",
        "summary": "Update a brand",
        "description": "Partial update. Only the fields present in the body are changed.",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BrandId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Brand updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteBrand",
        "summary": "Delete a brand",
        "description": "Deletes the brand. Voices attached to it are kept but detached (their brandId becomes null).\n",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BrandId"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/voices": {
      "get": {
        "operationId": "listVoices",
        "summary": "List the account's voices",
        "tags": [
          "Memory"
        ],
        "responses": {
          "200": {
            "description": "Voices listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createVoice",
        "summary": "Create a voice",
        "description": "Creates a voice. Supply `samples` and the profile is distilled from them (the common path, same as POST /v1/voices/build). Omit `samples` and the explicit `traits`/`doNot`/`exemplars` you pass are stored as-is.\n",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceCreateRequest"
              },
              "examples": {
                "distill": {
                  "summary": "Distil from sample posts (recommended)",
                  "value": {
                    "name": "Founder voice",
                    "samples": [
                      "We shipped the new scorer today. 11 dimensions down to 6.",
                      "Paused a $675/day campaign. The CAC math stopped working."
                    ]
                  }
                },
                "explicit": {
                  "summary": "A hand-authored voice",
                  "value": {
                    "name": "Founder voice",
                    "traits": [
                      "Open mid-scene with a concrete detail",
                      "Short sentences"
                    ],
                    "doNot": [
                      "Never opens with a question"
                    ],
                    "exemplars": [
                      "We paused the campaign. The math stopped working."
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/voices/build": {
      "post": {
        "operationId": "buildVoice",
        "summary": "Distil a voice profile from sample posts and save it",
        "description": "Reads sample posts, extracts the traits to imitate, the things to never do, and a few exemplar snippets, then saves the result as a voice. Returns the saved voice; pass its id to generate or plan.\n",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BuildVoiceRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Build from two samples",
                  "value": {
                    "name": "Founder voice",
                    "samples": [
                      "We shipped the new scorer today. 11 dimensions down to 6.",
                      "Paused a $675/day campaign. The CAC math stopped working."
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Voice built and saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/v1/voices/{id}": {
      "get": {
        "operationId": "getVoice",
        "summary": "Fetch a voice",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateVoice",
        "summary": "Update a voice",
        "description": "Partial update. Only the fields present in the body are changed.",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VoiceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voice updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteVoice",
        "summary": "Delete a voice",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/VoiceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "List the account's connected channels",
        "description": "Returns channel metadata only. Tokens are never included.",
        "tags": [
          "Channels"
        ],
        "responses": {
          "200": {
            "description": "Channels listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/channels/linkedin/connect": {
      "post": {
        "operationId": "connectLinkedIn",
        "summary": "Begin the LinkedIn OAuth connect",
        "description": "Starts the OAuth handshake. Returns an authorization URL to redirect the member to, plus the opaque state token that ties the callback back to this account. PKCE is handled for you. Disabled (503) when the deployment has no token-vault key or LinkedIn app credentials.\n",
        "tags": [
          "Channels"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectChannelRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Connect with no brand scoping",
                  "value": {}
                },
                "scoped": {
                  "summary": "Attach the channel to a brand",
                  "value": {
                    "brandId": "brd_example"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authorization URL minted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectChannelResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        }
      }
    },
    "/v1/channels/linkedin/callback": {
      "get": {
        "operationId": "linkedinCallback",
        "summary": "OAuth redirect target (public)",
        "description": "Where LinkedIn redirects after the member authorizes. Public: trust comes from the single-use, account-bound state, not the query params. Exchanges the code for tokens, seals them into the vault, and returns the connected channel. Not called directly by API clients.\n",
        "tags": [
          "Channels"
        ],
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "description": "The authorization code from LinkedIn.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "description": "The opaque state token returned by connect.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Channel connected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or expired OAuth state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The token exchange with the platform failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        }
      }
    },
    "/v1/channels/{id}": {
      "get": {
        "operationId": "getChannel",
        "summary": "Fetch a channel",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "disconnectChannel",
        "summary": "Disconnect a channel",
        "description": "Wipes the stored tokens (unrecoverable) and marks the channel revoked. The row is kept for history.\n",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel disconnected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/channels/{id}/refresh": {
      "post": {
        "operationId": "refreshChannel",
        "summary": "Refresh a channel's access token",
        "description": "Refreshes the access token if it is at or near expiry; a no-op when the token is still fresh. Returns the current channel either way.\n",
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel refreshed (or already fresh)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "description": "The token refresh with the platform failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        }
      }
    },
    "/v1/media": {
      "get": {
        "operationId": "listMedia",
        "summary": "List the account's uploaded media",
        "description": "Results are paginated newest-first; page with limit and offset and follow pagination.hasMore.\n",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum media items to return (default 50, max 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of media items to skip from the newest.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Media listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaListResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "uploadMedia",
        "summary": "Upload a media file",
        "description": "Upload raw bytes with the file's Content-Type header (validated against an allowlist). Optionally scope to a brand with the brandId query param and supply a filename with the X-Filename header. Returns the stored media metadata.\n",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "name": "brandId",
            "in": "query",
            "required": false,
            "description": "Optional brand to attach the media to. Must belong to the account.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Filename",
            "in": "header",
            "required": false,
            "description": "Optional original filename, reduced to a basename.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The raw file bytes. The Content-Type header declares the type.",
          "content": {
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/gif": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Media uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "The file exceeds the size limit for its type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media/{id}": {
      "get": {
        "operationId": "getMedia",
        "summary": "Fetch media metadata",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Media found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteMedia",
        "summary": "Delete media",
        "description": "Removes the metadata row and the stored object.",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Media deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/media/{id}/content": {
      "get": {
        "operationId": "getMediaContent",
        "summary": "Download the media bytes",
        "tags": [
          "Media"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "The raw file bytes, with the original Content-Type.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List the account's posts",
        "description": "Optionally filter by status. Results are paginated newest-first; page with limit and offset and follow pagination.hasMore. Posts carry no token material.\n",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter to one post status.",
            "schema": {
              "$ref": "#/components/schemas/PostStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum posts to return (default 50, max 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of posts to skip from the newest.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posts listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostListResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createPost",
        "summary": "Create a draft post",
        "description": "Creates a post in the draft state. Supply the text plus optional brand, channel, and media references. Any brandId or channelId must belong to the calling account. Pass an Idempotency-Key header to make a retry of this call safe (a replay returns the original post, not a duplicate).\n",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePostRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "A text-only draft",
                  "value": {
                    "text": "We cut onboarding from 11 fields to 3. Activation up 40%."
                  }
                },
                "targeted": {
                  "summary": "Draft bound to a channel",
                  "value": {
                    "text": "Shipping receipt for the week.",
                    "channelId": "chn_example",
                    "platform": "linkedin"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created",
            "headers": {
              "idempotent-replay": {
                "description": "Present and set to `true` only when this response is a cached replay of an earlier request with the same Idempotency-Key.\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/posts/bulk-schedule": {
      "post": {
        "operationId": "bulkSchedulePosts",
        "summary": "Create and schedule many posts at once",
        "description": "Creates a batch of posts and schedules each for its runAt time in a single call (the /plan week made concrete). Each item is validated independently; any bad reference fails the whole batch. Pass an Idempotency-Key header to make a retry of the whole batch safe.\n",
        "tags": [
          "Scheduling"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkScheduleRequest"
              },
              "examples": {
                "week": {
                  "summary": "A two-day plan",
                  "value": {
                    "items": [
                      {
                        "text": "Day one shipping receipt.",
                        "channelId": "chn_example",
                        "runAt": 1893456000000
                      },
                      {
                        "text": "Day two contrarian take.",
                        "channelId": "chn_example",
                        "runAt": 1893542400000
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Posts created and scheduled",
            "headers": {
              "idempotent-replay": {
                "description": "Present and set to `true` only when this response is a cached replay of an earlier request with the same Idempotency-Key.\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostListResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/posts/{id}": {
      "get": {
        "operationId": "getPost",
        "summary": "Fetch a post",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Post found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "summary": "Edit a draft post",
        "description": "Partial update. Only draft posts can be edited; editing a post in any other state returns 409. Omit a field to leave it unchanged.\n",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Delete a post",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Post deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts/{id}/publish": {
      "post": {
        "operationId": "publishPost",
        "summary": "Publish a post now",
        "description": "Publishes immediately through the exactly-once path. A replayed or concurrent call returns 409 not_claimable. A revoked channel leaves the post failed and returns 409 channel_revoked with the failed post embedded. A platform error returns 502 publish_failed.\n",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Post published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not in a publishable state (not_claimable) or the channel is revoked (channel_revoked). The terminal post is embedded alongside the error.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostErrorResult"
                }
              }
            }
          },
          "502": {
            "description": "The platform rejected the publish (publish_failed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostErrorResult"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        }
      }
    },
    "/v1/posts/{id}/schedule": {
      "post": {
        "operationId": "schedulePost",
        "summary": "Schedule (or re-schedule) a post",
        "description": "Sets the post to scheduled for runAt and enqueues a single pending job. Re-scheduling cancels the prior pending job. The per-minute cron publishes due posts through the exactly-once path.\n",
        "tags": [
          "Scheduling"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SchedulePostRequest"
              },
              "examples": {
                "soon": {
                  "summary": "Schedule for a fixed time",
                  "value": {
                    "runAt": 1893456000000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/posts/{id}/cancel": {
      "post": {
        "operationId": "cancelPost",
        "summary": "Cancel a scheduled or draft post",
        "description": "Moves the post to canceled and cancels its pending job. A post in a terminal state (published, failed) cannot be canceled and returns 409.\n",
        "tags": [
          "Scheduling"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Post canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/posts/{id}/score": {
      "post": {
        "operationId": "scoreStoredPost",
        "summary": "Score a stored post and record the result",
        "description": "Scores the stored post's text and persists the score against the post, building the score-vs-reality dataset. Distinct from POST /v1/score, which grades arbitrary text without saving.\n",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Post scored and recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoredScoreResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/v1/posts/{id}/scores": {
      "get": {
        "operationId": "listPostScores",
        "summary": "List recorded scores for a post",
        "tags": [
          "Posts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Scores listed, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostScoreListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts/{id}/metrics": {
      "get": {
        "operationId": "getPostMetrics",
        "summary": "Read a post's latest engagement and full history",
        "description": "Returns the latest engagement snapshot plus the full time-series history (newest first) for a published post. Counters are null when the platform does not expose that dimension for the post type.\n",
        "tags": [
          "Metrics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest snapshot and history",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostMetricsResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/posts/{id}/metrics/refresh": {
      "post": {
        "operationId": "refreshPostMetrics",
        "summary": "Poll the platform now for fresh engagement",
        "description": "Polls the platform for the post's current engagement and stores a new snapshot. Mirrors the per-minute cron sweep for a single post. A draft or unpublished post returns 409; a revoked channel returns 409.\n",
        "tags": [
          "Metrics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PostId"
          }
        ],
        "responses": {
          "200": {
            "description": "A fresh snapshot was stored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetricRefreshResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        }
      }
    },
    "/v1/calendar": {
      "get": {
        "operationId": "getCalendar",
        "summary": "Read the cross-channel content calendar",
        "description": "Returns scheduled and published posts within an optional time window, ordered chronologically. from/to are unix-ms timestamps.\n",
        "tags": [
          "Scheduling"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start of the window, unix epoch milliseconds.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End of the window, unix epoch milliseconds.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar entries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/analytics": {
      "get": {
        "operationId": "getAnalytics",
        "summary": "Account-wide engagement rollup",
        "description": "Aggregates the latest snapshot of every published post into account totals and a per-platform breakdown. Optional from/to filter on the post's published time (unix-ms).\n",
        "tags": [
          "Metrics"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start of the window, unix epoch milliseconds.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End of the window, unix epoch milliseconds.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregated analytics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/calibration": {
      "get": {
        "operationId": "getCalibration",
        "summary": "Predicted score vs measured engagement",
        "description": "The calibration dataset: each published post that has both a recorded score and a metric snapshot, pairing the predicted score with the measured engagement. Posts missing either are omitted.\n",
        "tags": [
          "Metrics"
        ],
        "responses": {
          "200": {
            "description": "Calibration rows, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalibrationResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "description": "Returns the account's endpoints (secrets never included) plus the set of events you can subscribe to.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Webhooks listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Register a webhook endpoint",
        "description": "Registers a signed callback URL. The signing secret is returned once in this response and never again; store it now. Omit events to subscribe to all of them.\n",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              },
              "examples": {
                "all": {
                  "summary": "Subscribe to every event",
                  "value": {
                    "url": "https://example.com/hooks/socialkit"
                  }
                },
                "filtered": {
                  "summary": "Only publish/fail events",
                  "value": {
                    "url": "https://example.com/hooks/socialkit",
                    "events": [
                      "post.published",
                      "post.failed"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created; secret returned once",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateWebhookResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Fetch a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List delivery attempts for a webhook",
        "description": "Returns delivery attempts with status and retry state, newest-first. Results are paginated; page with limit and offset and follow pagination.hasMore.\n",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum deliveries to return (default 50, max 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of deliveries to skip from the newest.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries listed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryListResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "KeyId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The API key id (e.g. `key_...`).",
        "schema": {
          "type": "string"
        }
      },
      "BrandId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The brand id (e.g. `brd_...`).",
        "schema": {
          "type": "string"
        }
      },
      "VoiceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The voice id (e.g. `voi_...`).",
        "schema": {
          "type": "string"
        }
      },
      "ChannelId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The channel id (e.g. `chn_...`).",
        "schema": {
          "type": "string"
        }
      },
      "MediaId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The media id (e.g. `med_...`).",
        "schema": {
          "type": "string"
        }
      },
      "PostId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The post id (e.g. `pst_...`).",
        "schema": {
          "type": "string"
        }
      },
      "WebhookId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The webhook endpoint id (e.g. `whk_...`).",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A client-chosen key (max 255 chars) that makes a creation safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and creates nothing new. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.\n",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request body",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found for this account",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.\n",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the rate-limit window resets",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UpstreamError": {
        "description": "The model or gateway misbehaved; safe to retry",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotConfigured": {
        "description": "The capability is not configured on this deployment",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "SocialKit API key, sent as `Authorization: Bearer sk_...`. Create and rotate keys from the dashboard.\n"
      }
    },
    "schemas": {
      "Platform": {
        "type": "string",
        "description": "Target platform. Only `linkedin` is live; others are planned.",
        "enum": [
          "linkedin"
        ]
      },
      "MediaKind": {
        "type": "string",
        "description": "The media attached to the post. Drives the format multiplier.",
        "enum": [
          "none",
          "single-image",
          "multi-image",
          "document",
          "video",
          "poll",
          "link"
        ]
      },
      "FollowerBand": {
        "type": "string",
        "description": "Author follower bucket. Optional context for calibration.",
        "enum": [
          "under-500",
          "500-2k",
          "2k-10k",
          "10k-50k",
          "50k-plus"
        ]
      },
      "Dimension": {
        "type": "string",
        "description": "A scored dimension of the post.",
        "enum": [
          "hook",
          "algorithmFit",
          "specificity",
          "structure",
          "voice",
          "engagement"
        ]
      },
      "HookArchetype": {
        "type": "string",
        "description": "The classified shape of the post's opening hook.",
        "enum": [
          "contrarian",
          "statement",
          "question",
          "pure-number",
          "story",
          "other"
        ]
      },
      "ScoreRequest": {
        "type": "object",
        "required": [
          "post"
        ],
        "additionalProperties": false,
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "post": {
            "type": "string",
            "minLength": 1,
            "maxLength": 3000,
            "description": "The full post text to grade."
          },
          "authorName": {
            "type": "string",
            "description": "Optional author name for context."
          },
          "authorHeadline": {
            "type": "string",
            "description": "Optional author headline/title for context."
          },
          "followerBand": {
            "$ref": "#/components/schemas/FollowerBand"
          },
          "media": {
            "$ref": "#/components/schemas/MediaKind"
          },
          "hasExternalLink": {
            "type": "boolean",
            "description": "Whether the post body or its preview points off-platform. External links cap algorithm fit. Derived from the post text when omitted.\n"
          },
          "hashtagCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of hashtags. Six or more caps algorithm fit. Derived from the post text when omitted.\n"
          },
          "dayOfWeek": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "Planned publish day, Sunday=0. Optional."
          },
          "hourLocal": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23,
            "description": "Planned publish hour in the author's local time. Optional."
          }
        }
      },
      "ScoreBreakdown": {
        "type": "object",
        "description": "Per-dimension scores, each 0-100.",
        "required": [
          "hook",
          "algorithmFit",
          "specificity",
          "structure",
          "voice",
          "engagement"
        ],
        "additionalProperties": false,
        "properties": {
          "hook": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "algorithmFit": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "specificity": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "structure": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "voice": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "engagement": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          }
        }
      },
      "Signal": {
        "type": "object",
        "description": "One ranked observation about the post.",
        "required": [
          "dimension",
          "impact",
          "weight",
          "label",
          "detail"
        ],
        "properties": {
          "dimension": {
            "$ref": "#/components/schemas/Dimension"
          },
          "impact": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ]
          },
          "weight": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "description": "How much this signal moved the score (1-5)."
          },
          "label": {
            "type": "string",
            "description": "Short headline a human reads at a glance."
          },
          "detail": {
            "type": "string",
            "description": "One sentence on the specific move in the post."
          },
          "rule": {
            "type": "string",
            "description": "The named algorithm rule this ties to, if any."
          }
        }
      },
      "ScoreResult": {
        "type": "object",
        "required": [
          "schema",
          "overall",
          "breakdown",
          "verdict",
          "signals",
          "hookArchetype",
          "hookArchetypeNote",
          "formatMultiplier",
          "formatNote"
        ],
        "properties": {
          "schema": {
            "type": "string",
            "description": "Score schema version.",
            "example": "v0.3"
          },
          "overall": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Weighted overall score."
          },
          "breakdown": {
            "$ref": "#/components/schemas/ScoreBreakdown"
          },
          "verdict": {
            "type": "string",
            "description": "One-line plain-language verdict."
          },
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signal"
            }
          },
          "hookArchetype": {
            "$ref": "#/components/schemas/HookArchetype"
          },
          "hookArchetypeNote": {
            "type": "string",
            "description": "Benchmark note for the detected hook archetype."
          },
          "formatMultiplier": {
            "type": "number",
            "description": "Reach multiplier implied by the chosen media format."
          },
          "formatNote": {
            "type": "string",
            "description": "Why the format multiplier is what it is."
          }
        }
      },
      "RewriteChange": {
        "type": "object",
        "description": "One edit the rewrite made, tied to a scored dimension.",
        "required": [
          "dimension",
          "note"
        ],
        "properties": {
          "dimension": {
            "$ref": "#/components/schemas/Dimension"
          },
          "note": {
            "type": "string",
            "description": "One sentence on what changed and why."
          }
        }
      },
      "RewriteResult": {
        "type": "object",
        "description": "The rewrite plus the before/after scores. The after-score is reported as measured and is never massaged to look like an improvement.\n",
        "required": [
          "rewrite",
          "changes",
          "before",
          "after"
        ],
        "properties": {
          "rewrite": {
            "type": "string",
            "description": "The rewritten post."
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RewriteChange"
            }
          },
          "before": {
            "$ref": "#/components/schemas/ScoreResult"
          },
          "after": {
            "$ref": "#/components/schemas/ScoreResult"
          }
        }
      },
      "GenerateRequest": {
        "type": "object",
        "required": [
          "brief"
        ],
        "additionalProperties": false,
        "properties": {
          "brief": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "What the post should be about."
          },
          "authorName": {
            "type": "string",
            "description": "Optional author name for voice context."
          },
          "authorHeadline": {
            "type": "string",
            "description": "Optional author headline/title for context."
          },
          "followerBand": {
            "$ref": "#/components/schemas/FollowerBand"
          },
          "media": {
            "$ref": "#/components/schemas/MediaKind"
          },
          "archetype": {
            "$ref": "#/components/schemas/HookArchetype"
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3,
            "description": "How many drafts to produce (1-3, default 1)."
          },
          "brandId": {
            "type": "string",
            "description": "Optional brand id whose context (business, audience, themes, link policy) conditions the drafts. Must belong to the calling account.\n"
          },
          "voiceId": {
            "type": "string",
            "description": "Optional voice id whose traits and exemplars the drafts imitate. Must belong to the calling account.\n"
          }
        }
      },
      "GeneratedCandidate": {
        "type": "object",
        "required": [
          "post",
          "score"
        ],
        "properties": {
          "post": {
            "type": "string",
            "description": "The generated post text."
          },
          "score": {
            "$ref": "#/components/schemas/ScoreResult"
          }
        }
      },
      "GenerateResult": {
        "type": "object",
        "required": [
          "candidates"
        ],
        "properties": {
          "candidates": {
            "type": "array",
            "description": "Drafts sorted by overall score, best first.",
            "items": {
              "$ref": "#/components/schemas/GeneratedCandidate"
            }
          }
        }
      },
      "Cadence": {
        "type": "string",
        "description": "Posting rhythm hint for the calendar.",
        "enum": [
          "daily",
          "weekdays",
          "3x-week"
        ]
      },
      "PlanRequest": {
        "type": "object",
        "required": [
          "brief"
        ],
        "additionalProperties": false,
        "properties": {
          "brief": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Themes, goals, or topics the calendar should cover."
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 14,
            "description": "Number of calendar slots (1-14, default 5)."
          },
          "authorName": {
            "type": "string",
            "description": "Optional author name for context."
          },
          "authorHeadline": {
            "type": "string",
            "description": "Optional author headline/title for context."
          },
          "followerBand": {
            "$ref": "#/components/schemas/FollowerBand"
          },
          "cadence": {
            "$ref": "#/components/schemas/Cadence"
          },
          "brandId": {
            "type": "string",
            "description": "Optional brand id whose context conditions the plan. Must belong to the calling account.\n"
          },
          "voiceId": {
            "type": "string",
            "description": "Optional voice id whose style conditions the plan. Must belong to the calling account.\n"
          }
        }
      },
      "PlanItem": {
        "type": "object",
        "required": [
          "slot",
          "pillar",
          "hook",
          "angle",
          "format",
          "archetype"
        ],
        "properties": {
          "slot": {
            "type": "integer",
            "minimum": 1,
            "description": "1-based ordinal in the plan."
          },
          "pillar": {
            "type": "string",
            "description": "The content pillar this idea serves."
          },
          "hook": {
            "type": "string",
            "description": "The opening line to write."
          },
          "angle": {
            "type": "string",
            "description": "One sentence on the take or argument."
          },
          "format": {
            "$ref": "#/components/schemas/MediaKind"
          },
          "archetype": {
            "$ref": "#/components/schemas/HookArchetype"
          }
        }
      },
      "PlanResult": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanItem"
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "id",
          "email",
          "plan"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Account id (e.g. `acc_...`)."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "plan": {
            "type": "string",
            "description": "Billing plan. New accounts start on `free`."
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "description": "API key metadata. The secret is never included.",
        "required": [
          "id",
          "name",
          "keyPrefix",
          "createdAt",
          "lastUsedAt",
          "revokedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Key id (e.g. `key_...`)."
          },
          "name": {
            "type": "string",
            "description": "Human-readable label for the key."
          },
          "keyPrefix": {
            "type": "string",
            "description": "The first characters of the secret, for display."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "lastUsedAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last successful auth, epoch milliseconds, or null."
          },
          "revokedAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Revocation time, epoch milliseconds, or null if live."
          }
        }
      },
      "CreateAccountRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200
          },
          "name": {
            "type": "string",
            "description": "Optional label for the first key. Defaults to `default`."
          }
        }
      },
      "CreateAccountResult": {
        "type": "object",
        "required": [
          "account",
          "key",
          "apiKey"
        ],
        "properties": {
          "account": {
            "$ref": "#/components/schemas/Account"
          },
          "key": {
            "$ref": "#/components/schemas/ApiKey"
          },
          "apiKey": {
            "type": "string",
            "description": "The plaintext key, shown exactly once. Store it now; it cannot be retrieved again.\n"
          }
        }
      },
      "CreateKeyRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "description": "Optional label for the key. Defaults to `default`."
          }
        }
      },
      "CreateKeyResult": {
        "type": "object",
        "required": [
          "key",
          "apiKey"
        ],
        "properties": {
          "key": {
            "$ref": "#/components/schemas/ApiKey"
          },
          "apiKey": {
            "type": "string",
            "description": "The plaintext key, shown exactly once."
          }
        }
      },
      "KeyListResult": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          }
        }
      },
      "RevokeResult": {
        "type": "object",
        "required": [
          "revoked"
        ],
        "properties": {
          "revoked": {
            "type": "boolean",
            "description": "True when the key was revoked."
          }
        }
      },
      "Brand": {
        "type": "object",
        "description": "Workspace context that conditions generation.",
        "required": [
          "id",
          "name",
          "description",
          "audience",
          "themes",
          "linkPolicy",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Brand id (e.g. `brd_...`)."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who the business is."
          },
          "audience": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who the brand talks to."
          },
          "themes": {
            "type": "array",
            "description": "Recurring themes the brand posts about.",
            "items": {
              "type": "string"
            }
          },
          "linkPolicy": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text policy on outbound links."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last update time, epoch milliseconds."
          }
        }
      },
      "BrandCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "audience": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "themes": {
            "type": "array",
            "maxItems": 25,
            "items": {
              "type": "string"
            }
          },
          "linkPolicy": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300
          }
        }
      },
      "BrandUpdateRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Partial update; omit a field to leave it unchanged.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "audience": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "themes": {
            "type": "array",
            "maxItems": 25,
            "items": {
              "type": "string"
            }
          },
          "linkPolicy": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300
          }
        }
      },
      "BrandResult": {
        "type": "object",
        "required": [
          "brand"
        ],
        "properties": {
          "brand": {
            "$ref": "#/components/schemas/Brand"
          }
        }
      },
      "BrandListResult": {
        "type": "object",
        "required": [
          "brands"
        ],
        "properties": {
          "brands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Brand"
            }
          }
        }
      },
      "Voice": {
        "type": "object",
        "description": "A reusable writing profile distilled from sample posts.",
        "required": [
          "id",
          "brandId",
          "name",
          "traits",
          "doNot",
          "exemplars",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Voice id (e.g. `voi_...`)."
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The brand this voice belongs to, or null."
          },
          "name": {
            "type": "string"
          },
          "traits": {
            "type": "array",
            "description": "Stylistic traits to imitate.",
            "items": {
              "type": "string"
            }
          },
          "doNot": {
            "type": "array",
            "description": "Things this voice never does.",
            "items": {
              "type": "string"
            }
          },
          "exemplars": {
            "type": "array",
            "description": "Few-shot snippets that anchor the voice.",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last update time, epoch milliseconds."
          }
        }
      },
      "VoiceCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "brandId": {
            "type": "string",
            "description": "Optional brand to attach the voice to."
          },
          "samples": {
            "type": "array",
            "description": "1-12 sample posts in the target voice. When present, the profile is distilled from them and any traits/doNot/exemplars you also pass are ignored.\n",
            "minItems": 1,
            "maxItems": 12,
            "items": {
              "type": "string",
              "maxLength": 4000
            }
          },
          "traits": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          },
          "doNot": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          },
          "exemplars": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          }
        }
      },
      "VoiceUpdateRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Partial update; omit a field to leave it unchanged.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reattach to a brand, or null to detach."
          },
          "traits": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          },
          "doNot": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          },
          "exemplars": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BuildVoiceRequest": {
        "type": "object",
        "required": [
          "name",
          "samples"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "brandId": {
            "type": "string",
            "description": "Optional brand to attach the built voice to."
          },
          "samples": {
            "type": "array",
            "description": "1-12 sample posts written in the target voice.",
            "minItems": 1,
            "maxItems": 12,
            "items": {
              "type": "string",
              "maxLength": 4000
            }
          }
        }
      },
      "VoiceResult": {
        "type": "object",
        "required": [
          "voice"
        ],
        "properties": {
          "voice": {
            "$ref": "#/components/schemas/Voice"
          }
        }
      },
      "VoiceListResult": {
        "type": "object",
        "required": [
          "voices"
        ],
        "properties": {
          "voices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Voice"
            }
          }
        }
      },
      "DeleteResult": {
        "type": "object",
        "required": [
          "deleted"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "description": "True when the resource was deleted."
          }
        }
      },
      "ChannelStatus": {
        "type": "string",
        "description": "Connection health of the channel.",
        "enum": [
          "connected",
          "expired",
          "revoked",
          "error"
        ]
      },
      "Channel": {
        "type": "object",
        "description": "A connected social account. Carries no token material.",
        "required": [
          "id",
          "accountId",
          "brandId",
          "platform",
          "platformUserId",
          "handle",
          "status",
          "scopes",
          "expiresAt",
          "lastRefreshAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Channel id (e.g. `chn_...`)."
          },
          "accountId": {
            "type": "string",
            "description": "The owning account id."
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The brand this channel belongs to, or null."
          },
          "platform": {
            "type": "string",
            "description": "The platform, e.g. `linkedin`."
          },
          "platformUserId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The platform's stable user id for the connected member."
          },
          "handle": {
            "type": [
              "string",
              "null"
            ],
            "description": "A display handle or name for the connected account."
          },
          "status": {
            "$ref": "#/components/schemas/ChannelStatus"
          },
          "scopes": {
            "type": "array",
            "description": "Granted OAuth scopes.",
            "items": {
              "type": "string"
            }
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Access token expiry, epoch milliseconds, or null."
          },
          "lastRefreshAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last token refresh, epoch milliseconds, or null."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last update time, epoch milliseconds."
          }
        }
      },
      "ChannelResult": {
        "type": "object",
        "required": [
          "channel"
        ],
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        }
      },
      "ChannelListResult": {
        "type": "object",
        "required": [
          "channels"
        ],
        "properties": {
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Channel"
            }
          }
        }
      },
      "ConnectChannelRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "brandId": {
            "type": "string",
            "description": "Optional brand to scope the channel to. Must belong to the calling account.\n"
          }
        }
      },
      "ConnectChannelResult": {
        "type": "object",
        "required": [
          "url",
          "state"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "The platform authorization URL to redirect the member to."
          },
          "state": {
            "type": "string",
            "description": "The opaque, single-use state token tying the callback to this account. Returned for visibility; the callback validates it.\n"
          }
        }
      },
      "Media": {
        "type": "object",
        "description": "Metadata for an uploaded file. The storage key is never returned.",
        "required": [
          "id",
          "accountId",
          "brandId",
          "contentType",
          "size",
          "filename",
          "sha256",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Media id (e.g. `med_...`)."
          },
          "accountId": {
            "type": "string",
            "description": "The owning account id."
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The brand this media belongs to, or null."
          },
          "contentType": {
            "type": "string",
            "description": "The validated MIME type of the stored bytes."
          },
          "size": {
            "type": "integer",
            "description": "Size in bytes."
          },
          "filename": {
            "type": [
              "string",
              "null"
            ],
            "description": "The original filename (basename), or null."
          },
          "sha256": {
            "type": "string",
            "description": "SHA-256 of the bytes, hex-encoded."
          },
          "createdAt": {
            "type": "integer",
            "description": "Upload time, epoch milliseconds."
          }
        }
      },
      "MediaResult": {
        "type": "object",
        "required": [
          "media"
        ],
        "properties": {
          "media": {
            "$ref": "#/components/schemas/Media"
          }
        }
      },
      "MediaListResult": {
        "type": "object",
        "required": [
          "media",
          "pagination"
        ],
        "properties": {
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Media"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "hasMore"
            ],
            "properties": {
              "limit": {
                "type": "integer",
                "description": "The page size applied to this response."
              },
              "offset": {
                "type": "integer",
                "description": "The offset applied to this response."
              },
              "hasMore": {
                "type": "boolean",
                "description": "True when another page exists past this one."
              }
            }
          }
        }
      },
      "PostStatus": {
        "type": "string",
        "description": "Where the post sits in its lifecycle.",
        "enum": [
          "draft",
          "scheduled",
          "publishing",
          "published",
          "failed",
          "canceled"
        ]
      },
      "Post": {
        "type": "object",
        "description": "A unit of content. Carries no token material.",
        "required": [
          "id",
          "accountId",
          "brandId",
          "channelId",
          "platform",
          "status",
          "text",
          "mediaIds",
          "scheduledFor",
          "publishedAt",
          "platformPostId",
          "platformUrl",
          "error",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Post id (e.g. `pst_...`)."
          },
          "accountId": {
            "type": "string",
            "description": "The owning account id."
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The brand this post belongs to, or null."
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The channel this post publishes to, or null."
          },
          "platform": {
            "type": "string",
            "description": "The target platform, e.g. `linkedin`."
          },
          "status": {
            "$ref": "#/components/schemas/PostStatus"
          },
          "text": {
            "type": "string",
            "description": "The post body."
          },
          "mediaIds": {
            "type": "array",
            "description": "Ids of media attached to the post.",
            "items": {
              "type": "string"
            }
          },
          "scheduledFor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Scheduled publish time, epoch milliseconds, or null."
          },
          "publishedAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Actual publish time, epoch milliseconds, or null."
          },
          "platformPostId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The platform's id for the published post, or null."
          },
          "platformUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public URL of the published post, or null."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last publish error, or null."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last update time, epoch milliseconds."
          }
        }
      },
      "PostResult": {
        "type": "object",
        "required": [
          "post"
        ],
        "properties": {
          "post": {
            "$ref": "#/components/schemas/Post"
          }
        }
      },
      "PostListResult": {
        "type": "object",
        "required": [
          "posts",
          "pagination"
        ],
        "properties": {
          "posts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Post"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "hasMore"
            ],
            "properties": {
              "limit": {
                "type": "integer",
                "description": "The page size applied to this response."
              },
              "offset": {
                "type": "integer",
                "description": "The offset applied to this response."
              },
              "hasMore": {
                "type": "boolean",
                "description": "True when another page exists past this one."
              }
            }
          }
        }
      },
      "PostErrorResult": {
        "type": "object",
        "description": "An error with the affected post's terminal state embedded.",
        "required": [
          "error",
          "post"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "post": {
            "$ref": "#/components/schemas/Post"
          }
        }
      },
      "CreatePostRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8000,
            "description": "The post body."
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Channel to publish to. Must belong to the account."
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Brand to attach. Must belong to the account."
          },
          "platform": {
            "type": "string",
            "description": "Target platform. Defaults from the channel, else linkedin."
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdatePostRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Partial update; omit a field to leave it unchanged. Draft only.",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8000
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ]
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ]
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SchedulePostRequest": {
        "type": "object",
        "required": [
          "runAt"
        ],
        "additionalProperties": false,
        "properties": {
          "runAt": {
            "type": "integer",
            "description": "When to publish, epoch milliseconds. Must be in the future."
          }
        }
      },
      "BulkScheduleItem": {
        "type": "object",
        "required": [
          "text",
          "runAt"
        ],
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8000
          },
          "runAt": {
            "type": "integer",
            "description": "When to publish this post, epoch milliseconds."
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ]
          },
          "brandId": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "type": "string"
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BulkScheduleRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/BulkScheduleItem"
            }
          }
        }
      },
      "PostScore": {
        "type": "object",
        "description": "A score recorded against a stored post.",
        "required": [
          "id",
          "postId",
          "score",
          "verdict",
          "payload",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Score id (e.g. `psc_...`)."
          },
          "postId": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "The overall score, rounded."
          },
          "verdict": {
            "type": [
              "string",
              "null"
            ],
            "description": "One-line plain-language verdict, or null."
          },
          "payload": {
            "description": "The full ScoreResult captured at scoring time."
          },
          "createdAt": {
            "type": "integer",
            "description": "When the score was recorded, epoch milliseconds."
          }
        }
      },
      "StoredScoreResult": {
        "type": "object",
        "required": [
          "score",
          "result"
        ],
        "properties": {
          "score": {
            "$ref": "#/components/schemas/PostScore"
          },
          "result": {
            "$ref": "#/components/schemas/ScoreResult"
          }
        }
      },
      "PostScoreListResult": {
        "type": "object",
        "required": [
          "scores"
        ],
        "properties": {
          "scores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostScore"
            }
          }
        }
      },
      "CalendarEntry": {
        "type": "object",
        "required": [
          "postId",
          "status",
          "when",
          "channelId",
          "platform",
          "preview"
        ],
        "properties": {
          "postId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PostStatus"
          },
          "when": {
            "type": "integer",
            "description": "Scheduled or published time, epoch milliseconds."
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ]
          },
          "platform": {
            "type": "string"
          },
          "preview": {
            "type": "string",
            "description": "A short preview of the post text."
          }
        }
      },
      "CalendarResult": {
        "type": "object",
        "required": [
          "calendar"
        ],
        "properties": {
          "calendar": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalendarEntry"
            }
          }
        }
      },
      "MetricSnapshot": {
        "type": "object",
        "description": "One engagement snapshot for a post. Each counter is null when the platform did not report that dimension (distinct from a reported zero).\n",
        "required": [
          "id",
          "postId",
          "impressions",
          "likes",
          "comments",
          "shares",
          "clicks",
          "fetchedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Snapshot id (e.g. `met_...`)."
          },
          "postId": {
            "type": "string"
          },
          "impressions": {
            "type": [
              "integer",
              "null"
            ]
          },
          "likes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "comments": {
            "type": [
              "integer",
              "null"
            ]
          },
          "shares": {
            "type": [
              "integer",
              "null"
            ]
          },
          "clicks": {
            "type": [
              "integer",
              "null"
            ]
          },
          "fetchedAt": {
            "type": "integer",
            "description": "When this snapshot was captured, epoch milliseconds."
          }
        }
      },
      "PostMetricsResult": {
        "type": "object",
        "required": [
          "latest",
          "history"
        ],
        "properties": {
          "latest": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MetricSnapshot"
              },
              {
                "type": "null"
              }
            ],
            "description": "The most recent snapshot, or null if never polled."
          },
          "history": {
            "type": "array",
            "description": "All snapshots, newest first.",
            "items": {
              "$ref": "#/components/schemas/MetricSnapshot"
            }
          }
        }
      },
      "MetricRefreshResult": {
        "type": "object",
        "required": [
          "snapshot"
        ],
        "properties": {
          "snapshot": {
            "$ref": "#/components/schemas/MetricSnapshot"
          }
        }
      },
      "MetricTotals": {
        "type": "object",
        "required": [
          "posts",
          "impressions",
          "likes",
          "comments",
          "shares",
          "clicks",
          "engagements"
        ],
        "properties": {
          "posts": {
            "type": "integer",
            "description": "Posts counted (those with at least one snapshot)."
          },
          "impressions": {
            "type": "integer"
          },
          "likes": {
            "type": "integer"
          },
          "comments": {
            "type": "integer"
          },
          "shares": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "engagements": {
            "type": "integer",
            "description": "likes + comments + shares + clicks."
          }
        }
      },
      "PlatformTotals": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MetricTotals"
          },
          {
            "type": "object",
            "required": [
              "platform"
            ],
            "properties": {
              "platform": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Analytics": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MetricTotals"
          },
          {
            "type": "object",
            "required": [
              "avgEngagementsPerPost",
              "byPlatform"
            ],
            "properties": {
              "avgEngagementsPerPost": {
                "type": "number",
                "description": "Engagements per post, averaged. 0 with no posts."
              },
              "byPlatform": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PlatformTotals"
                }
              }
            }
          }
        ]
      },
      "AnalyticsResult": {
        "type": "object",
        "required": [
          "analytics"
        ],
        "properties": {
          "analytics": {
            "$ref": "#/components/schemas/Analytics"
          }
        }
      },
      "CalibrationRow": {
        "type": "object",
        "description": "A published post paired with its predicted score and measured engagement. Only posts that have both appear.\n",
        "required": [
          "postId",
          "platform",
          "score",
          "verdict",
          "publishedAt",
          "impressions",
          "likes",
          "comments",
          "shares",
          "clicks",
          "engagements"
        ],
        "properties": {
          "postId": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "The latest recorded score."
          },
          "verdict": {
            "type": [
              "string",
              "null"
            ]
          },
          "publishedAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the post was published, epoch milliseconds."
          },
          "impressions": {
            "type": [
              "integer",
              "null"
            ]
          },
          "likes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "comments": {
            "type": [
              "integer",
              "null"
            ]
          },
          "shares": {
            "type": [
              "integer",
              "null"
            ]
          },
          "clicks": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engagements": {
            "type": "integer",
            "description": "likes + comments + shares + clicks (nulls counted as 0)."
          }
        }
      },
      "CalibrationResult": {
        "type": "object",
        "required": [
          "calibration"
        ],
        "properties": {
          "calibration": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalibrationRow"
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "description": "An event you can subscribe a webhook to.",
        "enum": [
          "post.published",
          "post.failed",
          "post.scored",
          "channel.disconnected"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "description": "A signed callback endpoint. The secret is never included here.",
        "required": [
          "id",
          "accountId",
          "url",
          "events",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Webhook id (e.g. `whk_...`)."
          },
          "accountId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "The callback URL."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time, epoch milliseconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last update time, epoch milliseconds."
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "description": "The HTTPS callback URL to deliver events to."
          },
          "events": {
            "type": "array",
            "description": "Events to subscribe to. Omit to subscribe to all.",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          }
        }
      },
      "CreateWebhookResult": {
        "type": "object",
        "required": [
          "webhook",
          "secret"
        ],
        "properties": {
          "webhook": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret, returned once (it cannot be retrieved again). Every delivery carries an x-socialkit-timestamp header (epoch seconds) and an x-socialkit-signature header of the form `sha256=<hex>`. To verify: reject the request if the timestamp is not recent (e.g. older than five minutes, which blocks replays), then compute HMAC-SHA256 over the string `<timestamp>.<raw body>` with this secret and compare it, in constant time, to the hex in the signature header.\n"
          }
        }
      },
      "WebhookResult": {
        "type": "object",
        "required": [
          "webhook"
        ],
        "properties": {
          "webhook": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          }
        }
      },
      "WebhookListResult": {
        "type": "object",
        "required": [
          "webhooks",
          "events"
        ],
        "properties": {
          "webhooks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          },
          "events": {
            "type": "array",
            "description": "The full set of subscribable events.",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "webhookId",
          "event",
          "status",
          "attempts",
          "lastStatusCode",
          "lastError",
          "nextAttemptAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "webhookId": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sending",
              "delivered",
              "failed",
              "dead"
            ]
          },
          "attempts": {
            "type": "integer",
            "description": "Number of delivery attempts so far."
          },
          "lastStatusCode": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status of the last attempt, or null."
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ],
            "description": "Error from the last attempt, or null."
          },
          "nextAttemptAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the next retry is due, epoch milliseconds, or null."
          },
          "createdAt": {
            "type": "integer"
          },
          "updatedAt": {
            "type": "integer"
          }
        }
      },
      "DeliveryListResult": {
        "type": "object",
        "required": [
          "deliveries",
          "pagination"
        ],
        "properties": {
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "limit",
              "offset",
              "hasMore"
            ],
            "properties": {
              "limit": {
                "type": "integer",
                "description": "The page size applied to this response."
              },
              "offset": {
                "type": "integer",
                "description": "The offset applied to this response."
              },
              "hasMore": {
                "type": "boolean",
                "description": "True when another page exists past this one."
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              }
            }
          }
        }
      }
    }
  }
}