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.
REST API
Connect a script or application with JSON over HTTP.
https://promeo.ai/api/v1
MCP
Sign in to Promeo from your assistant, then choose its Project and permissions.
https://mcp.promeo.ai
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
- 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.
- Name it Promeo, enter
https://mcp.promeo.ai, and select OAuth with automatic/dynamic client registration. Leave client ID and secret blank. - 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.
- 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.
| Assistant | Setup |
|---|---|
| Claude | Settings → 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. |
| OpenClaw | Control 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. |
| Hermes | Add 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 Muse | End-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 clients | Use Streamable HTTP and OAuth 2.1 discovery/PKCE with dynamic registration, or a protected Bearer credential setting for API keys. |
mcp_servers:
promeo:
url: "https://mcp.promeo.ai"
auth: oauthThese guides follow the clients’ documented setup. The read-only test verifies your actual connection. No universal JSON configuration is assumed.
Get an API key
- Sign in to Promeo, verify your email, and complete Workspace setup.
- Open MCP and API in the dashboard and select New key.
- Give the integration a name, choose one Project or all Projects, and select its permissions. New keys start with read permissions only.
- Choose an expiry of 30 days, 90 days, or one year.
- 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.
| Permission | Allowed action |
|---|---|
projects:read | Discover accessible workspaces and list their Projects |
projects:update | Update Project profile fields |
projects:delete | Delete a Project, its content, and its connections |
posts:read | Filter and paginate posts, read delivery results, or validate proposed posts and estimate credits |
channels:read | List connected accounts and their selectable Discord channels/Pinterest boards |
library:read | Search the Project's image library and inspect owned images |
media:upload | Upload files, import public HTTPS media, or render slides into a Project |
media:generate | Generate or AI-edit library images, or start a sample run; uses AI credits |
automations:read | Read slideshow automations' style profiles and setup conversations |
automations:write | Change style profiles, add style references, and use the style setup chat |
posts:create | Create approved drafts; never publish as a side effect |
posts:schedule | Schedule a reviewed draft or return a scheduled post to draft |
posts:publish | Submit a reviewed draft/scheduled post for immediate publishing |
posts:update | Edit a draft; editing a scheduled post additionally requires posts:schedule |
posts:delete | Delete 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:
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:
workspaceEpochandactorIncarnationfrom the latest authorized workspace or content-list response.expectedRevision, equal to the record'scontentRevisionwhen 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.
| Limit | Allowance |
|---|---|
| Each API key | 60 requests per minute; 5,000 per day |
| Account, across all keys and Firebase sessions | 120 requests per minute; 10,000 per day |
| Mutation attempts, per account | 20 per minute; 200 per day |
| Project profile saves, shared with the dashboard | 50 per day |
| Draft saves, shared with the dashboard | 60 per day |
| Scheduling/scheduled edits and immediate submissions, separate operation buckets | 30 per day each |
| Media uploads, shared with the dashboard | 120 per hour |
| Remote media fetch attempts | 120 per hour |
| Channel destination lookup | 60 per 10 minutes |
| Post deletions, shared with the dashboard | 100 per day |
| API/MCP Project deletions | 20 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):
{ "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:
{
"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:
begin: JSON{ "size": 12345 }, withX-Promeo-Draft-Id(a UUID is valid),X-Promeo-File-Name, andX-Promeo-Video-Type(video/mp4,video/quicktime, orvideo/webm). Retain the returneduploadIdandpartSize.part: send consecutive byte parts asapplication/octet-stream, usingX-Promeo-Upload-Idand a zero-basedX-Promeo-Upload-Part. Respect the returned part size (currently 24 MiB); the last part can be smaller.finish: sendX-Promeo-Upload-Idand an empty body. Processing can take time. HTTP 202 andstate: processingmeans wait, not upload again.status: poll withX-Promeo-Upload-Idafter an interrupted finish, with backoff. Use the returnedmediaonly whenstateiscomplete.cancel: abandon an incomplete upload usingX-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:
{
"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:
{ "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.
{
"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
curl --fail-with-body \
--header "Authorization: Bearer $PROMEO_API_KEY" \
https://promeo.ai/api/v1/projectsGET /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.
{
"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.
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/AbCdEfGhIjKlMnOpQrStReturns { "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.
curl --fail-with-body --request DELETE \
--header "Authorization: Bearer $PROMEO_API_KEY" \
https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrStReturns { "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 argument | Meaning |
|---|---|
limit | Page size from 1 to 100; default 50 |
cursor | Opaque nextCursor from the previous page; keep the Project and filters unchanged |
status | draft, scheduled, processing, publishing, published, or failed; matches the displayed status |
scheduledFrom, scheduledTo | Inclusive start and exclusive end as ISO 8601 timestamps with explicit UTC offsets; unscheduled drafts are excluded |
channelId | One account ID from list_channels |
search | Literal 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.
curl --fail-with-body \
--header "Authorization: Bearer $PROMEO_API_KEY" \
https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt/postsThe result is { "posts": [...], "nextCursor": "..." } plus workspace context.
Existing clients can continue reading posts. Each post includes:
Scroll across to see all columns.
| Field | Meaning |
|---|---|
id | Post ID for update or delete requests |
contentRevision | Author-controlled content version to echo as expectedRevision |
status | draft, scheduled, processing, publishing, published, or failed |
title, description, tags | Shared title, caption, and up to ten tags |
scheduledAt | ISO 8601 UTC time; an unscheduled draft returns an empty string |
destinations | Selected channel snapshots with platform, connection ID, and display name |
destinationOverrides | Optional per-destination copy, media, and settings |
destinationOverrides[].discordChannelId | Discord 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. |
destinationResults | Optional per-destination publishing results |
media, mediaCount | Saved media metadata, including public delivery URLs |
privacyLevel, allowComment, autoAddMusic | Saved 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, photoCoverIndex | Disclosure and cover settings |
aiGenerated, channelSettings | Post-wide AI-generated label request and per-channel provider options ({ connectionId, settings }). Creation and edits can set these; omitted settings are preserved. |
awaitingApproval | Whether a scheduled post still needs approval |
publishStatus, failReason | Optional processing details |
automationId, automationPostType | Optional 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 field | Behavior |
|---|---|
title | Shared title, at most 90 characters |
description | Shared caption, at most 4,000 characters; destination limits still apply |
tags | Replace tags with up to ten values, each at most 32 user-perceived characters; [] clears them |
scheduledAt | Change an existing schedule, or a draft's proposed time; "" clears a draft's proposed time |
timezone | Timezone saved with the post |
media | Replace shared attachments with the exact media objects returned by an upload; [] clears them |
connectionIds | Replace selected accounts using IDs from list_channels; also remove obsolete entries from account-specific arrays |
destinationOverrides | Replace account-specific content; each entry inherits omitted fields from its saved override, or shared content for a new override; [] clears all overrides |
channelSettings, aiGenerated | Replace per-account provider options, or set the shared AI-generated label |
privacyLevel, allowComment, autoAddMusic, commercialContent, brandContentToggle, brandOrganicToggle, photoCoverIndex | Edit the ordinary publishing settings; each destination's constraints still apply |
scheduledComments, autoRepostOverrides | Replace 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.
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.
curl --fail-with-body --request DELETE \
--header "Authorization: Bearer $PROMEO_API_KEY" \
https://promeo.ai/api/v1/projects/AbCdEfGhIjKlMnOpQrSt/posts/PostDocumentIdReturns { "deleted": true }. Confirm the user's intent before calling. A
repeat delete after successful removal returns 404.
Errors and status codes
{
"error": {
"code": "permission-denied",
"message": "This API key needs the posts:update permission."
}
}Scroll across to see all columns.
| HTTP status | Meaning and recovery |
|---|---|
400 | Invalid input, unknown fields, malformed ID, or excessive request size |
401 | Missing, invalid, expired, or revoked credential; use a valid replacement |
403 | Missing key permission or unsupported browser origin |
404 | Route or accessible resource was not found |
405 | Unsupported method; inspect Allow |
409 | Resolve account setup, required approval, resource state, or a concurrent edit |
415 | Send a JSON request with Content-Type: application/json |
429 | Wait for Retry-After, then retry with backoff |
500 | Unexpected 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
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
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.
| Tool | Arguments | Result |
|---|---|---|
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_post | Same shape as schedule, approved content, no required time | Accepted 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 fileName | Uploaded 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 proposalMessageId | The 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 runId | What 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
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.