Skip to content
PromeoGet started

Developer documentation

Connect to Promeo

Connect your assistant to read channels, upload media, prepare drafts, and schedule or publish posts after your approval. Use MCP in your AI app or the REST API in your own integration.

Sign in or use an API keyChoose a Project and permissions

Start with your assistant’s setup guide. Use Sign in to Promeo when supported, or create an API key for scripts and other clients. New connections default to read-only access.

Connect ChatGPT

  1. On ChatGPT web, open Settings → Apps. Enable Developer mode under Advanced settings if needed, then choose Create. Some accounts show Plugins → Add custom MCP server instead.
  2. Name it Promeo, enter https://mcp.promeo.ai, and select OAuth with automatic/dynamic client registration. Leave client ID and secret blank.
  3. Scan tools and follow Sign in to Promeo. Sign in to your existing account, select one or more Projects (or All Projects), review the requested permissions, and select Allow access. All Projects includes future Projects in your owned workspace; individual selections remain a fixed set.
  4. Finish creating/installing the app, enable Promeo in a new chat, and try the prompt below. New access defaults to Read; choose Write to allow requested uploads, editing, scheduling and publishing. Use Advanced permissions to choose individual actions.

Custom app availability and write actions depend on your ChatGPT plan and workspace permissions. See OpenAI’s setup guide and custom MCP setup. If creation is unavailable, ask your workspace admin to enable custom apps.

Test your connection

Replace Example Project with your Project’s name, or copy a personalized prompt from MCP and API → ChatGPT in Promeo:

Use Promeo to find the Project “Example Project”, verify the connection, and list its connected channels. Summarize account names, platforms, and my granted permissions. Do not create, edit, upload, schedule, or publish anything.

Promeo marks Project access as verified only after a successful authenticated Project read. Creating a key, signing in, or listing available tools does not complete this test. The setup popup refreshes connection status; Check connection fetches the latest evidence. A revoked or expired connection no longer counts. Verification records access to Promeo, not a live social-provider health check or proof that every posting format is supported.

Other assistants

Scroll across to see all columns.

AssistantSetup
ClaudeSettings → Connectors → Add custom connector. Name it Promeo, use the MCP URL above, then Connect and sign in. Leave OAuth credentials blank for registration. Admin approval may be required. Claude guide.
OpenClawControl UI → Settings → MCP → Add server, name promeo, select Streamable HTTP and the MCP URL. Run openclaw mcp login promeo, approve access, then enable the connector for your session. OpenClaw guide.
HermesAdd the YAML below to the local ~/.hermes/config.yaml, then run hermes mcp login promeo in a fresh terminal and sign in. Hermes guide.
Meta MuseEnd-to-end connection has not been verified. If your version offers remote MCP, use the URL and OAuth. Otherwise choose another assistant; creating a key does not add MCP support.
Other clientsUse Streamable HTTP and OAuth 2.1 discovery/PKCE with dynamic registration, or a protected Bearer credential setting for API keys.
yaml
mcp_servers:
  promeo:
    url: "https://mcp.promeo.ai"
    auth: oauth

These guides follow the clients’ documented setup. The read-only test verifies your actual connection. No universal JSON configuration is assumed.

Get an API key

  1. Sign in to Promeo, verify your email, and complete Workspace setup.
  2. Open MCP and API in the dashboard and select New key.
  3. Give the integration a name, choose one Project or all Projects, and select its permissions. New keys start with read permissions only.
  4. Choose an expiry of 30 days, 90 days, or one year.
  5. Create the key and save its value in your integration's protected secret settings. The complete key is shown only once.

The dashboard lists each key's name, masked prefix, Project access, permissions, creation date, expiry, and last use. Last use is recorded at most once per minute. You can have up to 20 active keys and sign-in connections combined. Selecting Disconnect and confirming stops subsequent requests with that key; already accepted requests may finish. To rotate a key, create its replacement, update your integration, then revoke the old key.

A key scoped to All Projects also includes future Projects owned by the account. A key scoped to one Project cannot access another Project. Deleting that Project removes its scoped keys. Account deletion, a disabled account, or revoked email verification prevents API access. Joining another workspace never expands an existing key's access. Only Owners can create or manage workspace keys; invite-only accounts cannot create keys.

Permissions

Setup offers two levels:

  • Read: view Projects, channels, posts and the image library, and validate proposed posts.
  • Write: Read plus media uploads and slide rendering, draft creation and editing, scheduling, immediate publishing and Project profile updates. Each post still needs explicit approval before it is saved, scheduled or published.

Deletion and AI image generation (media:generate, which uses AI credits) are available separately under Advanced permissions. OAuth presets select only permissions requested by that assistant. Existing connections keep their exact granted scopes; changing the setup choices does not expand them. The server continues to check these independent scopes and workspace roles:

Scroll across to see all columns.

