{"openapi":"3.1.0","info":{"title":"Ubiqlu API","version":"1.0.0","description":"Workspace-scoped REST API for the full publishing flow: discover the workspace a key unlocks, list connected social accounts, upload media, create posts as drafts or schedule them, publish now, poll publish status, and retry failed targets. Authenticate with a workspace API key sent as a bearer token; every key is scoped to one workspace and cannot cross workspace boundaries."},"servers":[{"url":"https://ubiqlu.com/api/v1"}],"paths":{"/workspaces":{"get":{"tags":["Workspaces"],"summary":"Discover the key workspace","description":"Returns the single workspace the API key unlocks, so an agent can learn its workspace id, slug, and timezone from a key alone.","security":[{"bearerAuth":[]}],"parameters":[],"responses":{"200":{"description":"Workspace returned.","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}},"required":["workspaces"],"additionalProperties":false},"example":{"workspaces":[{"id":"workspace_id","name":"Acme","slug":"acme","timezone":"America/New_York","brandSummary":"Indie hardware studio.","createdAt":"2026-06-01T09:00:00.000Z"}]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Workspace not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/social-accounts":{"get":{"tags":["Accounts"],"summary":"List social accounts","description":"Returns the connected social accounts in a workspace. Use the returned ids as socialAccountIds when creating a post.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and accounts."}],"responses":{"200":{"description":"Social accounts returned.","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/SocialAccount"}}},"required":["accounts"],"additionalProperties":false},"example":{"accounts":[{"id":"social_account_id","platform":"instagram","providerVariant":"instagram_business_login","displayName":"Acme","handle":"acme","avatarUrl":"https://cdn.example.com/avatar.png","status":"connected","health":{"state":"connected","needsReconnect":false,"label":"Connected","detail":null},"tokenExpiresAt":"2026-08-01T09:00:00.000Z","connectedAt":"2026-06-01T09:00:00.000Z","lastUsedAt":"2026-06-05T14:30:00.000Z"}]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/media":{"get":{"tags":["Media"],"summary":"List media assets","description":"Returns the latest media assets registered inside one workspace.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and media assets."}],"responses":{"200":{"description":"Media assets returned.","content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","items":{"$ref":"#/components/schemas/MediaAsset"}}},"required":["assets"],"additionalProperties":false},"example":{"assets":[{"id":"media_asset_id","fileName":"launch-image.png","publicUrl":"https://cdn.example.com/workspaces/{workspaceId}/2026-06/asset.png","mimeType":"image/png","bytes":245760,"width":1200,"height":800,"durationSeconds":null,"postTypeHint":"feed_image","createdAt":"2026-06-02T10:15:00.000Z"}]}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Media"],"summary":"Finalize uploaded media","description":"Verifies an uploaded object, stores its metadata, and returns the registered media asset.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and uploaded object."}],"responses":{"201":{"description":"Media asset registered or returned on retry.","content":{"application/json":{"schema":{"type":"object","properties":{"asset":{"$ref":"#/components/schemas/MediaAsset"}},"required":["asset"],"additionalProperties":false},"example":{"asset":{"id":"media_asset_id","fileName":"launch-image.png","publicUrl":"https://cdn.example.com/workspaces/{workspaceId}/2026-06/asset.png","mimeType":"image/png","bytes":245760,"width":1200,"height":800,"durationSeconds":null,"postTypeHint":"feed_image","createdAt":"2026-06-02T10:15:00.000Z"}}}}},"400":{"description":"Invalid body or unsupported media type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, inactive entitlement, or foreign storage key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Uploaded object could not be verified against metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string","description":"Display file name for the stored media asset."},"storageKey":{"type":"string","minLength":1,"description":"Storage key returned by the upload-url endpoint."},"mimeType":{"type":"string","enum":["image/jpeg","image/png","video/mp4"],"description":"Expected object content type."},"bytes":{"type":"integer","exclusiveMinimum":0,"maximum":8388608,"description":"Expected object size in bytes. Maximum: 8.0 MB."},"width":{"description":"Image or video width when known.","anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},{"type":"null"}]},"height":{"description":"Image or video height when known.","anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},{"type":"null"}]},"durationSeconds":{"description":"Video duration when known.","anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]}},"required":["fileName","storageKey","mimeType","bytes"]},"example":{"fileName":"launch-image.png","storageKey":"workspaces/{workspaceId}/2026-06/asset.png","mimeType":"image/png","bytes":245760,"width":1200,"height":800}}}}}},"/workspaces/{workspaceId}/media/upload-url":{"post":{"tags":["Media"],"summary":"Create an upload URL","description":"Returns a short-lived Cloudflare R2 upload URL plus the storage key to finalize later.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and future media asset."}],"responses":{"200":{"description":"Upload URL created.","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string","format":"uri","description":"Short-lived Cloudflare R2 upload URL."},"expiresInSeconds":{"type":"number","description":"Seconds until the upload URL expires."},"storageKey":{"type":"string","description":"Storage key to pass back to the finalize endpoint."},"publicUrl":{"type":"string","format":"uri","description":"Durable public URL the finalized asset will be served from."}},"required":["uploadUrl","expiresInSeconds","storageKey","publicUrl"],"additionalProperties":false},"example":{"uploadUrl":"https://...","expiresInSeconds":900,"storageKey":"workspaces/{workspaceId}/2026-06/asset.png","publicUrl":"https://cdn.example.com/workspaces/{workspaceId}/2026-06/asset.png"}}}},"400":{"description":"Invalid file name, MIME type, or file size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string","minLength":1,"description":"Original file name for display and metadata."},"mimeType":{"type":"string","enum":["image/jpeg","image/png","video/mp4"],"description":"Supported upload content type."},"bytes":{"type":"integer","exclusiveMinimum":0,"maximum":8388608,"description":"File size in bytes. Maximum: 8.0 MB."}},"required":["fileName","mimeType","bytes"]},"example":{"fileName":"launch-image.png","mimeType":"image/png","bytes":245760}}}}}},"/workspaces/{workspaceId}/posts":{"post":{"tags":["Posts"],"summary":"Create a post","description":"Creates a post and its per-account publish targets. Set timing to 'draft' to save without publishing, 'scheduled' with scheduledAt to publish later, or 'now' to publish immediately.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key, media, and accounts."}],"responses":{"201":{"description":"Post created and targets dispatched.","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/Post"}},"required":["post"],"additionalProperties":false},"example":{"post":{"id":"post_id","caption":"Launch day is here.","title":null,"aggregateStatus":"publishing","resolvedAt":"2026-06-06T12:00:00.000Z","createdAt":"2026-06-06T12:00:00.000Z","targets":[{"id":"post_target_id","status":"publishing","platformPostUrl":null,"errorMessage":null,"errorContext":null,"scheduledAt":null,"publishedAt":null,"socialAccount":{"displayName":"Acme","handle":"acme","avatarUrl":"https://cdn.example.com/avatar.png","platform":"instagram"}}],"media":[{"id":"post_media_id","position":0,"publicUrl":"https://cdn.example.com/workspaces/{workspaceId}/2026-06/asset.png","mimeType":"image/png","fileName":"launch-image.png"}]}}}}},"400":{"description":"Invalid caption, media, accounts, timing, or scheduledAt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string","description":"Post caption. Trimmed before use. May be empty when media is attached."},"mediaAssetId":{"anyOf":[{"type":"string","description":"Media asset id from the media endpoints. Omit or null for text-only posts."},{"type":"null"}]},"socialAccountIds":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Connected social account ids to publish to."},"timing":{"default":"now","type":"string","enum":["draft","scheduled","now"],"description":"Publish timing. Defaults to 'now' when omitted."},"scheduledAt":{"anyOf":[{"type":"string","format":"date-time","description":"ISO 8601 date-time in UTC. Required when timing is 'scheduled'."},{"type":"null"}]}},"required":["socialAccountIds"]},"examples":{"Draft (saved, not published)":{"value":{"caption":"Launch day is here.","mediaAssetId":"media_asset_id","socialAccountIds":["social_account_id"],"timing":"draft"}},"Scheduled":{"value":{"caption":"Launch day is here.","mediaAssetId":"media_asset_id","socialAccountIds":["social_account_id"],"timing":"scheduled","scheduledAt":"2026-06-10T15:00:00.000Z"}},"Publish now":{"value":{"caption":"Launch day is here.","mediaAssetId":"media_asset_id","socialAccountIds":["social_account_id"],"timing":"now"}}}}}}}},"/workspaces/{workspaceId}/posts/{postId}":{"get":{"tags":["Posts"],"summary":"Get post status","description":"Returns one post with its per-account publish targets and media, so callers can poll publish status and read published URLs or failure reasons.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and post."},{"name":"postId","in":"path","required":true,"schema":{"type":"string"},"description":"Post to read status for."}],"responses":{"200":{"description":"Post returned.","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/Post"}},"required":["post"],"additionalProperties":false},"example":{"post":{"id":"post_id","caption":"Launch day is here.","title":null,"aggregateStatus":"published","resolvedAt":"2026-06-06T12:01:30.000Z","createdAt":"2026-06-06T12:00:00.000Z","targets":[{"id":"post_target_id","status":"published","platformPostUrl":"https://www.instagram.com/p/abc123/","errorMessage":null,"errorContext":null,"scheduledAt":null,"publishedAt":"2026-06-06T12:01:30.000Z","socialAccount":{"displayName":"Acme","handle":"acme","avatarUrl":"https://cdn.example.com/avatar.png","platform":"instagram"}}],"media":[]}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Post not found in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/post-targets/{targetId}/retry":{"post":{"tags":["Posts"],"summary":"Retry a publish target","description":"Re-dispatches a failed publish target through the durable publish pipeline. Only failed targets can be retried; in-flight targets are rejected.","security":[{"bearerAuth":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace that owns the API key and target."},{"name":"targetId","in":"path","required":true,"schema":{"type":"string"},"description":"Failed publish target id (from the post targets array)."}],"responses":{"200":{"description":"Retry dispatched.","content":{"application/json":{"schema":{"type":"object","properties":{"postId":{"type":"string","description":"Post that owns the retried target."},"postTargetId":{"type":"string","description":"Publish target that was re-dispatched."},"publishJobId":{"type":"string","description":"Durable publish job created for the retry."}},"required":["postId","postTargetId","publishJobId"],"additionalProperties":false},"example":{"postId":"post_id","postTargetId":"post_target_id","publishJobId":"publish_job_id"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Wrong workspace, missing scope, or inactive entitlement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Publish target not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Target is not failed or a retry is already in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Ubiqlu API key"}},"schemas":{"Workspace":{"type":"object","properties":{"id":{"type":"string","description":"Workspace identifier the API key unlocks."},"name":{"type":"string","description":"Workspace display name."},"slug":{"type":"string","description":"URL-safe workspace slug."},"timezone":{"type":"string","description":"IANA timezone used for scheduling."},"brandSummary":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional brand summary used for context."},"createdAt":{"type":"string","format":"date-time","description":"ISO timestamp the workspace was created."}},"required":["id","name","slug","timezone","brandSummary","createdAt"],"additionalProperties":false},"SocialAccount":{"type":"object","properties":{"id":{"type":"string","description":"Social account identifier."},"platform":{"type":"string","enum":["instagram","linkedin","facebook"],"description":"Connected platform."},"providerVariant":{"type":"string","enum":["instagram_business_login","linkedin_personal","linkedin_company","facebook_page"],"description":"Connection variant for the platform."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Account display name."},"handle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Account handle or username."},"avatarUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Avatar image URL."},"status":{"type":"string","enum":["connected","token_expiring","disconnected","error"],"description":"Stored connection status."},"health":{"type":"object","properties":{"state":{"type":"string","enum":["connected","token_expiring","disconnected","error"]},"needsReconnect":{"type":"boolean"},"label":{"type":"string"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["state","needsReconnect","label","detail"],"additionalProperties":false,"description":"Live health used to decide whether a reconnect is needed."},"tokenExpiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO timestamp when the access token expires."},"connectedAt":{"type":"string","format":"date-time","description":"ISO timestamp when the account was connected."},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO timestamp of the last publish that used this account."}},"required":["id","platform","providerVariant","displayName","handle","avatarUrl","status","health","tokenExpiresAt","connectedAt","lastUsedAt"],"additionalProperties":false},"MediaAsset":{"type":"object","properties":{"id":{"type":"string","description":"Media asset identifier."},"fileName":{"type":"string","description":"Display name."},"publicUrl":{"type":"string","format":"uri","description":"Durable public URL for provider publishing."},"mimeType":{"type":"string","enum":["image/jpeg","image/png","video/mp4"],"description":"Stored media content type."},"bytes":{"type":"number","description":"File size in bytes."},"width":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Pixel width when known."},"height":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Pixel height when known."},"durationSeconds":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Video duration when known."},"postTypeHint":{"anyOf":[{"type":"string","enum":["feed_image","feed_video","reel"]},{"type":"null"}],"description":"Provider hint for how to publish the asset."},"createdAt":{"type":"string","format":"date-time","description":"ISO timestamp."}},"required":["id","fileName","publicUrl","mimeType","bytes","width","height","durationSeconds","postTypeHint","createdAt"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"string","description":"Post identifier."},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Post caption."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional post title."},"aggregateStatus":{"type":"string","enum":["draft","scheduled","publishing","published","partially_published","failed"],"description":"Roll-up status across every publish target."},"resolvedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO timestamp the post published or is scheduled for."},"createdAt":{"type":"string","format":"date-time","description":"ISO timestamp the post was created."},"targets":{"type":"array","items":{"$ref":"#/components/schemas/PostTarget"},"description":"Per-account publish targets."},"media":{"type":"array","items":{"$ref":"#/components/schemas/PostMedia"},"description":"Media attached to the post, ordered by position."}},"required":["id","caption","title","aggregateStatus","resolvedAt","createdAt","targets","media"],"additionalProperties":false},"PostTarget":{"type":"object","properties":{"id":{"type":"string","description":"Publish target identifier. Retry uses this id."},"status":{"type":"string","enum":["draft","scheduled","publishing","published","failed"],"description":"Per-account publish status."},"platformPostUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Live URL of the published post, once available."},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Human-readable failure reason when the target failed."},"errorContext":{"anyOf":[{"type":"object","properties":{"body":{"type":"string"}},"required":["body"],"additionalProperties":false},{"type":"null"}],"description":"Raw provider error payload for debugging failed targets."},"scheduledAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO timestamp the target is scheduled to publish."},"publishedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO timestamp the target was published."},"socialAccount":{"type":"object","properties":{"displayName":{"anyOf":[{"type":"string"},{"type":"null"}]},"handle":{"anyOf":[{"type":"string"},{"type":"null"}]},"avatarUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"platform":{"type":"string","enum":["instagram","linkedin","facebook"]}},"required":["displayName","handle","avatarUrl","platform"],"additionalProperties":false,"description":"Account the target publishes to."}},"required":["id","status","platformPostUrl","errorMessage","errorContext","scheduledAt","publishedAt","socialAccount"],"additionalProperties":false},"PostMedia":{"type":"object","properties":{"id":{"type":"string","description":"Post media item identifier."},"position":{"type":"number","description":"Zero-based order within the post."},"publicUrl":{"type":"string","format":"uri","description":"Durable public URL of the attached media asset."},"mimeType":{"type":"string","description":"Content type of the attached media asset."},"fileName":{"type":"string","description":"Display name of the attached media asset."}},"required":["id","position","publicUrl","mimeType","fileName"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message."}},"required":["error"],"additionalProperties":false}}}}