Managing Posts
Create, list, delete, and reconnect OpenQuok posts/post group from the command line.
Connect your agent today
Draft from chat, review in your calendar, and publish only what you approve.
Overview
The posts:* commands wrap the Posts APIs. They drive every step of the publishing lifecycle: pick channels and a schedule time, create a post group, iterate on it, then delete it or reconnect it to its provider-native id once it is published.
Post groups vs. post rows
A post group is the multi-channel composition the UI calls a post; its id is returned as postGroup from posts:create and on list rows. Full get / update / delete group over HTTP is for the signed-in app only — use the workspace or session /posts/group/… APIs. The CLI uses post row ids from posts:list for posts:status, posts:reschedule, posts:delete, posts:missing, and posts:connect.
Create a post
The simplest case: one channel, one body, one scheduled timestamp. You can use short flags (-c, -s, -i, …) or the long names that match POST /public/posts.
openquok posts:create
-c "Hello from OpenQuok"
-s "2026-01-15T12:00:00Z"
-i "<integration-id>" | Flag | Description |
|---|---|
| -c --content | Post body. Repeat for thread-style segments; extra segments map to provider replies when supported (see Threads examples). Long form: --body. |
| -s --date | Schedule time (ISO-8601, required unless -j). Same as --scheduledAt. |
| -t --type | schedule (default) or draft — maps to API scheduled / draft. Long form: --status scheduled | draft. |
| -i --integrations | Comma-separated channel ids (required unless -j). Same as --integrationIds. |
| -m --media | Comma-separated storage paths or URLs per flag (the CLI supplies a placeholder media id when you did not run upload). Values from repeated -m are merged into the main post’s media list. Prefer upload JSON; for attachments on individual follow-up lines use Threads examples or -j. |
| -d --delay | Milliseconds between segments when using multiple -c (default 5000); converted to reply delaySeconds. |
| --settings | Platform-specific settings JSON; merged into each selected integration. |
| -j --json | Path to a JSON file whose root object is the full POST /public/posts body (skips other flags). |
| --bodiesByIntegrationId | JSON object keyed by integration UUID; each value is the per-channel body override. |
| --mediaByIntegrationId | JSON object keyed by integration UUID; each value is an array of id and path media items for that channel. Channels omitted in the map inherit -m / root media. |
| --providerSettingsByIntegrationId | JSON map of provider-specific settings (see CLI Examples). |
| --tagNames | Comma-separated workspace tag names. |
| --repeatInterval | Backend repeat enum (e.g. weekly). |
| -n --note | Human review checklist / todo for the account kanban (agent creates only; max 2000 chars). Same field as posts:review-todo. |
Draft instead of scheduling
openquok posts:create
-c "Review before publishing"
-s "2026-01-15T12:00:00Z"
-t draft
-i "<integration-id>" Agent draft with a human review todo
When an AI agent creates a draft for a human to approve, pass --note so the checklist appears on the account kanban (filter Agent). The CLI always sets isAgent: true on create. Update the note later with posts:review-todo; humans clear the agent flag when they mark reviewed in the dashboard.
openquok posts:create
-c "Launch week announcement"
-s "2026-01-15T12:00:00Z"
-t draft
-i "<integration-id>"
--note "Confirm CTA URL and UTM params before scheduling" See Update Review Todo and Development environment → Kanban smoke test.
Post for Different Body per Channel
Pass --bodiesByIntegrationId to customize each row; the canonical --body becomes a fallback:
openquok posts:create
-s "2026-01-15T12:00:00Z"
-i "<threads-integration-id>,<instagram-integration-id>"
-c "Fallback body"
--bodiesByIntegrationId '{"<threads-integration-id>":"Threads-only caption","<instagram-integration-id>":"Instagram-only caption #photography"}' Post with different media per channel
Upload assets first, then pass shared defaults with -m and per-channel lists with --mediaByIntegrationId:
THREADS_MEDIA=$(openquok upload ./threads.png | jq -c '{id: .data.id, path: .data.filePath}')
IG_MEDIA=$(openquok upload ./instagram.png | jq -c '{id: .data.id, path: .data.filePath}')
openquok posts:create
-s "2026-01-15T12:00:00Z"
-c "Same caption everywhere"
-i "<threads-integration-id>,<instagram-integration-id>"
-m '[{"id":"global","path":"uploads/fallback.png"}]'
--mediaByIntegrationId "{"<threads-integration-id>":[${THREADS_MEDIA}],"<instagram-integration-id>":[${IG_MEDIA}]}" Post and Attach media
Upload the asset first (returns data.id and data.path), then pass them back as JSON:
MEDIA=$(openquok upload ./photo.jpg | jq -c '{id: .data.id, path: .data.filePath}')
openquok posts:create
-s "2026-01-15T12:00:00Z"
-c "Check this out!"
-i "<integration-id>"
-m "[${MEDIA}]" Threads and follow-up comments
Pass -c more than once to build a chain of messages: the first becomes the main body; each extra -c becomes a scheduled follow-up in replies (with message and delaySeconds).
Providers that support that shape (for example Threads) are covered in depth on CLI Examples → Threads.
You can still repeat -m for paths or upload JSON blobs; the CLI collects them into one root-level media array on the post. If a follow-up line needs its own media, build providerSettingsByIntegrationId by hand, use -j with a full JSON body, or follow the jq recipes on the Threads page.
openquok posts:create
-c "Thread 1/3" -m "image1.jpg"
-c "Thread 2/3" -m "image2.jpg"
-c "Thread 3/3"
-s "2026-01-15T10:00:00Z"
-i "<integration-id>" Use -d to set the gap in milliseconds between consecutive follow-ups (default 5000 when omitted).
openquok posts:create
-c "First segment"
-c "Second segment"
-c "Third segment"
-s "2026-01-15T10:00:00Z"
-d 2000
-i "<integration-id>" One integration id for scripted threads
The shorthand above assumes a single channel UUID in -i so the follow-up replies map cleanly. For multiple channels in one command, the same replies payload is merged into each integration's provider settings — only use that when it matches how you want every platform to behave.
Per-platform media rules
Instagram requires at least one attachment for scheduled posts; Threads accepts text-only. Each provider has its own validation — Check with `openquok integrations:settings` before you script a batch.
Multi-channel post (same body)
Send one canonical --body / -c to every channel by listing several integration ids in a single comma-separated -i argument (same wire shape as integrationIds: string[] on POST /public/posts).
openquok posts:create
-c "Posting everywhere!"
-s "2026-01-15T12:00:00Z"
-i "<integration-id-1>,<integration-id-2>,<integration-id-3>" When channels need different copy, use --bodiesByIntegrationId (see above) instead of relying on a single body.
Platform-specific settings
Some providers expect extra fields (post type, article title, tags, thread replies, and so on). The CLI accepts a JSON object with --settings and merges it into providerSettingsByIntegrationId for each integration id you passed with -i. Deeper per-channel control lives in --providerSettingsByIntegrationId; keys from --settings override the same keys from that map when both are present.
Dev.to title and tags (markdown body in -c):
openquok posts:create
-c "Queue this markdown article before it stalls in drafts."
-s "2026-01-15T12:00:00Z"
--settings '{"title":"Keep technical posts shipping","tags":["webdev","productivity"]}'
-i "<integration-id>" Other providers use the same flag with their own keys (for example a planned subreddit payload). Discover valid shapes with openquok integrations:settings <integration-id> and the Integrations CLI page.
Create from a JSON file
For posts with detailed platform-specific content (large providerSettingsByIntegrationId, many tags, or bodies per channel), use the Payload Wizard to build the request and copy a JSON body:
openquok posts:create -j post.json Example post.json (placeholders only — replace ids and timestamps with values from your workspace):
{
"scheduledAt": "2026-01-15T12:00:00.000Z",
"status": "scheduled",
"body": "Short default caption when a channel has no override.",
"integrationIds": [
"<integration-id-1>",
"<integration-id-2>"
],
"bodiesByIntegrationId": {
"<integration-id-1>": "Short version for channel A.",
"<integration-id-2>": "Longer version for channel B."
},
"media": [{ "id": "<media-id>", "path": "uploads/shared.png" }],
"mediaByIntegrationId": {
"<integration-id-1>": [{ "id": "<media-id>", "path": "uploads/channel-a.png" }]
},
"providerSettingsByIntegrationId": {
"<integration-id-1>": {
"replies": [{ "message": "Follow-up only on this channel", "delaySeconds": 60 }]
},
"<integration-id-2>": {
"__type": "instagram"
}
},
"tagNames": ["launch-week"]
} JSON is the public API, not provider-specific
The file mirrors PublicCreatePostDto in the SDK: an integrationIds array, optional body / bodiesByIntegrationId / media / mediaByIntegrationId / providerSettingsByIntegrationId, plus scheduledAt and status. There is no separate nested posts array keyed by provider name in this endpoint.
List posts
openquok posts:list List posts (Filter by Date Range)
Override the window with explicit ISO timestamps:
openquok posts:list
--start "2026-01-01T00:00:00Z"
--end "2026-02-01T00:00:00Z" Same window with the long names: --start / --end.
Defaults in CLI
GET /public/posts/list requires start and end. The CLI fills omitted flags with ±30 local calendar days from today (ISO UTC on the wire). Optional customerGroupId narrows by channel group. SDK and raw HTTP clients must send both dates; see List Posts.
Filter to specific channels by passing a comma-separated list of integration ids:
openquok posts:list
--start "2026-01-01T00:00:00Z"
--end "2026-02-01T00:00:00Z"
-i "<integration-id-1>,<integration-id-2>" List posts (Filter by customer group)
Use the channel-group id from your workspace (row id in integration_customers; assign integrations to groups in the dashboard). The CLI sends it as the customerGroupId query parameter on GET /public/posts/list.
openquok posts:list --customer <customer-group-id> Alias for --customerGroupId (channel-group id).
You can combine —customerGroupId with -i / —integrationIds: only integrations that belong to both the group and the CSV are queried.
To find post rows whose provider id could not be mapped yet (database column release_id is the string missing; JSON responses use releaseId), see Connecting missing posts.
Connecting missing posts
Some platforms do not return a stable published asset id right away. The worker then stores release_id = ‘missing’ on the row; the list API exposes that as releaseId: “missing” (camelCase). Per-post analytics stay blocked until you link the row to the real provider id.
When `posts:missing` returns nothing
The backend only calls a provider-specific enumerator when the integration implements the optional missing hook and the row still has release_id === "missing". Otherwise the list-missing endpoint returns data.items: []. See Get missing content.
List available content
openquok posts:missing <post-id> The CLI prints the API envelope, for example:
{
"success": true,
"data": {
"items": [
{ "id": "example-provider-release-id", "url": "https://example.com/preview-cover.jpeg" },
{ "id": "example-provider-release-id-alt", "url": "https://example.com/preview-cover-2.jpeg" }
]
}
} Narrow to id and preview URL:
openquok posts:missing <post-id> | jq '.data.items[] | {id, url}' Connect a post
After you pick the matching provider id from the list above, connect the row:
openquok posts:connect <post-id> -r "example-provider-release-id" Full workflow
# 1. Find post rows that still need a release id (unwrap data.posts from the list response)
openquok posts:list
--start "2026-01-01T00:00:00Z"
--end "2026-02-01T00:00:00Z"
| jq '.data.posts[] | select(.releaseId == "missing") | {id, content, integrationId}'
# 2. Ask the provider for recent candidates (replace <post-id>)
openquok posts:missing <post-id> | jq '.data.items'
# 3. Link the correct provider id
openquok posts:connect <post-id> -r "example-provider-release-id"
# 4. Confirm per-post analytics resolve (pick a window the CLI accepts)
openquok analytics:post <post-id> -d 7 Update, and delete a post
Changing post status
Move a post between draft and scheduled without changing its publish date. Pass any post row id from posts:list (the id field — the same identifier you use for posts:delete).
openquok posts:status <post-id> -s draft
openquok posts:status <post-id> -s schedule | Flag | Description |
|---|---|
| -s --status | draft — moves a scheduled group back to draft and terminates any in-flight publishing workflow, so it will not publish until you promote it again. schedule or scheduled — promotes a draft into the publishing queue and (re)starts the workflow so it publishes at the stored time. |
Use this when you want to pause a scheduled post without deleting it, or hand a draft to the scheduler once it is ready.
Rescheduling posts
Move a post group to a new publish time. Pass any post row id from posts:list.
openquok posts:reschedule <post-id> -s "2026-06-15T14:30:00.000Z" | Flag | Description |
|---|---|
| -s --scheduledAt | New publish time (ISO-8601, required). Same field as POST /public/posts. |
| --action | update (default) — moves publishDate only and preserves each row’s state (draft, scheduled, or published). schedule — re-queues publishing at the new time and clears releaseId, releaseUrl, and errors. |
| --republish | Set when —action schedule and the group already has published rows; otherwise the API returns 400. |
# Draft or scheduled — move slot without changing state
openquok posts:reschedule <post-id> -s "2026-06-15T14:30:00.000Z"
# Published — re-queue at a new future time (clears provider release ids)
openquok posts:reschedule <post-id> -s "2026-06-20T10:00:00.000Z" --action schedule --republish See Reschedule post for SDK and curl examples.
Deleting posts
openquok posts:delete <post-id> Soft-deletes the post row you name and the whole post group it belongs to (a row never publishes in isolation). To target a group by id alone, use the workspace; the public API deletes by row id (DELETE /public/posts/{postId}).
End-to-end workflow
INTEGRATION_ID=$(openquok integrations:list | jq -r '.[] | select(.identifier=="threads") | .id')
MEDIA=$(openquok upload ./hero.png | jq -c '{id: .data.id, path: .data.filePath}')
POST_ID=$(openquok posts:create
-s "2026-01-20T15:00:00Z"
-t schedule
-c "Scheduled with media"
-i "$INTEGRATION_ID"
-m "[${MEDIA}]"
| jq -r '.data.posts[0].id')
openquok posts:status "$POST_ID" -s draft