PermissionAllowed action
projects:readDiscover accessible workspaces and list their Projects
projects:updateUpdate Project profile fields
projects:deleteDelete a Project, its content, and its connections
posts:readFilter and paginate posts, read delivery results, or validate proposed posts and estimate credits
channels:readList connected accounts and their selectable Discord channels/Pinterest boards
library:readSearch the Project's image library and inspect owned images
media:uploadUpload files, import public HTTPS media, or render slides into a Project
media:generateGenerate or AI-edit library images, or start a sample run; uses AI credits
automations:readRead slideshow automations' style profiles and setup conversations
automations:writeChange style profiles, add style references, and use the style setup chat
posts:createCreate approved drafts; never publish as a side effect
posts:scheduleSchedule a reviewed draft or return a scheduled post to draft
posts:publishSubmit a reviewed draft/scheduled post for immediate publishing
posts:updateEdit a draft; editing a scheduled post additionally requires posts:schedule
posts:deleteDelete a Promeo post record and its stored media

Permissions are independent. For example, an integration with posts:update needs posts:read as well if it should inspect posts before editing them. Grant delete permissions only to integrations that should remove saved data. Deleting a Project includes its stored posts and assets even if the key does not separately have posts:delete.

Authentication

OAuth access tokens and API keys use the same Bearer header for REST and MCP:

http
Authorization: Bearer <promeo-api-key>

The examples use PROMEO_API_KEY, supplied through your client's protected credential facility. Never put a real key in source control, logs, URLs, shared configuration files, or support messages. Promeo stores only a hash of the key, so a lost value cannot be recovered; revoke it and create another.

Verified Firebase ID tokens issued for Promeo's Firebase project (promeo-e55d3) remain supported in the same Bearer header. They expire and must be refreshed through Firebase Authentication. Firebase sessions have the account's full integration permissions and share the account's request limits. For unattended integrations in an owned workspace, use a scoped dashboard API key. Member API keys for joined workspaces are not supported.

OAuth sign-in uses the existing Promeo login and explicit Owner consent. Each connection is limited to the reviewed Projects and granted permissions, lasts up to 90 days, and is listed beside API keys. Access tokens expire within one hour; refresh tokens rotate on use. Reusing an already spent code or refresh token revokes that connection. Disconnecting it invalidates all its tokens. Existing connections keep their original Project access. Reconnect to change the approved selection; refreshing a token never expands it. All Projects is an explicit choice and never includes another owner's Projects or joined workspaces. Deleting the first selected Project removes that scoped connection, including its other Project access, through the existing credential deletion policy. Registered client IDs remain valid after old authorization requests are cleaned up. Approved consent that is never exchanged releases its connection slot after the two-minute code expires: cleanup runs before new connection/key issuance and during scheduled maintenance. Live codes and exchanged grants are preserved.

Resource discovery is at https://mcp.promeo.ai/.well-known/oauth-protected-resource. The issuer stays https://promeo.ai, with discovery at https://promeo.ai/.well-known/oauth-authorization-server. Clients register at https://promeo.ai/oauth/agent/register and use the advertised authorize, token, and revoke endpoints. Codes require S256 PKCE, exact registered callbacks, and resource=https://mcp.promeo.ai in authorization and token requests. HTTPS callbacks and exact localhost/loopback HTTP callbacks with an explicit port are supported. The optional offline_access scope is advertised for clients that request it; rotating refresh tokens are issued with the reviewed 90-day grant. Token and refresh responses never widen Project access or permissions. Reconnect to change access. Promeo does not implement OpenID identity scopes or Client ID Metadata Documents; choose dynamic registration (DCR).

The previous https://promeo.ai/mcp transport remains a compatibility alias. Its metadata is at https://promeo.ai/.well-known/oauth-protected-resource/mcp. Existing grants retain their original resource audience on refresh; exchanging a code or refresh token for the other audience is rejected. Both allowlisted audiences identify the same Promeo API and retain the same Project and scopes.

The account must retain a verified email and an active profile. Terms agreement follows product use; a stored Terms version does not gate API or MCP access. OpenAPI, MCP initialize, and the unauthenticated tool-schema catalog are public. An authenticated catalog is filtered to granted permissions. Every data/tool request requires authentication. HTTP 401 challenges and tool authentication metadata point clients to Promeo’s sign-in discovery.

Workspace context and reviewed content

GET /api/v1/workspaces or MCP list_workspaces returns the accessible workspace IDs, roles, actorIncarnation, and workspaceEpoch. Keys see only their Owner's workspace. Firebase sessions see owned and joined workspaces. Omitting workspaceId from Project listing selects the owned workspace only; an invite-only account gets an empty owned listing. Resource routes resolve their workspace from the Project and verify any supplied workspace ID matches.

Members can read Projects and read, edit, and delete saved posts through both transports. Project profile changes and Project deletion remain Owner-only. The role check and any API key permission must both allow an action.

Scheduling, scheduled edits, immediate publishing, and unscheduling always require the reviewed expectedRevision. Once sharing has been enabled, every update or delete must also include:

  • workspaceEpoch and actorIncarnation from the latest authorized workspace or content-list response.
  • expectedRevision, equal to the record's contentRevision when the caller reviewed it. Worker status timestamps are not content revisions.

