generated: '2026-08-13' method: searched source: https://planable.io/guides/planable-public-api/ derived_from: openapi/_original/planable-openapi.json api: Planable Public API v1 base_url: https://api.planable.io/api/v1 authentication: style: bearer token header: 'Authorization: Bearer pln_...' token_prefix: pln_ scopes: [read, write] applied_to: all 51 published operations detail: authentication/planable-authentication.yml versioning: style: url-path current: v1 pattern: https://api.planable.io/api/v{n} note: >- Planable's guide states plainly "The API is versioned, all endpoints live under /api/v1". The 2026 changelog entry is titled "MCP and API v2, including campaigns, custom views, and competitors", but the live spec still declares version 1.0.0 and serves under /api/v1 — the "v2" in that entry is product-release language, not a served path. No /api/v2 base is published. deprecation_policy: none published sunset_headers: 'none — the spec declares no deprecated:true operation and no Sunset/Deprecation header anywhere.' idempotency: supported: true mechanism: natural-key semantic idempotency, documented per operation header: none note: >- Planable does NOT accept an Idempotency-Key request header, and there is no global replay contract. What it does publish — in the operation descriptions of its own spec — is explicit retry-safe semantics on four operations, which is exactly what an agent needs before retrying. Every other write operation should be treated as non-idempotent. idempotent_operations: - operation: POST /posts/{id}/share behavior: "Idempotent: if the post is already shared, returns the existing link. Grouped posts share a single link across the group." - operation: DELETE /posts/{id}/share behavior: "Idempotent: returns 204 even if the post is not shared." - operation: POST /pages/{id}/competitors behavior: "Re-adding an already-tracked competitor is idempotent — returns 200 with the existing row instead of creating a duplicate." - operation: POST /keywords behavior: "Re-adding an already-tracked keyword is idempotent — returns 200 with the existing row instead of creating a duplicate." retry_guidance: - 429 RATE_LIMITED is always safe to retry after the window named by X-RateLimit-Reset. - 5xx / INTERNAL on a POST that creates a post is NOT safe to blind-retry — no dedupe key exists; list and reconcile first. - The four operations above may be retried freely. pagination: primary_style: limit/offset params: limit: in: query type: integer default: 20 maximum: 100 used_by: 8 operations offset: in: query type: integer used_by: 6 operations secondary_style: cursor cursor_params: cursor: in: query type: integer minimum: 0 used_by: GET /pages/{id}/competitors/top-posts response_envelope: data: array of resources pagination: nextCursor: number or null total: number note: >- The pagination object is only present on the cursor-paged operation; limit/offset operations return a bare data array envelope. filtering_and_sorting: date_range: startDate / endDate (7 operations) and a `range` shorthand (6 operations) sort: sortBy — enum [engagement, recency] scope: workspaceId is a required query parameter on most collection operations post_filters: status, approvalStatus (MCP surface), platform, sentiment, teamOnly, campaignId, pageId request_id_tracing: supported: true location: response body — error.requestId header: none published note: >- Every error body carries a required `requestId` (a UUID). It is not echoed in a response header, so a client must parse the body to capture it. Quote it in support requests. error_envelope: format: vendor JSON (NOT RFC 9457 / application/problem+json) content_type: application/json shape: error: code: string, one of a closed 10-value enum message: string requestId: string details: array (documented in the developer guide; not declared in the spec schema) required: [code, message, requestId] catalog: errors/planable-problem-types.yml rate_limit_signaling: headers: [X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset] present_on: every API response (observed on an unauthenticated 401) retry_after: not sent exhaustion_status: 429 exhaustion_code: RATE_LIMITED detail: rate-limits/planable-rate-limits.yml resource_semantics: immutability: >- Published posts are immutable. Create/update applies to draft and scheduled states only; editing a published post returns POST_ALREADY_PUBLISHED. draft_default: Posts created via API land as drafts and pass through the normal approval flow. auto_publish_caveat: >- If a workspace has auto-publish-on-approval enabled, an API-created post will publish once approved — the API does not opt out of that workspace setting. grouped_posts: Pass multiple pageIds in one POST /posts to create a synced cross-platform post. async_pattern: >- Metrics and social-listening syncs are trigger-then-poll: POST /pages/{id}/sync (202) then GET /pages/{id}/sync-status; POST /keywords then GET /keywords/{keywordId}/sync-status. media_limits: upload_source: public URL max_file_size: 100MB per file (developer guide) max_items_per_post: 10 (MCP capability map) field_expansion: supported: false note: No expand/fields/include parameter is published; responses are fixed shapes. metadata: custom_metadata: not supported — no user-defined metadata field is published on any resource. cross_links: errors: errors/planable-problem-types.yml lifecycle: lifecycle/planable-lifecycle.yml authentication: authentication/planable-authentication.yml scopes: scopes/planable-scopes.yml rate_limits: rate-limits/planable-rate-limits.yml data_model: data-model/planable-data-model.yml