facebookFacebook Pagefeed · story · reel · live · page_idAthenous REST API · v1
One key. Fifteen networks. Posts and ads.
Schedule organic posts and run paid campaigns from your backend.
Authenticate with X-Api-Key on Pro, Agency, or Enterprise.
- Base
public-api.athenous.io - Organic15 networks
- Ads6+ platforms
- Auth
X-Api-Key
OpenAPI YAML and Postman export live in Settings → API keys after you sign in.
curl -sS "https://public-api.athenous.io/api/v1/posts?size=5" \
-H "X-Api-Key: $ATHENOUS_API_KEY"
# 200
# { "content": [{ "id": "…", "status": "DRAFT" }] }
Networks
One API key across organic channels and paid ad platforms already linked in your workspace.
Organic posts · 15 networks
- X
- Threads
- TikTok
- YouTube
- Bluesky
- Snapchat
- Discord
- Telegram
- Mastodon
Paid ads
- Meta
- TikTok
- Snapchat
Quickstart
Three steps from zero to a published post.
-
Connect accounts
Link profiles in Connections. Copy each
connectorIdfor Posts and Ads. -
Create an API key
In Settings → API keys, enable REST API and copy the key once. Add Posts (
athenous_post), Ads (athenous_ad), or both to limit that key to one product. -
Create & publish
POST /api/v1/postsfor organic, or campaign → ad set → ad, thenPOST …/publish.
Authentication
- Base URL
https://public-api.athenous.io- Header
X-Api-Key: ath_…- Scope
- Enable REST API (
api). Addathenous_postfor posts,athenous_adfor ads, or both. A key with neither product scope can call both. CLI-only keys are rejected. - Rate limit
- 100/min per key. 500/min with Batch. Posts, campaigns, and connectors each have their own window.
curl -sS "https://public-api.athenous.io/api/v1/posts?includePublications=true" \ -H "X-Api-Key: $ATHENOUS_API_KEY"
Connectors API (prerequisite)
List connections to obtain connectorId values used in posts and ads.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/connectors | List your connections (get connectorId, platform, purpose) |
| GET | /api/v1/connectors/{id} | Get one connector |
| PATCH | /api/v1/connectors/{id} | Update identity (Snap Posts: set externalId to Public Profile UUID) |
| GET | /api/v1/connectors/pages?platform=FACEBOOK | Facebook Pages for a connector |
| GET | /api/v1/connectors/ad-accounts?platform=META | Ad accounts for ads publish |
| GET | /api/v1/connectors/snapchat/public-profiles | Snapchat Public Profiles (often empty unless Snap allowlists your app) |
Posts API — full reference
Base path /api/v1/posts.
| Method | Path | Description | Body | Status |
|---|---|---|---|---|
| POST | /api/v1/posts | Create post | Request body | 201 |
| GET | /api/v1/posts | List posts (page, size, status[], includePublications) | — | 200 |
| GET | /api/v1/posts/quota/monthly | Monthly post quota for current user | — | 200 |
| GET | /api/v1/posts/platforms/threads/recent | Recent Threads media (connectorId, limit) | — | 200 |
| GET | /api/v1/posts/platforms/tiktok/creator-info | TikTok creator_info for UI | — | 200 |
| GET | /api/v1/posts/{id} | Get post by UUID | — | 200 |
| PUT | /api/v1/posts/{id} | Update post; ?status=DRAFT|SCHEDULED|… | Request body | 200 |
| DELETE | /api/v1/posts/{id} | Delete post and publications | — | 204 |
| POST | /api/v1/posts/{id}/publish | Publish to all channels in post | — | 200 |
| POST | /api/v1/posts/{id}/sync | Pull engagement metrics per channel | — | 200 |
| GET | /api/v1/posts/{id}/publications | List per-channel publication rows | — | 200 |
| POST | /api/v1/posts/media | Upload file (multipart field file) | multipart | 200 |
| POST | /api/v1/posts/media/upload-session | Signed GCS direct upload session | { fileName, contentType, byteSize } | 200 |
| GET | /api/v1/posts/export | Export all posts JSON (paid plan) | — | 200 |
| POST | /api/v1/posts/import | Import posts JSON (paid plan) | Request body | 201 |
| GET | /api/v1/posts/export/csv | Export CSV (paid plan) | — | 200 |
| POST | /api/v1/posts/import/csv | Import CSV (paid plan) | text/csv body | 201 |
| GET | /api/v1/posts/import/template | CSV template download (paid plan) | — | 200 |
Supported channels
Use these ids in channels[] and as keys inside channelSettings.
instagramInstagramBusiness via linked Meta PagethreadsThreadstext · image · video · carousellinkedinLinkedInpost → text · image · videotiktokTikTokvideo · Direct Post APItwitterX (Twitter)text · image · video · poll · threadyoutubeYouTubevideo · short · live · channel_idsnapSnapchatstory · spotlightredditRedditsubreddit requiredpinterestPinterestboard_id · pin · ideadiscordDiscordchannel_id · forumblueskyBlueskyauto embed · omit contentTypemastodonMastodontext posttelegramTelegramchat_idwhatsappWhatsAppto (E.164)channelSettings reference
channelSettings in the request body is a JSON string (stringify before send). Keys are channel ids. Each value is an object. These fields apply inside every channel object.
| Field | Type | Required | Description |
|---|---|---|---|
connectorId | UUID | Yes to publish | Connection that publishes this channel. Copy it from GET /api/v1/connectors. |
contentType | string | No | Format for that network. Allowed values are listed per channel below. |
privacy | string | No | Who can see the post. On LinkedIn, only_me skips publish. |
tags | string[] | No | Tags sent with the post. |
scheduledAt | string | No | ISO-8601 time for this channel only. Overrides the post scheduledAt. |
media_urls | string[] | No | Extra image or video URLs. Carousel limits depend on the channel. |
Example object
{
"facebook": {
"contentType": "feed",
"connectorId": "660e8400-e29b-41d4-a716-446655440001",
"page_id": "123456789012345",
"privacy": "public",
"tags": ["product", "launch"],
"media_urls": [
"https://storage.example/img1.jpg",
"https://storage.example/img2.jpg"
]
},
"linkedin": {
"contentType": "post",
"connectorId": "550e8400-e29b-41d4-a716-446655440000",
"author_urn": "urn:li:organization:12345",
"privacy": "public"
},
"bluesky": {
"connectorId": "770e8400-e29b-41d4-a716-446655440002"
},
"reddit": {
"contentType": "text",
"connectorId": "...",
"subreddit": "test"
}
}| Channel | Fields |
|---|---|
all | connectorId (UUID, required for publish), contentType, privacy, tags (string[]), scheduledAt (ISO-8601 override) |
facebook | contentType: feed|story|reel|live. feed auto-resolves to text|image|video|carousel|link_carousel. page_id, media_urls (2–10 for carousel) |
instagram | contentType, page_id, media_urls (carousel 2–10) |
linkedin | contentType: post (→ text|image|video). author_urn (optional). only_me skips publish |
threads | contentType (auto), link, alt_text, topic_tag (≤50), media_urls (carousel 2–20) |
bluesky | omit contentType or auto. link, video_url, media_urls (≤4 images), alt_text, alt_texts[] |
tiktok | tiktok_privacy_level (string, required; must match this account’s options, SELF_ONLY is Only me), tiktok_title (string), tiktok_allow_comment, tiktok_allow_duet, tiktok_allow_stitch (boolean; unset means off), tiktok_video_cover_timestamp_ms (number, milliseconds), tiktok_brand_content_toggle, tiktok_brand_organic_toggle, tiktok_is_aigc (boolean) |
reddit | subreddit (name or r/name), contentType: text|image|video|link |
pinterest | board_id, link, alt_text, media_urls (2–5 multi-image) |
youtube | channel_id, videoTitle (≤100), extraAccounts[{ connectorId, channel_id }] |
discord | channel_id, forum_channel_id, forum_thread_name, embeds_json, poll_json |
telegram | chat_id |
whatsapp | to (E.164 phone) |
snap | profile_id, locale, skip_save_to_profile, contentType: story|spotlight |
mastodon | contentType: text |
twitter | contentType: text|image|video|poll|thread |
Post fields
Every field on a post. Times are ISO-8601 in UTC, for example 2026-03-20T14:00:00Z.
Request body
Sent to POST /api/v1/posts and PUT /api/v1/posts/{id}. On update, a field you omit keeps its current value. PUT also accepts ?status= with one post status.
| Field | Type | Required | Description |
|---|---|---|---|
connectorId | UUID | No | Connection for a single-channel post. For several channels, set connectorId inside each channelSettings entry. |
title | string | No | Short title stored with the post in Athenous. |
body | string | No | Caption and main text sent to each channel. |
mediaUrl | string | No | HTTPS URL of the image or video. Upload a file with POST /api/v1/posts/media and use the returned mediaUrl. |
scheduledAt | string | No | When Athenous should publish. A draft must be approved before it can move to SCHEDULED. |
channels | string[] | No | Channel ids to publish to. Use the ids in Supported channels, such as linkedin and facebook. |
channelSettings | string | No | A JSON object encoded as a string. Keys are channel ids. Each value holds that channel’s connectorId and options. See channelSettings. |
Response body
Returned by create, get, update, approve, and publish. List returns these objects inside content.
| Field | Type | Description |
|---|---|---|
id | UUID | Athenous id of the post. |
connectorId | UUID or null | Single-channel connection, when one was stored. |
userId | string | Account that owns the post. |
title | string | Title stored with the post. |
body | string | Caption and main text. |
mediaUrl | string or null | Hosted image or video URL. |
status | string | DRAFT, SCHEDULED, PUBLISHING, PUBLISHED, PARTIAL, FAILED, or CANCELLED. PARTIAL means at least one channel published and at least one failed. |
scheduledAt | string or null | Planned publish time. |
publishedAt | string or null | Time the first channel published. |
createdAt | string | When the post was created. |
updatedAt | string | When the post was last saved. |
approvedAt | string or null | When the draft was approved. Empty until POST /api/v1/posts/{id}/approve. Editing the title, body, media, or channels clears it. A draft cannot be scheduled while this is empty. |
channels | string[] | Channel ids on this post. |
channelSettings | string | The channel options stored as a JSON string. |
publications | object[] or null | One object per channel. Included when includePublications=true, and on publish. Fields are listed under Publication. |
remoteUpdateWarnings | object[] or null | Present on PUT when a network rejected the edit after Athenous saved the post. Each object has channel (string) and message (string). |
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"connectorId": null,
"userId": "user-uuid",
"title": "March update",
"body": "Hello world",
"mediaUrl": "https://storage.googleapis.com/your-bucket/media.jpg",
"status": "DRAFT",
"scheduledAt": "2026-03-20T14:00:00Z",
"publishedAt": null,
"createdAt": "2026-03-18T10:00:00Z",
"updatedAt": "2026-03-18T10:00:00Z",
"approvedAt": null,
"channels": ["linkedin", "facebook"],
"channelSettings": "{\"linkedin\":{\"contentType\":\"post\",\"connectorId\":\"...\"},\"facebook\":{\"contentType\":\"feed\",\"connectorId\":\"...\",\"page_id\":\"123\"}}",
"publications": null,
"remoteUpdateWarnings": null
}List response
GET /api/v1/posts returns a page, not a single post.
| Field | Type | Description |
|---|---|---|
content | object[] | Posts on this page. Each item uses the response body fields above. |
totalElements | number | Total posts that match the filter. |
totalPages | number | Number of pages at the requested size. |
| Query | Type | Description |
|---|---|---|
page | number | Page index, starting at 0. Default 0. |
size | number | Page size from 1 to 1000. Default 20. |
status | string | Repeat to filter, for example status=DRAFT&status=SCHEDULED. |
includePublications | boolean | When true, each post includes publications. Default false. |
Publication
One object for each channel the post was sent to. Also returned by GET /api/v1/posts/{id}/publications and POST /api/v1/posts/{id}/sync.
| Field | Type | Description |
|---|---|---|
id | UUID | Id of this publication row. |
postId | UUID | Post this row belongs to. |
connectorId | UUID | Connection used for this channel. |
channel | string | Channel id, such as linkedin. |
externalId | string or null | Id the network returned for the published item. |
status | string | PENDING, PUBLISHING, PUBLISHED, or FAILED. |
errorMessage | string or null | Why this channel failed. |
publishedAt | string or null | When this channel published. |
syncedAt | string or null | When engagement numbers were last pulled. |
likes | number | Like count from the last sync. |
comments | number | Comment count from the last sync. |
shares | number | Share count from the last sync. |
impressions | number | Impression count from the last sync. |
views | number | View count from the last sync. |
clicks | number | Click count from the last sync. |
{
"id": "pub-uuid",
"postId": "post-uuid",
"connectorId": "connector-uuid",
"channel": "linkedin",
"externalId": "urn:li:share:123",
"status": "PUBLISHED",
"errorMessage": null,
"publishedAt": "2026-03-20T14:01:00Z",
"syncedAt": "2026-03-21T09:00:00Z",
"likes": 42,
"comments": 5,
"shares": 3,
"impressions": 1200,
"views": 0,
"clicks": 18
}Delete response
DELETE /api/v1/posts/{id} removes the post in Athenous and reports networks that did not remove the live item.
| Field | Type | Description |
|---|---|---|
deleted | boolean | True when the post was removed from Athenous. |
remoteDeleteWarnings | object[] | Networks that did not delete the live item. Each object has channel (string) and message (string). |
Ads API — full reference
Hierarchy: Campaign → Ad set → Ad. Publish top-down.
Ad platforms
| platform | Label | Typical publish fields |
|---|---|---|
META | Meta (Facebook / Instagram Ads) | connectorId, adAccountId, facebookPageId (ads) |
LINKEDIN | LinkedIn Ads | connectorId, adAccountId, objectiveType on ad set |
TIKTOK | TikTok Ads | connectorId, advertiser id; creative id for some ads |
PINTEREST | Pinterest Ads | connectorId, adAccountId, pinId on ad publish |
SNAPCHAT | Snapchat Ads | connectorId, ad account; creative id |
REDDIT | Reddit Ads | connectorId, adAccountId, post_id to promote |
OPENAI_ADS | OpenAI Ads | connectorId, adAccountId; image + CTA on ad |
SPOTIFY_ADS | Spotify Ads | connectorId, adAccountId; audio/video asset + image + CTA URL |
Campaigns
| Method | Path | Description | Body | Status |
|---|---|---|---|---|
| POST | /api/v1/campaigns | Create campaign | Request body | 201 |
| GET | /api/v1/campaigns | List (page, size, includeCounts) | — | 200 |
| GET | /api/v1/campaigns/{id} | Get campaign | — | 200 |
| PUT | /api/v1/campaigns/{id} | Update; ?status=ACTIVE|PAUSED|… | Request body | 200 |
| DELETE | /api/v1/campaigns/{id} | Delete campaign | — | 204 |
| DELETE | /api/v1/campaigns?ids=… | Bulk delete | — | 200 |
| POST | /api/v1/campaigns/{id}/publish | Create campaign on ad platform | Request body | 200 |
| POST | /api/v1/campaigns/{id}/sync | Fetch platform metrics | Request body | 200 |
| POST | /api/v1/campaigns/{id}/push | Push local changes to platform | Request body | 200 |
| GET | /api/v1/campaigns/{id}/publications | Platform publication records | — | 200 |
| GET | /api/v1/campaigns/export | Export JSON (paid) | — | 200 |
| POST | /api/v1/campaigns/import | Import JSON async (paid) | Request body | 201 |
Ad sets
| Method | Path | Description | Body | Status |
|---|---|---|---|---|
| POST | /api/v1/campaigns/{campaignId}/ad-sets | Create ad set | Request body | 201 |
| GET | /api/v1/campaigns/{campaignId}/ad-sets | List ad sets | — | 200 |
| GET | …/ad-sets/{adSetId} | Get ad set | — | 200 |
| PUT | …/ad-sets/{adSetId} | Update ad set | Request body | 200 |
| DELETE | …/ad-sets/{adSetId} | Delete ad set | — | 204 |
| POST | …/ad-sets/{adSetId}/publish | Publish ad set (after campaign) | Request body | 200 |
| POST | …/ad-sets/{adSetId}/sync | Sync ad set metrics | Request body | 200 |
| GET | …/ad-sets/{adSetId}/publications | List publications | — | 200 |
Ads
| Method | Path | Description | Body | Status |
|---|---|---|---|---|
| POST | …/ad-sets/{adSetId}/ads | Create ad creative | Request body | 201 |
| GET | …/ads?includePublications=true | List ads | — | 200 |
| GET | …/ads/{adId} | Get ad | — | 200 |
| PUT | …/ads/{adId} | Update ad | Request body | 200 |
| DELETE | …/ads/{adId} | Delete ad | — | 204 |
| POST | …/ads/{adId}/publish | Publish ad (after ad set) | Request body | 200 |
| POST | …/ads/{adId}/sync | Sync ad metrics | Request body | 200 |
| GET | …/ads/{adId}/publications | List publications | — | 200 |
Ad fields
Every field on a campaign, ad set, and ad. Times are ISO-8601 in UTC. On update, a field you omit keeps its current value. name is required when you create.
Campaign request body
POST /api/v1/campaigns and PUT /api/v1/campaigns/{id}. Update also accepts ?status= with a campaign status.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name. 1 to 255 characters. |
description | string | No | Longer description. Up to 5000 characters. |
platform | string | No | Ad network label, such as META or LINKEDIN. Up to 128 characters. |
objectiveType | string | No | Pinterest objective used at publish: AWARENESS, CONSIDERATION, VIDEO_VIEW, WEB_CONVERSION, CATALOG_SALES, or VIDEO_COMPLETION. Up to 64 characters. |
budgetAllocation | object | No | Map of platform name to amount, for example {"META": 5000, "LINKEDIN": 3000}. |
startAt | string | No | Scheduled start time. |
endAt | string | No | Scheduled end time. |
Campaign response body
| Field | Type | Description |
|---|---|---|
id | UUID | Athenous id of the campaign. |
userId | string | Account that owns the campaign. |
name | string | Campaign name. |
description | string or null | Description stored with the campaign. |
platform | string or null | Ad network stored on the campaign. |
objectiveType | string or null | Stored objective, used by Pinterest publish. |
budgetAllocation | object or null | Map of platform name to amount. |
status | string | DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, or ARCHIVED. |
startAt | string or null | Scheduled start time. |
endAt | string or null | Scheduled end time. |
createdAt | string | When the campaign was created. |
updatedAt | string | When the campaign was last saved. |
adSetCount | number or null | Number of ad sets. Present when includeCounts=true. |
adCount | number or null | Number of ads. Present when includeCounts=true. |
GET /api/v1/campaigns wraps campaigns in a page. page starts at 0. size is 1 to 1000, default 20.
| Field | Type | Description |
|---|---|---|
content | object[] | Campaigns on this page. |
totalElements | number | Total campaigns for this account. |
totalPages | number | Number of pages at the requested size. |
totalConversions | number | Sum of synced conversions across this account. Independent of the current page. |
totalActiveCampaigns | number | Count of campaigns whose status is ACTIVE. |
statusCounts | object | Map of each campaign status to how many campaigns have it. |
Ad set request body
POST /api/v1/campaigns/{campaignId}/ad-sets and PUT …/ad-sets/{adSetId}.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Ad set name. 1 to 255 characters. |
description | string | No | Description. Up to 2000 characters. |
status | string | No | DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, or ARCHIVED. |
dailyBudget | number | No | Daily budget amount. |
startAt | string | No | Start time. |
endAt | string | No | End time. |
targetingJson | string | No | Audience and placement options as a JSON string. Up to 10000 characters. |
objectiveType | string | No | Athenous objective, such as AWARENESS or TRAFFIC. Required when you publish a LinkedIn ad set. Up to 64 characters. |
Ad set response body
| Field | Type | Description |
|---|---|---|
id | UUID | Athenous id of the ad set. |
campaignId | UUID | Campaign this ad set belongs to. |
userId | string | Account that owns the ad set. |
name | string | Ad set name. |
description | string or null | Description. |
status | string | Same values as the request status. |
dailyBudget | number or null | Daily budget amount. |
startAt | string or null | Start time. |
endAt | string or null | End time. |
targetingJson | string or null | Stored targeting JSON. |
objectiveType | string or null | Stored objective. |
createdAt | string | When the ad set was created. |
updatedAt | string | When the ad set was last saved. |
Ad request body
POST …/ad-sets/{adSetId}/ads and PUT …/ads/{adId}.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Ad name. 1 to 255 characters. |
headline | string | No | Headline text. Up to 500 characters. |
bodyText | string | No | Primary text. Up to 5000 characters. |
imageUrl | string | No | Image URL. Up to 2048 characters. |
ctaLabel | string | No | Call-to-action label, such as Learn More or Sign Up. Up to 128 characters. |
ctaLink | string | No | Landing page opened when someone clicks the ad. Up to 2048 characters. |
platformCreativeId | string | No | Platform creative or asset id. Snapchat stores the creative built at publish. Spotify publish uses a READY audio or video asset id. Up to 255 characters. |
status | string | No | DRAFT, ACTIVE, PAUSED, or ARCHIVED. |
Ad response body
| Field | Type | Description |
|---|---|---|
id | UUID | Athenous id of the ad. |
adSetId | UUID | Ad set this ad belongs to. |
campaignId | UUID | Campaign this ad belongs to. |
userId | string | Account that owns the ad. |
name | string | Ad name. |
headline | string or null | Headline text. |
bodyText | string or null | Primary text. |
imageUrl | string or null | Image URL. |
ctaLabel | string or null | Call-to-action label. |
ctaLink | string or null | Landing page URL. |
status | string | DRAFT, ACTIVE, PAUSED, or ARCHIVED. |
createdAt | string | When the ad was created. |
updatedAt | string | When the ad was last saved. |
platformCreativeId | string or null | Platform creative or asset id saved after publish. |
publications | object[] or null | One object per network. Included when includePublications=true. Fields are listed under Ad publication. |
Ad publication
One object for each network the ad, ad set, or campaign was sent to.
| Field | Type | Description |
|---|---|---|
id | UUID | Id of this publication row. |
adId | UUID | Ad this row belongs to. |
adSetId | UUID | Ad set this row belongs to. |
campaignId | UUID | Campaign this row belongs to. |
platform | string | Network id, such as META or LINKEDIN. |
externalAdId | string or null | Id the network returned for the ad. |
externalAdSetId | string or null | Id the network returned for the ad set. |
status | string | Publication state on that network. |
metrics | object or null | Numbers from the last sync, such as impressions and clicks. |
lastSyncedAt | string or null | When metrics were last pulled. |
createdAt | string | When this publication row was created. |
updatedAt | string | When this publication row was last saved. |
Bulk delete response
POST /api/v1/campaigns/bulk-delete with {"ids": ["…"]}. ids is a required UUID array.
| Field | Type | Description |
|---|---|---|
requested | number | How many ids were sent. |
deleted | number | How many campaigns were removed. |
Publish request body
Fields for POST …/publish on a campaign, ad set, or ad.
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | META, LINKEDIN, TIKTOK, PINTEREST, SNAPCHAT, REDDIT, OPENAI_ADS, or SPOTIFY_ADS |
connectorId | UUID | Yes | Ads-purpose connection from GET /api/v1/connectors |
adAccountId | string | No | Platform ad account (act_… Meta, etc.) |
facebookPageId | string | No | Meta link ads / page-backed creatives |
objectiveType | string | No | Campaign/ad set objective (platform-specific) |
campaignBudgetOptimization | boolean | No | Pinterest CBO |
billableEvent | string | No | Pinterest: CLICKTHROUGH | IMPRESSION |
dailyBudgetOverride | number | No | Override ad set daily budget on publish |
pinId | string | No | Pinterest ad: existing pin id |
linkedinOffsiteDeliveryEnabled | boolean | No | LinkedIn Audience Network |
linkedinPoliticalIntent | string | No | POLITICAL | NOT_POLITICAL | NOT_DECLARED |
defaultTargetingCountries | string | No | Comma-separated ISO country codes (e.g. US,DE) |
Sync request body
Fields for POST …/sync on a campaign, ad set, or ad.
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Network to read metrics from. Same values as publish. |
connectorId | UUID | Yes | Connection Athenous uses to call that network. |
Status enums
| Entity | Values |
|---|---|
| Post | DRAFT, SCHEDULED, PUBLISHING, PUBLISHED, PARTIAL, FAILED, CANCELLED |
| Publication | PENDING, PUBLISHING, PUBLISHED, FAILED |
| Campaign | DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, ARCHIVED |
| AdSet | DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, ARCHIVED |
| Ad | DRAFT, ACTIVE, PAUSED, ARCHIVED |
HTTP errors
| Code | Meaning |
|---|---|
401 Unauthorized | Missing or invalid X-Api-Key header, or revoked key. |
403 Forbidden | Free/Starter plan, invalid API key, missing REST API scope, or a key that does not include this product (athenous_post or athenous_ad). |
404 Not found | Unknown id or ad set not under campaign. |
409 Conflict | Connector quota, duplicate publish, plan limits. |
422 Validation | Invalid body, channelSettings JSON, or missing connectorId. |
429 Rate limited | Retry with backoff; batch scope allows higher limits. |
502 Platform error | External network rejected publish; see publications[].errorMessage. |
Code examples
Start with curl, then switch to Python or Node. Replace connector IDs and your API key.
Create a multichannel post
curl -sS -X POST "https://public-api.athenous.io/api/v1/posts" \
-H "X-Api-Key: $ATHENOUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "API launch",
"body": "We shipped today.",
"mediaUrl": "https://storage.example/hero.jpg",
"channels": ["linkedin"],
"channelSettings": "{\"linkedin\":{\"contentType\":\"post\",\"connectorId\":\"CONNECTOR_UUID\"}}"
}'Upload a file and get a hosted mediaUrl
MEDIA_URL=$(curl -sS -X POST "https://public-api.athenous.io/api/v1/posts/media" \ -H "X-Api-Key: $ATHENOUS_API_KEY" \ -F "file=@./hero.jpg" | jq -r '.mediaUrl')
Publish campaign → ad set → ad (top-down)
# 1) Campaign
CID=$(curl -sS -X POST "https://public-api.athenous.io/api/v1/campaigns" \
-H "X-Api-Key: $ATHENOUS_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"API campaign","platform":"META","objectiveType":"AWARENESS"}' | jq -r '.id')
curl -sS -X POST "https://public-api.athenous.io/api/v1/campaigns/$CID/publish" \
-H "X-Api-Key: $ATHENOUS_API_KEY" -H "Content-Type: application/json" \
-d '{"platform":"META","connectorId":"CONNECTOR_UUID","adAccountId":"act_xxx","facebookPageId":"page_id"}'
# 2) Ad set → 3) Ad (same pattern under /ad-sets/.../ads/...)Create a post with requests
import json, os, requests
API = os.environ.get("ATHENOUS_API_URL", "https://public-api.athenous.io").rstrip("/")
KEY = os.environ["ATHENOUS_API_KEY"]
HEADERS = {"X-Api-Key": KEY}
settings = {
"linkedin": {"contentType": "post", "connectorId": os.environ["LINKEDIN_CONNECTOR_ID"]},
}
post = requests.post(
f"{API}/api/v1/posts",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"title": "From Python",
"body": "Hello API",
"channels": list(settings.keys()),
"channelSettings": json.dumps(settings),
},
).json()List posts with fetch
const API = (process.env.ATHENOUS_API_URL || "https://public-api.athenous.io").replace(/\/+$/,"");
const headers = { "X-Api-Key": process.env.ATHENOUS_API_KEY };
async function listPosts() {
const res = await fetch(`${API}/api/v1/posts?size=20&includePublications=true`, { headers });
return res.json();
}FAQ
How do I authenticate?
Create an API key in Settings → API Keys (Pro, Agency, or Enterprise) and send X-Api-Key: ath_… on every request. No separate token step.
Why is channelSettings a JSON string?
The request body stores channelSettings as a JSON string. Build the object in your language, then JSON.stringify before sending.
How does scheduling work?
Set scheduledAt on the post (and optionally per-channel scheduledAt in channelSettings). Athenous scheduler publishes SCHEDULED posts; or call POST …/publish immediately.
Can I publish to one channel only?
Set channels to a single id, or include only that channel in channelSettings with a valid connectorId.
Ads publish order?
Always: (1) POST campaign …/publish, (2) POST ad set …/publish, (3) POST ad …/publish. Sync with POST …/sync at any level.
Where do media URLs come from?
POST /api/v1/posts/media returns a hosted URL (GCS). Platforms fetch that URL at publish time; use upload rather than localhost links.
MCP (Claude, Cursor, ChatGPT)
Same key, natural language
Prefer agents over curl? Point Claude, Cursor, or ChatGPT at the Athenous MCP server with your existing API key.