REST supplies these as scalar query parameters, alongside optional workspaceId; PATCH bodies retain the existing editable-field shape. MCP supplies them as top-level tool arguments beside projectId, postId, and changes. For example, a REST update URL can end with ?workspaceId=<id>&workspaceEpoch=1&actorIncarnation=1&expectedRevision=4.

Read operations return the current workspace context and record content revisions. A mismatch or missing required value returns 409; reload and review the changed content before trying again. Do not silently replace the caller's reviewed revision with a fresh one. These requirements continue after the final Member leaves or Member access is paused, including for Owner API keys. Removal and reinvitation also change the membership incarnation, so old pending requests cannot regain authority. Legacy requests without these fields remain compatible only in workspaces that have never enabled sharing.

Rate limits

REST and MCP use shared limits. Switching interfaces, creating more keys, or refreshing a Firebase ID token does not reset the account's allowance.

Scroll across to see all columns.

LimitAllowance
Each API key60 requests per minute; 5,000 per day
Account, across all keys and Firebase sessions120 requests per minute; 10,000 per day
Mutation attempts, per account20 per minute; 200 per day
Project profile saves, shared with the dashboard50 per day
Draft saves, shared with the dashboard60 per day
Scheduling/scheduled edits and immediate submissions, separate operation buckets30 per day each
Media uploads, shared with the dashboard120 per hour
Remote media fetch attempts120 per hour
Channel destination lookup60 per 10 minutes
Post deletions, shared with the dashboard100 per day
API/MCP Project deletions20 per day

Account/key windows start with the first request after the previous window expires. Existing dashboard operation limits use fixed UTC time buckets. Authenticated requests count even when their route input or permissions are rejected. Requests rejected at an earlier limit do not consume later quotas. Authenticated tool discovery also counts. JSON requests are limited to 256 KiB; binary uploads use the separate limits below; MCP accepts one JSON-RPC message per request, with no batches. Shared-workspace requests additionally count against the resource Owner's aggregate allowance; adding Members does not multiply workload limits.

A 429 response includes Retry-After, the number of seconds to wait. This applies to both REST errors and MCP JSON-RPC errors. Wait at least that long and add backoff; do not immediately retry in a loop.

Promeo also applies shared pre-authentication limits to both surfaces, including public discovery: 1,200 requests per minute overall and 600 per minute per transport peer. Reverse proxies can share a peer allowance; these are conservative service protection limits, not individual customer quotas. They do not trust caller-supplied forwarding headers. These controls run in the application and are not a separately deployed network DDoS protection service.

REST API

Responses use JSON. The X-Promeo-API-Version response header is 2026-10-06. The OpenAPI specification describes every route, request body, and required permission. Encode path segments with your HTTP client's URL library.

Read channels and destinations

GET /api/v1/projects/{projectId}/channels requires channels:read and returns { "channels": [...] } plus workspace context. Each account has an id, platform, display name/username where available, connection metadata, and saved publishing options. Provider credentials are never returned. This reads Promeo’s saved connections; it does not log in or connect a new social account.

Use GET /api/v1/projects/{projectId}/channels/{connectionId}/destinations with the same permission to choose a Pinterest board or Discord bot channel. It verifies Project ownership before querying the provider. Other account types return an empty destination list because no extra selection is needed.

GET /api/v1/projects/{projectId} reads one Project with projects:read. GET /api/v1/projects/{projectId}/connection safely verifies access and returns the granted permissions. GET /api/v1/projects/{projectId}/posts/{postId} reads one post with posts:read, even if it is outside the recent-post list.

Upload media

media:upload supports ChatGPT attachments, HTTPS imports, and binary uploads. All files go through the same format validation, normalization, Project ownership, and media-claim rules as dashboard uploads. Uploading creates an unclaimed asset; use the exact returned media object when creating or scheduling a post. Unclaimed uploads expire after seven days. A media URL alone cannot bypass the ownership check.

For a publicly accessible file, send JSON to POST /api/v1/projects/{projectId}/media/import (MCP upload_media):

json
{ "url": "https://your-public-host.com/photo.jpg", "fileName": "photo.jpg" }

Remote imports accept public HTTPS images or videos up to 20 MiB, with a 30-second total download deadline. DNS answers are validated and pinned; private addresses, credentials in URLs, unsafe redirects, oversized streams, and compressed responses are rejected. The downloader does not forward your Promeo credential.

In ChatGPT, attach an image or video and ask Promeo to upload it. upload_media declares the ChatGPT file-input extension, so the client supplies a file object instead of asking you for a public URL:

json
{
  "file": {
    "download_url": "https://temporary-file-host.com/signed-file-url",
    "file_id": "file-provided-by-chatgpt",
    "mime_type": "image/jpeg",
    "file_name": "photo.jpg"
  }
}

The same shape works on the REST import route. download_url and file_id are required; mime_type and file_name are optional. Provide either file or url with fileName, not both. Attachment imports have the same 20 MiB limit and public-network checks. Temporary download links and file IDs are not stored or returned. MIME hints can identify generic binary responses, but never bypass validation of the actual bytes. File sharing depends on the client's support; use binary upload for larger video or a client without this extension.

