{"openapi":"3.1.0","info":{"title":"TheTabber API","version":"1.0.0","description":"Public developer API for TheTabber. Authenticate every request with a workspace-scoped API key minted in **[Settings → Developer API](/dashboard/settings/api-keys)**.\n\nPass the key as `Authorization: Bearer ttbr_live_…`. Every key is scoped to one workspace; all reads and writes are limited to that workspace's social accounts and media.\n\n**Rate limit:** 10 requests per second per key. Headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` are returned on every response.","contact":{"name":"TheTabber","url":"https://thetabber.com"}},"servers":[{"url":"https://thetabber.com","description":"Production"}],"tags":[{"name":"Identity","description":"Inspect the workspace and key the request is authenticated as."},{"name":"Social Accounts","description":"Connected social platform accounts available to the authenticated workspace."},{"name":"Media","description":"Upload media to TheTabber's storage and reference it later when creating posts. Two-step flow: request a presigned URL, then PUT the file directly to S3."},{"name":"Posts","description":"Create, list, and cancel posts. Posts publish immediately when `scheduled_at` is null, or get queued for a future publish otherwise."},{"name":"Post Results","description":"Per-platform outcomes of an api post. One row per social account targeted by a post."}],"security":[{"bearer":[]}],"paths":{"/v1/me":{"get":{"tags":["Identity"],"summary":"Get current key context","description":"Returns the workspace and key the request is authenticated as. Useful for SDKs to validate a key on initialization.","responses":{"200":{"description":"Current key context.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/social-accounts":{"get":{"tags":["Social Accounts"],"summary":"List connected social accounts","description":"Paginated list of social accounts connected to the authenticated workspace. Filter by one or more platforms or usernames using repeated query parameters.","parameters":[{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Limit"},{"name":"platform","in":"query","description":"Filter by platform(s). Repeat the query parameter to filter by multiple values (OR).","schema":{"type":"array","items":{"type":"string","enum":["tiktok","youtube","instagram","facebook","x","linkedin","pinterest","threads","bluesky"]}},"style":"form","explode":true},{"name":"username","in":"query","description":"Filter by username(s). Repeat to OR multiple values.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Paginated list of social accounts.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SocialAccountDto"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/media/create-upload-url":{"post":{"tags":["Media"],"summary":"Request a presigned upload URL","description":"Step 1 of two-step upload. Returns a `media_id` and a signed `upload_url`. Step 2: PUT your file directly to the `upload_url` with the same `Content-Type` as the `mime_type` you sent here. The URL expires in 10 minutes.\n\nAllowed mime types: `image/png`, `image/jpeg`, `image/webp`, `video/mp4`, `video/quicktime`. Size caps: 10MB for images, 200MB for videos.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUploadUrlDto"}}}},"responses":{"200":{"description":"Presigned upload URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUploadUrlResponseDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/media":{"get":{"tags":["Media"],"summary":"List media","description":"Paginated list of media assets for the authenticated workspace. Filter by one or more types.","parameters":[{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Limit"},{"name":"type","in":"query","description":"Filter by media kind. Repeat to OR multiple values.","schema":{"type":"array","items":{"type":"string","enum":["image","video"]}},"style":"form","explode":true}],"responses":{"200":{"description":"Paginated list of media.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MediaDto"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/media/{id}":{"get":{"tags":["Media"],"summary":"Get media by ID","description":"Returns the media record. If the asset is still `pending`, the server will HEAD-verify the S3 object and flip the status to `ready` when the file is present. Poll this endpoint after PUT-ing the file if you want explicit confirmation.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Media record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Media"],"summary":"Delete media","description":"Soft-deletes the record and removes the S3 object.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Media deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponseDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/posts":{"get":{"tags":["Posts"],"summary":"List posts","parameters":[{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","description":"Filter by status. Repeat to OR.","schema":{"type":"array","items":{"type":"string","enum":["scheduled","processing","posted","partial","failed","cancelled"]}},"style":"form","explode":true}],"responses":{"200":{"description":"Paginated list of posts.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PostDto"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Posts"],"summary":"Create a post","description":"Publishes immediately when `scheduled_at` is null, otherwise schedules for later. Supply media via uploaded `media` IDs or developer-hosted `media_urls`. A post may contain at most one video OR up to 10 images.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePostDto"}}}},"responses":{"200":{"description":"Post created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Quota exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"409":{"description":"Media not uploaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/posts/{id}":{"get":{"tags":["Posts"],"summary":"Get a post by ID","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Posts"],"summary":"Update a scheduled post","description":"Only posts with status `scheduled` can be updated. All fields are optional; only the keys present in the body are changed. `scheduled_at` must remain a future timestamp.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePostDto"}}}},"responses":{"200":{"description":"Post updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Status changed during update; refetch and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Posts"],"summary":"Cancel a post","description":"Cancels a scheduled or processing post. Posts already in a terminal state (posted, partial, failed) cannot be cancelled.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponseDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/post-results":{"get":{"tags":["Post Results"],"summary":"List post results","parameters":[{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Limit"},{"name":"post_id","in":"query","description":"Filter by post IDs. Repeat to OR.","schema":{"type":"array","items":{"type":"string","format":"uuid"}},"style":"form","explode":true},{"name":"platform","in":"query","description":"Filter by platform(s). Repeat to OR.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Paginated list of post results.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PostResultDto"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/post-results/{id}":{"get":{"tags":["Post Results"],"summary":"Get a post result by ID","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResultDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/social-accounts/{id}":{"get":{"tags":["Social Accounts"],"summary":"Get a social account by ID","parameters":[{"name":"id","in":"path","required":true,"description":"Social account UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Social account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialAccountDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Workspace-scoped API key minted in Settings → Developer API. Format: `ttbr_live_…`."}},"parameters":{"Offset":{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","default":0,"minimum":0}},"Limit":{"name":"limit","in":"query","description":"Number of items to return (max 100).","schema":{"type":"integer","default":10,"minimum":1,"maximum":100}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"NotFound":{"description":"Resource not found in the authenticated workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"RateLimited":{"description":"Too many requests. Inspect `X-RateLimit-Reset` for when to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}},"ValidationError":{"description":"Request body or parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDto"}}}}},"schemas":{"MeDto":{"type":"object","required":["user_id","workspace_id","workspace_name","key_id","scopes"],"properties":{"user_id":{"type":"string","format":"uuid"},"workspace_id":{"type":"string","format":"uuid"},"workspace_name":{"type":"string"},"key_id":{"type":"string","format":"uuid"},"key_name":{"type":"string","nullable":true},"key_prefix":{"type":"string","nullable":true,"example":"ttbr_live_8a"},"scopes":{"type":"array","items":{"type":"string"}}}},"SocialAccountDto":{"type":"object","required":["id","platform","is_active","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string","enum":["tiktok","youtube","instagram","facebook","x","linkedin","pinterest","threads","bluesky"]},"username":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true,"format":"uri"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"facebook_pages":{"type":"array","description":"Facebook only. The Pages this connection can publish to. Use a page `id` here to build the `page_mapping` a Facebook post requires. Omitted for non-Facebook accounts.","items":{"type":"object","required":["id","name","is_selected"],"properties":{"id":{"type":"string","description":"Facebook Page ID. Use as the `<page_id>` value in a post's `page_mapping`.","example":"1029384756"},"name":{"type":"string","nullable":true,"example":"My Brand"},"is_selected":{"type":"boolean","description":"Whether the user selected this Page as a default publish target in the dashboard."}}}}}},"CreateUploadUrlDto":{"type":"object","required":["name","mime_type","size_bytes"],"properties":{"name":{"type":"string","example":"photo.jpg","description":"Original filename. Used for the S3 key and audit."},"mime_type":{"type":"string","enum":["image/png","image/jpeg","image/webp","video/mp4","video/quicktime"],"example":"image/jpeg"},"size_bytes":{"type":"integer","minimum":1,"example":123456,"description":"Exact byte length of the file. Must match the bytes you PUT to the upload URL."}}},"CreateUploadUrlResponseDto":{"type":"object","required":["media_id","upload_url","name","expires_at"],"properties":{"media_id":{"type":"string","format":"uuid"},"upload_url":{"type":"string","format":"uri","description":"PUT your file to this URL within 10 minutes."},"name":{"type":"string"},"expires_at":{"type":"string","format":"date-time","description":"When the media record expires if not referenced by a post."}}},"MediaDto":{"type":"object","required":["id","mime_type","object","status","kind","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"mime_type":{"type":"string"},"kind":{"type":"string","enum":["image","video"]},"status":{"type":"string","enum":["pending","ready","deleted"]},"object":{"type":"object","required":["isDeleted","url","size_bytes","name"],"properties":{"isDeleted":{"type":"boolean"},"url":{"type":"string","nullable":true,"format":"uri","description":"Public S3 URL. null while pending."},"size_bytes":{"type":"integer"},"name":{"type":"string","nullable":true}}},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}},"DeleteResponseDto":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","example":true}}},"CreatePostDto":{"type":"object","required":["social_accounts"],"properties":{"caption":{"type":"string","description":"Caption applied to all targeted platforms unless overridden."},"social_accounts":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"},"description":"Social account UUIDs from /v1/social-accounts."},"media":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Media UUIDs from /v1/media. One video OR up to 10 images."},"media_urls":{"type":"array","items":{"type":"string","format":"uri"},"description":"Public URLs the developer already hosts. Ignored if `media` is provided."},"scheduled_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 future timestamp. Omit or null to publish immediately.","example":"2026-05-10T18:30:00Z"},"timezone":{"type":"string","description":"IANA timezone for the scheduled time. Defaults to UTC.","example":"America/New_York"},"platform_configurations":{"type":"object","description":"Per-platform overrides and platform-specific knobs. See examples below.\n\n**Facebook:** `page_mapping` maps each targeted Facebook `<account_id>` to the `<page_id>` to publish to. Get valid page IDs from the `facebook_pages` array on `GET /v1/social-accounts` (or `/v1/social-accounts/{id}`).","additionalProperties":true,"example":{"youtube":{"visibility":"unlisted","category":"22","caption":"Optional override"},"tiktok":{"privacy_level":"PUBLIC_TO_EVERYONE","allow_comment":true,"video_cover_timestamp_ms":0},"instagram":{"share_to_feed":true,"thumb_offset":0},"pinterest":{"description":"Pin caption","link":"https://example.com","board_id":"..."},"facebook":{"page_mapping":{"<account_id>":"<page_id>"},"title":"...","description":"..."},"twitter":{"thread_method":"single"}}}}},"UpdatePostDto":{"type":"object","description":"All fields are optional. Only keys present in the body are changed.","properties":{"caption":{"type":"string"},"social_accounts":{"type":"array","items":{"type":"string","format":"uuid"}},"media":{"type":"array","items":{"type":"string","format":"uuid"}},"media_urls":{"type":"array","items":{"type":"string","format":"uri"}},"scheduled_at":{"type":"string","format":"date-time","description":"New future timestamp. Cannot be null on update."},"timezone":{"type":"string"},"platform_configurations":{"type":"object","additionalProperties":true}}},"PostDto":{"type":"object","required":["id","status","post_type","social_accounts","media","media_urls","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["scheduled","processing","posted","partial","failed","cancelled"]},"post_type":{"type":"string","enum":["video","text-image"]},"caption":{"type":"string"},"social_accounts":{"type":"array","items":{"type":"string","format":"uuid"}},"media":{"type":"array","items":{"type":"string","format":"uuid"}},"media_urls":{"type":"array","items":{"type":"string","format":"uri"}},"platform_configurations":{"type":"object","additionalProperties":true},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string","nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PostResultDto":{"type":"object","required":["id","post_id","social_account_id","platform","success","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"post_id":{"type":"string","format":"uuid"},"social_account_id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"success":{"type":"boolean"},"error":{"type":"object","nullable":true,"description":"Present when success is false.","properties":{"message":{"type":"string"}}},"platform_data":{"type":"object","nullable":true,"description":"Present when success is true. Identifiers for the published content on the target platform.","properties":{"id":{"type":"string","nullable":true,"description":"Platform's native post ID."},"url":{"type":"string","nullable":true,"format":"uri","description":"Public URL of the published content."},"username":{"type":"string","nullable":true}}},"created_at":{"type":"string","format":"date-time"}}},"PageMeta":{"type":"object","required":["total","offset","limit","next"],"properties":{"total":{"type":"integer","description":"Total items available across all pages."},"offset":{"type":"integer"},"limit":{"type":"integer"},"next":{"type":"string","nullable":true,"format":"uri","description":"URL for the next page, or null if no more pages."}}},"ErrorDto":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","invalid_api_key","forbidden","not_found","validation_error","account_not_connected","media_kind_mismatch","media_not_uploaded","scheduled_in_past","quota_exceeded","rate_limited","method_not_allowed","internal_error"]},"message":{"type":"string"},"details":{}}}}}}}}