API reference
v1One contract for profiles, accounts, posts, media, analytics, comments and delivery events.
https://castrook.com/api/v1Requests and responses use JSON with snake_case fields. Every response includes x-request-id and rate-limit headers. Dates use ISO 8601 with an explicit timezone.
Endpoints
/profilesList customer profiles/profilesCreate a profile/profiles/{id}Get a profile/profiles/{id}Update a profile/profiles/{id}Delete an empty profile/connect-sessionsCreate hosted/embedded onboarding/connect-sessions/{id}Inspect a connection session/connect-sessions/{id}Revoke a connection link/mediaList private media/media/uploadsCreate a direct upload ticket/media/{id}/completeVerify and freeze upload/media/{id}Get private asset metadata/media/{id}/contentRead an authenticated preview/media/{id}Delete unused media/analytics/postsRead destination metrics/analytics/summaryRead totals and coverage/posts/{id}/analytics/refreshRefresh post performance/accountsList accounts/accountsCreate a test account/accounts/{id}Assign or clear account profile/accounts/{id}/analytics-consentRecord explicit YouTube analytics privacy acceptance/accounts/{id}Disconnect account/accounts/{id}/publishing-optionsGet TikTok creator settings/accounts/{id}/boardsList Pinterest boards/accounts/{id}/boards/{board_id}/sectionsList Pinterest board sections/postsList posts/postsCreate or schedule a post/posts/{id}Get post and targets/posts/{id}Cancel a pending post/commentsList comments/commentsReply to a comment/webhooksList endpoints/webhooksCreate an endpoint/webhooks/{id}Disable endpoint/deliveriesList webhook deliveries/logsList request logs/usageGet plan usage and limitsDownload the complete OpenAPI specification for all schemas, response codes, and query parameters.
Create a post
POST /posts requires an Idempotency-Key header. A new post returns HTTP 202; an identical replay returns HTTP 200 and Idempotency-Replayed: true.
| Field | Type | Description |
|---|---|---|
text | string | Post caption. Maximum 5,000 characters; platform limits also apply. Empty is valid with media or a LinkedIn article URL. |
account_ids | string[] | One or more account IDs in the same workspace and mode. |
media | object · optional | Legacy single { url, type }. HTTPS URL and video or image. Use either media or media_items. |
scheduled_at | string · optional | ISO 8601 timestamp at least 5 seconds in the future, up to 1 year ahead. |
profile_id | string · optional | Customer profile for this post and its destinations. |
media_items | object[] · optional | Ordered { type, url } or { type, asset_id } items; provider bounds apply. |
format | string · optional | feed, reel, story or video, where the platform supports it. |
destinations | object[] · optional | Per-account caption, media, format and options overrides |
platform_options | object · optional | Platform-specific visibility, consent, and publishing choices |
{
"text": "Made to be shared.",
"account_ids": ["acc_instagram", "acc_youtube"],
"media": {
"url": "https://your-cdn.com/launch.mp4",
"type": "video"
},
"platform_options": {
"youtube": {
"privacy_status": "private",
"made_for_kids": false
}
}
}The post response
{
"data": {
"id": "pst_example",
"text": "Made to be shared.",
"status": "queued",
"mode": "test",
"scheduled_at": null,
"created_at": "2026-09-26T12:00:00.000Z",
"updated_at": "2026-09-26T12:00:00.000Z",
"targets": [
{
"account_id": "acc_instagram",
"platform": "instagram",
"status": "queued",
"attempts": 0
}
]
}
}Targets can include provider_post_id, url, or an error object when the platform provides them. A successful private post may have no public URL. YouTube identifiers and links are refreshed while authorization remains valid; they can disappear after disconnection, deletion on YouTube, or expiry of an unrefreshable record. The historical delivery state is not a guarantee that a video is still available.
Pagination and filtering
List responses return data plus meta.next_cursor and meta.has_more. Pass the next cursor unchanged. Posts support status, platform, q, cursor, and limit filters. q matches a caption substring, ignoring case. Keep filters unchanged while paging; cursors are bound to those filters.
{
"data": [],
"meta": { "next_cursor": null, "has_more": false }
}