POST /api/v1/projects/{projectId}/media/upload with {} (MCP create_media_upload) returns the upload URL and required header names, never a credential. Your client must send its credential through its protected transport; an assistant must not ask the user to paste it into chat.

For a small file, send bytes to POST /api/v1/projects/{projectId}/media with Authorization: Bearer …, the file’s Content-Type, and X-Promeo-File-Name. Supply X-Promeo-Workspace-Id, X-Promeo-Workspace-Epoch, and X-Promeo-Actor-Incarnation from the latest workspace response when required. The server generates a media folder unless you supply a valid X-Promeo-Draft-Id. Success returns HTTP 201 and { "media": { ... } }.

For long video, use the same authenticated URL with ?videoUpload=... on each POST. Keep the Project, credential, and workspace context fixed:

  1. begin: JSON { "size": 12345 }, with X-Promeo-Draft-Id (a UUID is valid), X-Promeo-File-Name, and X-Promeo-Video-Type (video/mp4, video/quicktime, or video/webm). Retain the returned uploadId and partSize.
  2. part: send consecutive byte parts as application/octet-stream, using X-Promeo-Upload-Id and a zero-based X-Promeo-Upload-Part. Respect the returned part size (currently 24 MiB); the last part can be smaller.
  3. finish: send X-Promeo-Upload-Id and an empty body. Processing can take time. HTTP 202 and state: processing means wait, not upload again.
  4. status: poll with X-Promeo-Upload-Id after an interrupted finish, with backoff. Use the returned media only when state is complete.
  5. cancel: abandon an incomplete upload using X-Promeo-Upload-Id.

Source footage is limited to 10 GiB and 30 minutes, with normalized MP4 output limited to 768 MiB. Videos of one minute or longer currently publish only to YouTube; the other adapters require under one minute. Individual provider limits still apply. For clients without file sharing or secure binary transfer, upload the file in Promeo first.

Create, schedule, and publish

Use POST /api/v1/projects/{projectId}/posts with posts:create to save a draft:

json
{
  "requestId": "one-stable-id-for-this-draft",
  "changes": {
    "title": "Launch day",
    "description": "Our new collection is here.",
    "connectionIds": ["the-id-returned-by-list-channels"],
    "timezone": "Europe/Stockholm",
    "approved": true
  }
}

Use a stable 8–128 character requestId made of letters, digits, hyphens or underscores. An identical retry returns the same draft; changing the payload with that ID fails. This endpoint always creates a draft and consumes no posting credits. It returns { "postId": "...", "status": "draft" }. Drafts can omit channels and media while being prepared. Add uploaded media, channelSettings, or destinationOverrides as needed; consult OpenAPI for field shapes. Account selection is explicit through connectionIds.

Read the saved post and obtain its contentRevision. For scheduling, send POST /api/v1/projects/{projectId}/posts/{postId}/schedule?expectedRevision=1 with posts:schedule and a reviewed JSON changes object:

json
{ "scheduledAt": "2026-11-01T10:00:00Z", "approved": true }

Choose a real time two minutes to 90 days in the future. Omitted content, media, destinations and settings are preserved. The endpoint can also set those fields when finalizing a draft. It cannot change processing, publishing, or finished posts. Stale revisions return 409.

For immediate submission, send the same reviewed changes shape to POST /api/v1/projects/{projectId}/posts/{postId}/publish?expectedRevision=1 with posts:publish. Promeo sets the submission time to now. Both actions charge the same post allowance and applicable X credits as the dashboard. The response means accepted for publishing, not successfully delivered: read the post and its per-channel destinationResults to check the outcome. If a request times out, read before retrying; do not submit a second post.

POST /api/v1/projects/{projectId}/posts/{postId}/cancel?expectedRevision=1 with posts:schedule and { "approved": true } returns it to draft and releases reserved posting credits. Active publishing cannot be cancelled.

Approval applies to the exact content, accounts, timing, provider settings and credit usage. A connection grant is not approval of individual posts. TikTok requires the user’s explicit musicUsageConfirmed: true and, when applicable, brandedContentPolicyConfirmed: true; neither is inferred by the agent. Privacy choices, commercial disclosures, media validation, and provider publishing constraints remain enforced by the ordinary post validator.

Validate a post

POST /api/v1/projects/{projectId}/posts/validate (MCP validate_post) requires only posts:read. It previews the same content, channel, media ownership, provider and credit rules used by the write endpoints. It does not save or approve a post, claim media, reserve credits or enqueue publishing.

json
{
  "postId": "an-existing-draft-id",
  "action": "schedule",
  "changes": { "scheduledAt": "2026-11-01T10:00:00Z" }
}

Omit postId to validate new content supplied in changes. For a saved post, changes override its saved fields; omitted fields are preserved. action is draft, schedule, or publish (the default). Schedule checks require changes.scheduledAt. Approval is not required for this preview; TikTok consent requirements are still reported. Supply an optional reviewed expectedRevision as a REST query parameter or MCP argument to detect stale content.

