custom__ prefix. This page is the reference for every custom tool: the arguments it accepts and the fields it returns. Each tool returns a single JSON object as the text content of its result.
The custom toolset will grow over time as more jobs get their own purpose-built tool.
The image post flow
Publishing an image post takes three steps, two tool calls with an image upload in between:1
Start the upload
Call
custom__start-image-upload. It returns a mediaUuid, an uploadId and a short-lived uploadUrl for one image.2
Upload the image
HTTP
PUT the raw image bytes (not base64) to uploadUrl, with no Authorization header. Keep the ETag response header, it confirms the upload in the next step.3
Create the post
Call
custom__create-image-post with image: { mediaUuid, uploadId, etag } and the post fields. The post is created once the image is ready to display.custom__start-image-upload
Starts an image post (step 1 of 3) by reserving an upload slot for one image and returning everything the upload step needs. Takes no arguments.
Requires the write:media scope.
Result
string
required
UUID of the new media item. Pass it to
custom__create-image-post, and use it to reference the media in your library afterwards.string
required
Opaque identifier of this upload. Pass it to
custom__create-image-post exactly as received, don’t parse or modify it.string
required
Short-lived URL to
PUT the raw image bytes to. If it expires before the upload happens, call custom__start-image-upload again for a fresh one, an unused slot is harmless.string
required
A reminder of the remaining steps, embedded in the result so an assistant can complete the flow without consulting this page.
Example result
custom__create-image-post
Publishes a post carrying the uploaded image (step 3 of 3). It accepts the same post fields as create a new post, text, audience, pricing, scheduling, expiry, collections, plus the identifiers of the uploaded image. The post is only created once the image has finished processing and is ready to display, so a post never reaches the feed with broken media.
Requires the write:media, read:media and write:post scopes.
Arguments
object
required
The uploaded image to attach to the post.
string
required
Who can view the post:
subscribers or followers-and-subscribers.string
Text content of the post, up to 5,000 characters.
number
Price in cents for a pay-to-view post, minimum
300 ($3.00). Omit for a free post.string
Future date/time to publish the post, in ISO 8601 format with a timezone (for example
2026-07-01T18:00:00Z). When set, the post is scheduled instead of published immediately.string
Date/time when the post expires, in ISO 8601 format with a timezone.
object
Free teaser image shown to non-subscribers before they unlock a paid post. Upload it through its own
custom__start-image-upload call; the fields are the same as image.string[]
UUIDs of the collections to add the post to.
Result
The created post, the same shape as the create a new post response.string
required
Unique identifier of the created post.
string
required
Date/time when the post was created (ISO 8601 format).
string | null
required
Text content of the post.
number | null
required
Price in cents for paid posts.
string | null
required
UUID of the free preview media, when a
previewImage was supplied.string
required
Audience the post is visible to.
string | null
required
Future date/time when the post will be published, for scheduled posts.
string | null
required
Date/time when the post was published.
null for scheduled posts that have not gone live yet.string | null
required
Date/time when the post expires.
Example result
If a call fails
- A failed call never publishes a broken post: if the image could not be processed, no post is created and the error names the media that failed.
- An image that was already uploaded is not lost, the error lists the media UUIDs that remain in your media library, ready to attach to a post.
- The call is safe to retry with the same arguments; a retry picks up where the failed call left off rather than uploading or posting twice.
- If the error reports a rejected upload, check that the bytes were
PUTto theuploadUrland thatetagis theETagheader from thatPUT. An expireduploadUrlmeans starting over withcustom__start-image-upload.