> For the complete documentation index, see [llms.txt](https://help.contouron.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.contouron.com/api/api-reference-for-llms.md).

# API Reference for LLMs

> For the complete documentation index, see [llms.txt](https://help.contouron.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.contouron.com/api/llm.md).

## API Reference for LLMs

This is the machine-readable API reference for AI agents and LLM integrations.

```
CONTOURON API REFERENCE
=====================

IMPORTANT: CHECK HOW YOU ARE CONNECTED
- If you have contouron_create_post, contouron_list_accounts, etc. as MCP tools:
  You are connected via MCP. Call those tools directly. You do NOT need HTTP
  endpoints, base URLs, or auth headers. See tool descriptions for parameters.
  MCP tools reference: https://help.contouron.com/api/mcp/tools
- If you are making HTTP requests directly (no MCP tools available):
  Use the REST API reference below.

REST API:
Base URL: https://backend.contouron.com/v2
Auth Header: contouron-api-key: YOUR_API_KEY
Content-Type: application/json

API KEY FORMAT: Your key sometimes ends with one or more "=" characters (base64
padding). The "=" is part of the key. Send it exactly. Do not strip, trim, or
URL-encode it. In shells, .env files, and scripts, wrap the key in single quotes
(for example 'abc123=='), because "=" is a special character in those contexts.
A 401 or "invalid API key" almost always means the key sent does not match, and a
dropped trailing "=" is the most common cause. Verify with GET /users/me.

All creation operations are ASYNC. Submit a request, then poll for status.

Docs: https://help.contouron.com/api/start
OpenAPI: https://help.contouron.com/api/api-reference/openapi-reference
Errors: https://help.contouron.com/support/errors

================================================================================
MCP TOOL → REST API MAPPING (for reference only)
================================================================================

contouron_get_user                    → GET  /users/me
contouron_list_accounts               → GET  /users/me/accounts
contouron_create_post                 → POST /posts
contouron_get_post_status             → GET  /posts/:postSubmissionId
contouron_list_posts                  → GET  /posts
contouron_list_top_posts              → GET  /analytics
contouron_get_post_analytics          → GET  /posts/:id/analytics
contouron_list_comments               → GET  /comments
contouron_get_comment                 → GET  /comments/:commentId
contouron_post_comment                → POST /comments
contouron_list_conversations          → GET  /conversations
contouron_get_conversation            → GET  /conversations/:conversationId
contouron_list_messages               → GET  /messages
contouron_get_message                 → GET  /messages/:messageId
contouron_send_message                → POST /messages
contouron_create_source               → POST /source-resolutions-v3 (polls internally)
contouron_get_source_status           → GET  /source-resolutions-v3/:id
contouron_list_visual_templates       → GET  /videos/templates
contouron_create_visual               → POST /videos/from-templates
contouron_get_visual_status           → GET  /videos/creations/:id
contouron_list_schedules              → GET  /schedules
contouron_get_schedule                → GET  /schedules/:id
contouron_update_schedule             → PATCH /schedules/:id
contouron_delete_schedule             → DELETE /schedules/:id
contouron_list_pinterest_boards        → GET  /social/pinterest/boards
contouron_create_presigned_upload_url → POST /media/uploads

================================================================================
REST API ENDPOINTS
================================================================================

USER INFO & CONNECTED ACCOUNTS
  GET  /users/me              - Verify API key, get user info
  GET  /users/me/accounts     - List connected social accounts (get accountId)
  GET  /users/me/accounts/:accountId/subaccounts - Get Facebook/LinkedIn pageId
  GET  /social/pinterest/boards?accountId=ID - List Pinterest boards (get boardId)

PUBLISHING
  POST /posts                 - Create/publish a post (30 req/min)
  GET  /posts                 - List posts: scheduled/published/failed, cursor-paginated (60 req/min)
  GET  /posts/:postSubmissionId - Poll post status (60 req/min)

ANALYTICS (Published Post Metrics)
  Collected for twitter/instagram/facebook/threads/bluesky only (others return no metrics yet).
  GET  /analytics              - Top performing published posts (sortBy likes_count/comments_count/views_count/reach_count, default views_count; since/until/platform/limit)
  GET  /posts/:id/analytics    - Latest metrics + snapshot history for one published post (id = published post id, not postSubmissionId)
  GET  /published-posts        - Search published posts (query/platform) with latest analytics snapshot attached
  Metrics: every field is optional; counts are strings, rates are numbers, breakdowns are objects.
  Full metric list (common + platform-specific): https://help.contouron.com/api/analytics-metrics

COMMENTS (Instagram)
  GET  /comments               - List comments on your Instagram posts, cursor-paginated (60 req/min). Filters: postId, parentCommentId, accountId, platform, since, until
  GET  /comments/:commentId    - Get a single comment (60 req/min). Poll to check comment status
  POST /comments               - Post a top-level comment, or a reply when parentCommentId is set, on a published Instagram post (30 req/min)

MESSAGING (Instagram)
  GET  /conversations          - List direct-message conversations, cursor-paginated (60 req/min)
  GET  /conversations/:conversationId - Get a single conversation (60 req/min)
  GET  /messages               - List messages, cursor-paginated (60 req/min)
  GET  /messages/:messageId    - Get a single message (60 req/min). Poll to check send status
  POST /messages               - Send a direct message or a private reply to a comment; async, poll GET /messages/:messageId until sent|failed (30 req/min)

VISUALS
  POST /videos/from-templates - Create visual from template (30 req/min)
  GET  /videos/creations/:id  - Poll visual status
  GET  /videos/templates      - List available templates
  DELETE /videos/:id          - Delete a video

SOURCES (Content Extraction)
  POST /source-resolutions-v3 - Extract content from URL/text (30 req/min)
  GET  /source-resolutions-v3/:id - Poll source status (60 req/min)

SCHEDULES (Content Calendar)
  GET  /schedules              - List future scheduled posts (cursor-paginated)
  GET  /schedules/:id          - Get a single scheduled post
  PATCH /schedules/:id         - Update scheduled post (content and/or time)
  DELETE /schedules/:id        - Delete scheduled post and cancel publishing job

SCHEDULE SLOTS (Recurring Time Windows)
  GET  /schedule/slots         - List all scheduling slots
  POST /schedule/slots         - Create one or more slots
  PATCH /schedule/slots/:id    - Update slot targets (platforms/accounts)
  DELETE /schedules/slots/:id  - Delete a slot
  POST /schedule/slots/next-available - Find next open slot for a platform/account

MEDIA
  POST /media                 - Upload media from URL (30 req/min, optional)
  POST /media/uploads          - Get presigned upload URL for local files (120 req/min)

================================================================================
STEP 0: GET ACCOUNTS (always do this first)
================================================================================

GET /users/me/accounts
GET /users/me/accounts?platform=twitter  (filter by platform)

Response:
{
  "items": [
    { "id": "98432", "platform": "twitter", "fullname": "Jane", "username": "jane" }
  ]
}

For Facebook/LinkedIn, also fetch subaccounts to get pageId.
For YouTube, also fetch subaccounts to get playlistIds.
GET /users/me/accounts/98432/subaccounts

Response:
{
  "items": [
    { "id": "123456789", "accountId": "98432", "name": "My Business Page" }
  ]
}

Use items[].id as target.pageId when publishing to Facebook or LinkedIn.
For YouTube accounts, subaccounts return playlists. Use items[].id values as target.playlistIds (array).

For Pinterest, fetch boards to get boardId:
GET /social/pinterest/boards?accountId={accountId}
Response: { "items": [{ "id": "1234567890123456789", "name": "Summer Outfits" }] }
Use items[].id as target.boardId when publishing a pin.

================================================================================
PUBLISHING A POST
================================================================================

POST /posts

Minimal payload (Twitter):
{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Hello world",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}

RULES:
- content.platform and target.targetType must be set to the same value
- mediaUrls is required. Pass [] for text-only posts. Pass public URLs for media.
- accountId comes from GET /users/me/accounts
- No upload step needed. Pass any public URL in mediaUrls.
- For local files without a public URL, use POST /media/uploads to get a presigned upload URL:
  1. POST /media/uploads with {"filename": "photo.jpg"} -> returns {presignedUrl, publicUrl}
  2. PUT the file binary to presignedUrl with correct Content-Type header
  3. Use publicUrl in mediaUrls when publishing
  Max file size depends on plan (see [Plan Limits](../settings/billing-and-credits.md#plan-limits)). No GitHub raw URLs.
- scheduledTime and useNextFreeSlot are ROOT-LEVEL fields (siblings of "post"), NOT inside "post"

Response: { "postSubmissionId": "uuid" }
Poll: GET /posts/:postSubmissionId until status = "published"

PLATFORM-SPECIFIC target FIELDS:

  twitter:   (none required)
  linkedin:  pageId (optional, from subaccounts)
  facebook:  pageId (REQUIRED, from subaccounts)
  instagram: (none required)
  tiktok:    privacyLevel, disabledComments, disabledDuet, disabledStitch,
             isBrandedContent, isYourBrand, isAiGenerated (all REQUIRED)
  pinterest: boardId (REQUIRED, from GET /social/pinterest/boards)
  threads:   (none required)
  bluesky:   (none required)
  youtube:   title, privacyStatus, shouldNotifySubscribers (all REQUIRED)
  webhook:   url (REQUIRED)

SCHEDULING (root-level, NOT inside "post"):
  "scheduledTime": "2025-12-25T15:00:00Z"  -- schedule for specific time
  "useNextFreeSlot": true                   -- schedule at next calendar slot
  Omit both to publish immediately.

THREADS (Twitter, Bluesky, Threads):
  Add "additionalPosts" array to content:
  "additionalPosts": [{ "text": "Part 2", "mediaUrls": [] }]

================================================================================
CREATING VISUALS
================================================================================

POST /videos/from-templates
{
  "templateId": "77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd",
  "prompt": "Create a quote card about productivity with a blue gradient background",
  "inputs": {},
  "render": true
}

RULES:
- templateId is a UUID from GET /videos/templates (NOT a path)
- Set inputs to {} and describe what you want in "prompt" -- AI fills the inputs
- render: true to generate the video immediately

Response: { "id": "creation-uuid" }
Poll: GET /videos/creations/:id until status = "done"

================================================================================
EXTRACTING SOURCES
================================================================================

POST /source-resolutions-v3
{
  "source": {
    "sourceType": "url",
    "url": "https://example.com/article"
  }
}

Or for text:
{
  "source": {
    "sourceType": "text",
    "text": "Your content here..."
  }
}

Response: { "id": "source-uuid" }
Poll: GET /source-resolutions-v3/:id until status = "completed"

================================================================================
MANAGE SCHEDULED POSTS
================================================================================

List: GET /schedules?limit=20&cursor=OPTIONAL_CURSOR
Get:  GET /schedules/:id
Update: PATCH /schedules/:id { "scheduledTime": "2026-04-01T10:00:00Z", "content": { ... } }
Delete: DELETE /schedules/:id

================================================================================
SCHEDULE SLOTS
================================================================================

List: GET /schedule/slots
Create: POST /schedule/slots { "slots": [{ "time": "09:00", "timezone": "America/New_York", "targets": [{ "platform": "twitter", "accountId": "98432" }] }] }
Update: PATCH /schedule/slots/:id { "targets": [...] }
Delete: DELETE /schedules/slots/:id
Next available: POST /schedule/slots/next-available { "platform": "twitter", "accountId": "98432", "afterTime": "2026-04-01T00:00:00Z", "pageId": null }
Response: { "slot": { "slotId": "slot_1", "slotTime": "2026-04-02T09:00:00Z" } }

Use this when rescheduling a post to the next free slot:
1. POST /schedule/slots/next-available -> get slotTime
2. PATCH /schedules/:id -> set scheduledTime to slotTime

================================================================================
MANAGE COMMENTS
================================================================================

Comments are public comments your audience leaves on your published posts, plus
comments you post back through Contouron.

Supported platforms: instagram

Notes:
- Contouron does not backfill. It captures comments from when the account connects.
- Comments are retained for up to 45 days.
- Lists and posts top-level comments and one level of replies (no reply to a reply).

List comments:
GET /comments?limit=20&cursor=OPTIONAL_CURSOR&accountId=OPTIONAL_ACCOUNT_ID&postId=OPTIONAL_POST_ID&since=ISO_8601&until=ISO_8601
Response: { "items": [ <Comment>, ... ], "cursor": "..." }

Post a comment:
POST /comments
{
  "postId": "id-of-post-to-leave-comment-on",
  "text": "Comment text",
  "parentCommentId": "optional-id-of-comment-to-reply-to"
}
Response: a <Comment> object with status "queued"

PLATFORM-SPECIFIC FIELDS:

instagram:
  text limit: 2200 characters

Poll: GET /comments/:commentId
Status values: "queued" | "processing" | "posted" | "failed" | "deleted"
- posted: live on the platform
- failed: response includes errorMessage and errorCode

<Comment>:
{
  "id": "cmt_abc123",
  "accountId": "98434",
  "platform": "instagram",
  "postId": "post_xyz789",               // null if not published through Contouron
  "parentCommentId": null,               // set when this comment is a reply
  "platformPostId": "17851234567890",
  "platformCommentId": "17899876543210", // null until posted
  "authorId": "1784000000",              // null if unknown
  "isAuthor": false,                     // true = you authored it, false = audience
  "text": "Love this!",
  "status": "posted",
  "errorCode": null,
  "errorMessage": null,
  "createdAt": "2026-07-01T12:34:56Z"
}

NOTE: Replying to a new audience member counts toward the monthly active-contacts limit
(starter 100 / creator 250 / agency 1000).

================================================================================
MANAGE PRIVATE MESSAGES
================================================================================

Messages are private, direct messages sent through the platform inbox (DMs). Read conversations and messages, and send a direct message or a private
reply to a comment.

Supported platforms: instagram

Notes:
- Messages are retained for up to 45 days.
- You reply to people who already messaged you. No cold outreach.
- Platforms apply strict messaging windows. Review "MESSAGING WINDOWS" before sending messages.
- Reaching a new person counts toward the monthly active-contacts limit
  (starter 100 / creator 250 / agency 1000).

List conversations:
GET /conversations?limit=20&cursor=OPTIONAL_CURSOR&accountId=OPTIONAL_ACCOUNT_ID&platform=instagram
Response: { "items": [ <Conversation>, ... ], "cursor": "..." }

Get a conversation:
GET /conversations/:conversationId
Response: a <Conversation> object

<Conversation>:
{
  "id": "cnv_abc123",
  "accountId": "98434",
  "platform": "instagram",
  "participants": [
    { "id": "1784000000", "name": "Jamie", "username": "jamie", "profileImageUrl": null }
  ],
  "createdAt": "2026-07-01T12:00:00Z",
  "updatedAt": "2026-07-02T09:30:00Z"
}

List messages:
GET /messages?limit=20&cursor=OPTIONAL_CURSOR&conversationId=OPTIONAL_CONVERSATION_ID&platform=instagram
Response: { "items": [ <Message>, ... ], "cursor": "..." }

Send a message:
POST /messages
{
  "accountId": "required-account-id",
  "recipientId": "id-of-contact-to-send-message-to",
  "text": "Thanks for reaching out!",
  "target": { "targetType": "instagram" }
}

PLATFORM-SPECIFIC FIELDS:

instagram:
  Direct message, reply to a contact within the 24-hour window:
    recipientId: the incoming message's senderId
    target.targetType: "instagram"
  Private reply to a comment, once, within 7 days of the comment:
    recipientId: the comment's authorId
    target.targetType: "instagram"
    target.commentId: "id-of-comment-to-reply-to"

Poll: GET /messages/:messageId
Status values: "queued" | "processing" | "sent" | "delivered" | "failed"
- sent / delivered: handed to / delivered by the platform
- failed: response includes errorMessage and errorCode

<Message>:
{
  "id": "msg_abc123",
  "conversationId": "cnv_abc123",        // null when not linked to a conversation
  "platform": "instagram",
  "direction": "incoming",               // "incoming" received, "outgoing" you sent
  "senderId": "1784000000",              // null if unknown
  "recipientId": "17841400000000000",
  "text": "Hi! Is this available?",
  "status": "delivered",
  "errorCode": null,
  "errorMessage": null,
  "createdAt": "2026-07-02T09:30:00Z"
}

MESSAGING WINDOWS:
- Standard DM: reply within 24 hours of the person's last message.
- Private reply to a comment: set target.commentId. Allowed once, within 7 days of the
  comment. After it, message the person again only after they open a new 24-hour window.

================================================================================
COMPLETE WORKFLOW (for AI agents)
================================================================================

1. accounts = GET /users/me/accounts
   For Facebook or LinkedIn: also GET /users/me/accounts/{accountId}/subaccounts
   Use subaccount id as target.pageId (REQUIRED for Facebook, optional for LinkedIn)
   For YouTube: also GET /users/me/accounts/{accountId}/subaccounts to get playlist IDs
   Use subaccount ids as target.playlistIds (optional, array)
   For Pinterest: also GET /social/pinterest/boards?accountId={accountId}
   Use board id as target.boardId (REQUIRED for Pinterest)
2. sourceId = POST /source-resolutions-v3 { source: { sourceType, url/text } }
3. POLL: GET /source-resolutions-v3/{sourceId} until status = "completed"
4. videoId = POST /videos/from-templates { templateId, prompt, render: true }
5. POLL: GET /videos/creations/{videoId} until status = "done"
6. postId = POST /posts { post: { accountId, content, target }, scheduledTime?: "ISO8601" }
   scheduledTime and useNextFreeSlot go OUTSIDE "post", at the root level.
   Omit both to publish immediately.
7. POLL: GET /posts/{postId} until status = "published"
8. DONE: use publicUrl from step 7

================================================================================
PLAN LIMITS
================================================================================

Contouron has three plans (starter, creator, agency). Each plan enforces:

                               starter    creator    agency
Max connected social accounts  20         40         100
Max media upload size          400 MB     1 GB       1 GB
Queued scheduled posts         200        1000       3000
Scheduling horizon (months)    9          12         24
Active contacts per month      100        250        1000

Facebook Pages and LinkedIn Company Pages do not count toward the cap.

For synchronous calls like POST /posts, exceeding a plan limit returns HTTP 422.

For asynchronous calls like POST /media, errors surface asynchronously and the
records associated with the job will transition to status "failed".

================================================================================
RATE LIMITS
================================================================================

POST /posts:                    30 requests / minute
GET  /posts/:id:                60 requests / minute
POST /videos/from-templates:    30 requests / minute
POST /source-resolutions-v3:    30 requests / minute
GET  /source-resolutions-v3/:id: 60 requests / minute
POST /media:                    30 requests / minute
POST /media/uploads:            120 requests / minute
GET  /users/me/accounts:        No limit
GET  /users/me:                 No limit

429 response means rate limit exceeded. Check "message" for retry timing.

================================================================================
COMMON MISTAKES
================================================================================

1. Scheduling fields nested inside "post":
   scheduledTime and useNextFreeSlot are ROOT-LEVEL fields, siblings of "post".
   WRONG: { "post": { ..., "scheduledTime": "2025-12-25T15:00:00Z" } }
   RIGHT: { "post": { ... }, "scheduledTime": "2025-12-25T15:00:00Z" }
   If nested inside "post", the post publishes immediately instead of scheduling.

2. Missing pageId for Facebook:
   Facebook REQUIRES target.pageId. You must call GET /users/me/accounts/{accountId}/subaccounts
   to get the pageId before publishing. Without it, the request fails.

3. content.platform and target.targetType mismatch:
   These two fields must have the same value (e.g., both "twitter").

4. Using template path instead of UUID for templateId:
   WRONG: "templateId": "/base/v2/quote-card/.../v1"
   RIGHT: "templateId": "77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd"

5. Manually filling template inputs instead of using prompt:
   Set "inputs": {} and describe what you want in "prompt". AI fills the inputs.

6. Wrong accounts endpoint path:
   WRONG: GET /accounts or GET /v2/accounts
   RIGHT: GET /users/me/accounts
   There is no /accounts endpoint. Always use the full path /users/me/accounts.
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.contouron.com/api/api-reference-for-llms.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