The response contains valid, an issues array (code, message, and where available connectionId), and credits with estimated postCredits, xCredits, available allowances, periodEndsAt and reserved: false. Early input failures may return credits: null; other channel issues can include the credit estimate. Draft validation uses draft rules and zero posting credits. Provider lookups can refresh connection metadata and count against read limits. A successful preview is a point-in-time check; saving checks current access and allowances again.

Example prompt:

Use Promeo to list my connected channels and find up to 10 drafts for “Example Project”. Validate the draft I choose for publishing and explain any issues and credit usage. Ask for my approval before saving, scheduling or publishing.

List Projects

bash
curl --fail-with-body \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  https://promeo.ai/api/v1/projects

GET /api/v1/projects requires projects:read. Returns up to 100 accessible Projects, newest first. A scoped connection sees only its selected Projects; All Projects also includes future Projects in the owned workspace.

json
{
  "projects": [{
    "id": "AbCdEfGhIjKlMnOpQrSt",
    "contentRevision": 1,
    "name": "Example Project",
    "description": "Short-form product education",
    "audience": "Independent retailers",
    "features": "Product demonstrations and customer stories",
    "voice": "Clear, practical, and warm",
    "createdAt": "2026-08-20T09:30:00.000Z",
    "updatedAt": "2026-09-07T14:10:00.000Z"
  }]
}

Update a Project

PATCH /api/v1/projects/{projectId} requires projects:update. Include only fields you want to change; omitted fields are preserved. Supported fields are name (1–100 characters) and description, audience, features, and voice (1–20,000 characters each). The resulting profile must have all its context fields completed, just as when saving Project settings in the dashboard.

bash
curl --fail-with-body --request PATCH \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"name":"Updated Project","voice":"Friendly and concise"}' \
  https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt

Returns { "project": { ... } } using the Project shape above. A concurrent change detected during the update returns 409; read the current Project before retrying.

Delete a Project

DELETE /api/v1/projects/{projectId} requires projects:delete. This removes the Project, its stored content, and its connected accounts from Promeo. Pending scheduled, processing, or publishing posts must be cancelled or finished first. Existing provider revocation and media cleanup run as they do for dashboard deletion. Already published social content remains on its platform.

bash
curl --fail-with-body --request DELETE \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt

Returns { "deleted": true }. Confirm the user's intent in your integration before sending this request.

List Project posts

GET /api/v1/projects/{projectId}/posts requires posts:read. It returns up to 50 posts by default, ordered by stored scheduled time descending and post ID ascending for ties. Unscheduled drafts use their stored creation/save time for ordering, while their public scheduledAt remains empty.

Scroll across to see all columns.

Query parameter / MCP argumentMeaning
limitPage size from 1 to 100; default 50
cursorOpaque nextCursor from the previous page; keep the Project and filters unchanged
statusdraft, scheduled, processing, publishing, published, or failed; matches the displayed status
scheduledFrom, scheduledToInclusive start and exclusive end as ISO 8601 timestamps with explicit UTC offsets; unscheduled drafts are excluded
channelIdOne account ID from list_channels
searchLiteral case-insensitive text in the shared title or caption, up to 200 characters

Filters combine. Read additional pages until nextCursor is null. Cursors retain timestamp precision and are scoped to the owner, Project and filters. Malformed or mismatched cursors return 400; start from the first page. Pages reflect current data; edits during pagination can change the result order.

bash
curl --fail-with-body \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt/posts

The result is { "posts": [...], "nextCursor": "..." } plus workspace context. Existing clients can continue reading posts. Each post includes:

Scroll across to see all columns.

FieldMeaning
idPost ID for update or delete requests
contentRevisionAuthor-controlled content version to echo as expectedRevision
statusdraft, scheduled, processing, publishing, published, or failed
title, description, tagsShared title, caption, and up to ten tags
scheduledAtISO 8601 UTC time; an unscheduled draft returns an empty string
destinationsSelected channel snapshots with platform, connection ID, and display name
destinationOverridesOptional per-destination copy, media, and settings
destinationOverrides[].discordChannelIdDiscord text or announcement channel ID for a connected bot server. Required for publishing with new server connections; legacy bot connections retain their original channel by default. Webhooks cannot change destination.
destinationResultsOptional per-destination publishing results
media, mediaCountSaved media metadata, including public delivery URLs
privacyLevel, allowComment, autoAddMusicSaved publishing settings; TikTok defaults to Public, with Only me for supported photo accounts. Optional destinationOverrides[].privacyLevel overrides the shared choice for one account. Videos require Public.
brandContentToggle, brandOrganicToggle, photoCoverIndexDisclosure and cover settings
aiGenerated, channelSettingsPost-wide AI-generated label request and per-channel provider options ({ connectionId, settings }). Creation and edits can set these; omitted settings are preserved.
awaitingApprovalWhether a scheduled post still needs approval
publishStatus, failReasonOptional processing details
automationId, automationPostTypeOptional automation provenance

Update a post

PATCH /api/v1/projects/{projectId}/posts/{postId} requires posts:update. Only drafts and scheduled posts can be edited. Include at least one editable field and approved: true to confirm review of the exact changes.

Scroll across to see all columns.

Editable fieldBehavior
titleShared title, at most 90 characters
descriptionShared caption, at most 4,000 characters; destination limits still apply
tagsReplace tags with up to ten values, each at most 32 user-perceived characters; [] clears them
scheduledAtChange an existing schedule, or a draft's proposed time; "" clears a draft's proposed time
timezoneTimezone saved with the post
mediaReplace shared attachments with the exact media objects returned by an upload; [] clears them
connectionIdsReplace selected accounts using IDs from list_channels; also remove obsolete entries from account-specific arrays
destinationOverridesReplace account-specific content; each entry inherits omitted fields from its saved override, or shared content for a new override; [] clears all overrides
channelSettings, aiGeneratedReplace per-account provider options, or set the shared AI-generated label
privacyLevel, allowComment, autoAddMusic, commercialContent, brandContentToggle, brandOrganicToggle, photoCoverIndexEdit the ordinary publishing settings; each destination's constraints still apply
scheduledComments, autoRepostOverridesReplace follow-up comments or per-post repost rules; [] clears the supplied array

Include expectedRevision from the latest post read in the query string (REST) or tool arguments (MCP). It is required when editing media, accounts or publishing settings, and for all edits in a workspace that has enabled sharing. Legacy copy/timing-only draft edits in a never-shared workspace retain their existing guard. Every scheduled-post edit requires both posts:update and posts:schedule, plus a reviewed revision. Unapproved scheduled automation output must be approved through schedule_post with a future time or publish_post; editing cannot approve it as a side effect.

Omitted fields are preserved. Supplied arrays replace their saved arrays; shared copy and media changes do not replace existing destination-specific overrides. The OpenAPI/MCP schema lists the supported options for each platform. A draft stays a draft, and a scheduled post stays scheduled. Use the separate creation, scheduling and publishing endpoints for those transitions. Scheduled saves must meet the existing scheduling and destination validation rules; ordinarily the time must be between two minutes and 90 days from now. Editing a generated draft detaches it from its automation, matching the dashboard.

For a post with a TikTok destination, also send musicUsageConfirmed: true after the user accepts TikTok's Music Usage Confirmation. When applicable, include brandedContentPolicyConfirmed: true after explicit acceptance. Promeo never invents these confirmations for an integration.

bash
curl --fail-with-body --request PATCH \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"description":"Updated caption","tags":["launch"],"approved":true}' \
  'https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt/posts/PostDocumentId?expectedRevision=1'

Returns { "postId": "PostDocumentId", "status": "draft" } or the same shape with "scheduled". A publishing claim or concurrent edit returns 409; read the current post before retrying.

Post reads include automatic repost overrides, approved per-channel rules, and separate delivery results when present. Omitted settings retain their saved values. Channel-wide repost defaults are configured through Channels; post overrides can be edited through REST/MCP or the composer's Advanced section.

Delete a post

DELETE /api/v1/projects/{projectId}/posts/{postId} requires posts:delete. It removes the Promeo post record and stored media. Processing or publishing posts return 409. Deleting a published record does not delete the content from its social platform.

bash
curl --fail-with-body --request DELETE \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt/posts/PostDocumentId

Returns { "deleted": true }. Confirm the user's intent before calling. A repeat delete after successful removal returns 404.

Errors and status codes

json
{
  "error": {
    "code": "permission-denied",
    "message": "This API key needs the posts:update permission."
  }
}

Scroll across to see all columns.

HTTP statusMeaning and recovery
400Invalid input, unknown fields, malformed ID, or excessive request size
401Missing, invalid, expired, or revoked credential; use a valid replacement
403Missing key permission or unsupported browser origin
404Route or accessible resource was not found
405Unsupported method; inspect Allow
409Resolve account setup, required approval, resource state, or a concurrent edit
415Send a JSON request with Content-Type: application/json
429Wait for Retry-After, then retry with backoff
500Unexpected failure; keep the safe reference code when contacting support

Do not automatically replay updates or deletes after a timeout or server error: read the current resource first because the original request may have succeeded. Use bounded backoff for transient failures.

MCP

Promeo provides stateless Streamable HTTP at https://mcp.promeo.ai using protocol version 2025-06-18. Send one JSON-RPC 2.0 message per HTTP POST. There are no session IDs, batches, or SSE stream; GET returns 405.

Use the client-specific sign-in steps above. For key-based scripts, supply the Authorization header through your client’s protected credential facility; configuration keys and environment interpolation differ between clients.

Initialize

bash
curl --fail-with-body https://mcp.promeo.ai \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example-client","version":"1.0.0"}}}'

Send notifications/initialized after the handshake if your client requires it. Notifications receive 202 and never execute tools.

List tools

bash
curl --fail-with-body https://mcp.promeo.ai \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

An authenticated catalog contains only permitted tools. Without a credential it contains public schemas and OAuth requirements, never Project data:

Scroll across to see all columns.

ToolArgumentsResult
list_workspaces{}{ "workspaces": [...] }
list_projects{}{ "projects": [...] }
verify_connection{ "projectId": "..." }Project, granted permissions, and successful connection evidence
get_project{ "projectId": "..." }{ "project": { ... } }
list_channels{ "projectId": "..." }Public connected-account metadata
list_channel_destinations{ "projectId": "...", "connectionId": "..." }Selectable Discord channels or Pinterest boards
get_post{ "projectId": "...", "postId": "..." }{ "post": { ... } } including revision and delivery results
create_post{ "projectId": "...", "requestId": "...", "changes": { ... } }Saved draft ID
schedule_post{ "projectId": "...", "postId": "...", "expectedRevision": 1, "changes": { ... } }Scheduled post ID
publish_postSame shape as schedule, approved content, no required timeAccepted immediate submission; poll get_post for delivery
cancel_post{ "projectId": "...", "postId": "...", "expectedRevision": 1, "approved": true }{ "cancelled": true }, post becomes draft
upload_media{ "projectId": "...", "file": { "download_url": "https://...", "file_id": "..." } }, or url with fileNameUploaded media object
create_media_upload{ "projectId": "..." }Binary endpoint URL and header instructions; no credential or file data
list_posts{ "projectId": "...", "status": "draft", "limit": 10, "cursor": "..." } (filters/cursor optional){ "posts": [...], "nextCursor": "..." }
validate_post{ "projectId": "...", "postId": "...", "action": "publish", "changes": {} } (postId/changes optional){ "valid": true, "issues": [], "credits": { ... }, "requiresApproval": true }
update_project{ "projectId": "...", "changes": { ... } }{ "project": { ... } }
delete_project{ "projectId": "..." }{ "deleted": true }
update_post{ "projectId": "...", "postId": "...", "changes": { ... } }{ "postId": "...", "status": "draft" } or "scheduled"
delete_post{ "projectId": "...", "postId": "..." }{ "deleted": true }
get_channel_rules{ "projectId": "...", "connectionIds": ["..."] } (connectionIds optional)Per-channel and combined image count, caption limit, approval and AI-label rules, plus slide canvas sizes
search_library{ "projectId": "...", "query": "beach", "kind": "generic" } (filters optional){ "images": [{ "generationId": "...", "image": { ... }, "aiGenerated": true }] }
inspect_media{ "projectId": "...", "image": { "objectPath": "..." } } or { "generationId": "..." }An MCP image block (at most 1024 px) plus size; REST-style JSON includes it as base64
render_slide{ "projectId": "...", "format": "9:16", "background": { "generationId": "..." }, "overlaySvg": "<svg>…</svg>" }Rendered media for create_post, aiGenerated, and the overlay's named elements
generate_image{ "projectId": "...", "name": "...", "description": "...", "aspectRatio": "9:16" }New library image; uses 3 AI credits
edit_image{ "projectId": "...", "sourceGenerationId": "...", "name": "...", "instructions": "..." }Edited copy in the library; uses 3 AI credits
start_sample_run{ "projectId": "...", "instructions": "...", "connectionIds": ["..."], "format": "9:16", "slideCount": { "minimum": 3, "maximum": 6 }, "budgets": { ... } }{ "runId": "...", "status": "queued" }; Promeo's agent makes one slideshow draft (1 AI credit when a draft is produced, plus 3 per generated image if allowed)
get_agent_run{ "projectId": "...", "runId": "..." }{ "run": { "status": "...", "usage": { ... }, "steps": [...], "postId": "..." } }
get_style_profile{ "projectId": "...", "automationId": "..." }Current Must/Style text, references and version history; writes Promeo's starter profile on first read
update_style_profile{ "projectId": "...", "automationId": "...", "expectedVersion": 2, "must": "...", "style": "..." }, or restoreVersion, or proposalMessageIdThe new version
add_style_reference{ "projectId": "...", "automationId": "...", "objectPath": "..." }Library generationId for an uploaded reference image
get_style_chat{ "projectId": "...", "automationId": "..." }Setup conversation with any pending proposal
set_automation_budgets{ "projectId": "...", "automationId": "...", "agentBudgets": { "maxImageGenerations": 1 } }The automation's effective per-post budgets, clamped by Promeo's ceilings
get_slides{ "projectId": "...", "postId": "..." }Slides in order with named elements, overlay, background, version, and the post's contentRevision
update_slide_elements{ "projectId": "...", "postId": "...", "expectedRevision": 4, "slideIndex": 0, "elementTexts": { "headline": "..." } } (or overlaySvg, background, backgroundColor)The redrawn slides
restore_slide_version{ "projectId": "...", "postId": "...", "expectedRevision": 5, "slideIndex": 0, "version": 1 }The slides, with the earlier version redrawn as a new version
edit_slides{ "projectId": "...", "postId": "...", "expectedRevision": 6, "order": [1, 0] }The reordered slides
get_post_chat / send_post_chat_message{ "projectId": "...", "postId": "...", "message": "...", "selection": [{ "slideIndex": 0, "elementId": "headline" }] }Fix with AI conversation; replies include the updated slides
get_run{ "projectId": "...", "postId": "..." } or runIdWhat the run used per slide, its interpretation note, outcome and feedback
get_run_history{ "projectId": "...", "automationId": "...", "limit": 5 }The digest of recent runs that every new run also receives
record_feedback{ "projectId": "...", "postId": "...", "text": "...", "scope": "always" }Stored feedback linked to the run
propose_profile_change{ "projectId": "...", "automationId": "...", "must": "...", "style": "...", "summary": "..." }A pending proposal to accept with update_style_profile
send_style_chat_message{ "projectId": "...", "automationId": "...", "message": "...", "attachments": ["generationId"] }The stored message and the assistant's reply, possibly with a proposal

The creation tools are MCP-only for now. render_slide draws the overlay in canvas pixels (1:1 = 1080×1080, 3:4 = 1080×1440, 9:16 = 1080×1920) over an owned library image, uploaded or rendered media, or a solid color. The overlay is a restricted SVG subset: shapes, text, gradients, clip paths, masks and simple filters, with fonts limited to TikTok Sans, sans-serif, serif and monospace. Images, links, scripts, CSS blocks, entities and any url() other than url(#id) are rejected. Give editable parts an id and a data-role (such as headline or cta) so they are reported back and can be referred to later. Pass aiGenerated: true to create_post when a result says the slide contains AI imagery. Image generation shares the dashboard's daily image limit, and a failed generation releases its reserved credits.

start_sample_run needs media:generate plus the permissions the run uses (posts:create, media:upload, library:read); the run never publishes, schedules or deletes. Budgets are optional: maxTurns (default 12, at most 30), maxToolCalls (40, at most 100), maxImageGenerations (0, at most 6), maxCostUsd for model usage ($0.25, at most $1) and visionCheck (off). A run that hits a limit ends as budget_exhausted without a draft and without the run's AI credit; images it already generated stay in the library and stay charged. Runs are not retried automatically. With automationId, start_sample_run uses that automation's style profile as instructions and its channels, format and slide range unless the request overrides them.

Style profiles hold prose rather than settings: a binding Must section, a looser Style section, and reference images with notes. Updates name the expectedVersion they were based on and are refused if someone saved a newer version first. send_style_chat_message runs Promeo's own setup assistant; it never saves a profile itself, so accept its proposal with update_style_profile and proposalMessageId. Assistants that prefer their own model can read and update profiles directly instead.

changes accepts the corresponding REST action’s fields and follows the same approval and state rules. Write tools advertise destructive annotations. The client should request user confirmation for destructive operations. The table shows the original arguments; workspace selection and the context/ revision arguments described above apply to the same tools. Connecting or disconnecting social accounts still happens in the dashboard.

Interactive results (MCP Apps)

Promeo supports the MCP Apps extension (io.modelcontextprotocol/ui). create_post, get_post and validate_post link to a post review app, and render_slide links to a slide preview, through _meta.ui.resourceUri (with ChatGPT's openai/outputTemplate alias). Hosts that support MCP Apps fetch the ui://promeo/… HTML with resources/read and show it in a sandboxed frame; resources/list lists both apps and needs no credential because the templates contain no user data. The URI ends in a content version, so an updated app gets a new URI. Each app declares a Content Security Policy that allows only Promeo media and no network connections; actions such as "Approve and schedule…" send a chat message so the assistant confirms details before calling a tool. Hosts without MCP Apps ignore this metadata and still receive the complete text and structuredContent result.

The slide tools link to the slide editor app. In hosts with MCP Apps the person can click parts of a slide; the app tells the assistant what is selected (ui/update-model-context), edits selected text in place, and undoes slide changes by calling the tools itself. Slide edits never approve a post.

Call a tool

bash
curl --fail-with-body https://mcp.promeo.ai \
  --header "Authorization: Bearer $PROMEO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"update_post","arguments":{"projectId":"AbCdEfGhIjKlMnOpQrSt","postId":"PostDocumentId","changes":{"description":"Updated caption","approved":true}}}}'

Successful results provide both content containing JSON text and an object in structuredContent. Validation, permission, and ownership errors return a tool result with isError: true. Authentication failures use HTTP 401 with a discovery challenge; tool calls also include mcp/www_authenticate metadata. Insufficient-scope tool errors can prompt reauthorization. Rate limits use HTTP errors with a JSON-RPC error; limits always return HTTP 429 and Retry-After. Protocol errors use -32600 (invalid request), -32601 (unknown method), or -32602 (invalid parameters); unexpected failures use -32603.

Compatibility and support

  • Ignore unknown response fields unless your integration needs them.
  • Keep the advertised API and MCP versions in safe diagnostics.
  • Generation, connecting/disconnecting social accounts, webhooks, and pagination cursors are outside this API. Use the dashboard for generation and account setup.
  • A successful submission means Promeo accepted it into the publishing queue. Provider validation, processing and partial failures remain possible; read the post’s per-channel results before reporting publication.
  • Never expose API keys, Firebase tokens, channel credentials, or full user content in logs or support requests.

For unexpected failures, include the request path or MCP method, timestamp, HTTP status, advertised version, and any safe error reference. Contact Promeo support with those details.