Posts
One post can go to several networks. Persist it, approve it when you want it on the calendar, then publish or wait for scheduledAt. Send X-Api-Key. Field limits are in the reference. Channel ids are on Platforms.
Endpoints
| Method | Path | Result |
|---|---|---|
| POST | /api/v1/posts | 201. Creates a draft. |
| GET | /api/v1/posts | Page. page, size, repeated status, includePublications. |
| GET | /api/v1/posts/{id} | One post. |
| PUT | /api/v1/posts/{id}?status= | Update. Omitted fields stay as they are. A draft must be approved before SCHEDULED. |
| POST | /api/v1/posts/{id}/approve | Sets approvedAt. |
| POST | /api/v1/posts/{id}/publish | Sends every channel now. |
| POST | /api/v1/posts/{id}/retry-failed | Sends only channels whose last publish failed. |
| POST | /api/v1/posts/{id}/sync | Pulls likes, comments, shares, impressions, views, and clicks. |
| GET | /api/v1/posts/{id}/publications | One row per channel. |
| DELETE | /api/v1/posts/{id} | deleted and remoteDeleteWarnings. |
| GET | /api/v1/posts/{id}/notes | Team notes on this post. |
| POST | /api/v1/posts/{id}/notes | Body {"body":"…"}. Drafts only. 2000 characters or fewer. |
| DELETE | /api/v1/posts/{id}/notes/{noteId} | The author of the note can remove it. |
| POST | /api/v1/posts/media | Multipart file. Returns mediaUrl. |
| POST | /api/v1/posts/media/upload-session | Signed upload for a large file. |
| GET | /api/v1/posts/quota/monthly | Monthly post quota for this account. |
| GET | /api/v1/posts/best-times | Query channel, tzOffsetMinutes, top. |
| GET | /api/v1/posts/platforms/threads/recent | Query connectorId, limit, optional after. |
| GET | /api/v1/posts/platforms/tiktok/creator-info | Query connectorId. Privacy options for that TikTok account. |
| GET | /api/v1/posts/export | JSON export. Paid plans. |
| POST | /api/v1/posts/import | JSON import of an export payload. Paid plans. 201. |
| GET | /api/v1/posts/export/csv | CSV export. Paid plans. |
| POST | /api/v1/posts/import/csv | CSV body, Content-Type: text/csv. Paid plans. 201. |
| GET | /api/v1/posts/import/template | CSV template. Paid plans. |
Status values: DRAFT, SCHEDULED, PUBLISHING, PUBLISHED, PARTIAL, FAILED, CANCELLED. A publication is PENDING, PUBLISHING, PUBLISHED, or FAILED. PARTIAL means at least one channel published and at least one failed.
Create a multi-channel draft
channelSettings is a JSON string. Build the object in your language, then stringify it. Each channel that should publish needs its own connectorId from GET /api/v1/connectors.
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": "March update",
"body": "Hello from the API",
"mediaUrl": "https://storage.googleapis.com/your-bucket/hero.jpg",
"channels": ["linkedin", "facebook"],
"channelSettings": "{\"linkedin\":{\"contentType\":\"post\",\"connectorId\":\"LINKEDIN_CONNECTOR\"},\"facebook\":{\"contentType\":\"feed\",\"connectorId\":\"FACEBOOK_CONNECTOR\",\"page_id\":\"123456789\"}}"
}'
Facebook feed, LinkedIn post, a blank Bluesky type, and Threads text with media are expanded to the network’s real type at publish time. A per-channel scheduledAt overrides the post time for that channel only. LinkedIn privacy only_me skips publish.
Approve, then schedule:
curl -sS -X POST "https://public-api.athenous.io/api/v1/posts/$POST_ID/approve" \
-H "X-Api-Key: $ATHENOUS_API_KEY"
curl -sS -X PUT "https://public-api.athenous.io/api/v1/posts/$POST_ID?status=SCHEDULED" \
-H "X-Api-Key: $ATHENOUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scheduledAt":"2026-03-20T14:00:00Z"}'
Editing the title, body, media, or channels clears approvedAt. Scheduling before approve returns a 4xx whose detail is Approve this draft before it can be scheduled. Publish now with POST /api/v1/posts/{id}/publish. Read failures on publications[].errorMessage, then call retry-failed.
Response
Create, get, update, approve, and publish return this object. A list puts the same objects in content, with totalElements and totalPages. publications is present when you pass includePublications=true, and on publish.
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "March update",
"body": "Hello from the API",
"mediaUrl": "https://storage.googleapis.com/your-bucket/hero.jpg",
"status": "DRAFT",
"scheduledAt": null,
"publishedAt": null,
"approvedAt": null,
"channels": ["linkedin", "facebook"],
"channelSettings": "{\"linkedin\":{\"contentType\":\"post\",\"connectorId\":\"…\"}}",
"publications": null,
"createdAt": "2026-03-18T10:00:00Z",
"updatedAt": "2026-03-18T10:00:00Z"
}
Media
Networks fetch mediaUrl when the post publishes. Upload first. Files under about 90 MiB can use multipart. Larger files use a signed session so the bytes go straight to storage.
curl -sS -X POST "https://public-api.athenous.io/api/v1/posts/media" \
-H "X-Api-Key: $ATHENOUS_API_KEY" \
-F "file=@./hero.jpg"
curl -sS -X POST "https://public-api.athenous.io/api/v1/posts/media/upload-session" \
-H "X-Api-Key: $ATHENOUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileName":"video.mp4","contentType":"video/mp4","byteSize":104857600}'
The session returns uploadUrl, mediaUrl, requiredContentType, and expiresInSeconds. PUT the file bytes to uploadUrl with Content-Type set to requiredContentType. Store mediaUrl on the post. Carousel URLs go in media_urls inside that channel’s settings. A 403 on the storage PUT means the bucket CORS policy does not allow that origin.
Notes
Notes stay on a draft. You can add one only while status is DRAFT. The body is required and must be 2000 characters or fewer. Only the author can delete a note.
curl -sS -X POST "https://public-api.athenous.io/api/v1/posts/$POST_ID/notes" \
-H "X-Api-Key: $ATHENOUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Hold for legal review"}'
Helpers
curl -sS "https://public-api.athenous.io/api/v1/posts/platforms/tiktok/creator-info?connectorId=CONNECTOR_UUID" \
-H "X-Api-Key: $ATHENOUS_API_KEY"
curl -sS "https://public-api.athenous.io/api/v1/posts/platforms/threads/recent?connectorId=CONNECTOR_UUID&limit=25" \
-H "X-Api-Key: $ATHENOUS_API_KEY"
curl -sS "https://public-api.athenous.io/api/v1/posts/best-times?channel=linkedin&tzOffsetMinutes=0&top=5" \
-H "X-Api-Key: $ATHENOUS_API_KEY"
TikTok tiktok_privacy_level must be one of the options returned for that account. SELF_ONLY is Only me. GET /api/v1/posts/quota/monthly is the monthly cap for this account.
Import and export
Paid plans. A free plan receives 403. Export JSON, edit it, and post it back to import. CSV import sends the file body with Content-Type: text/csv; charset=UTF-8. Download the column header row from GET /api/v1/posts/import/template first.
Python and Node
import json, os, requests
API = os.environ["ATHENOUS_API_URL"].rstrip("/")
headers = {"X-Api-Key": os.environ["ATHENOUS_API_KEY"]}
settings = {"linkedin": {"contentType": "post", "connectorId": os.environ["LINKEDIN_CONNECTOR_ID"]}}
created = requests.post(f"{API}/api/v1/posts", headers=headers, json={
"body": "Hi",
"channels": ["linkedin"],
"channelSettings": json.dumps(settings),
}).json()
post_id = created["id"]
requests.post(f"{API}/api/v1/posts/{post_id}/approve", headers=headers)
requests.post(f"{API}/api/v1/posts/{post_id}/publish", headers=headers)
const API = process.env.ATHENOUS_API_URL.replace(/\/+$/, "");
const headers = { "X-Api-Key": process.env.ATHENOUS_API_KEY, "Content-Type": "application/json" };
const settings = { linkedin: { contentType: "post", connectorId: process.env.LINKEDIN_CONNECTOR_ID } };
const created = await fetch(`${API}/api/v1/posts`, {
method: "POST",
headers,
body: JSON.stringify({
body: "Hi",
channels: ["linkedin"],
channelSettings: JSON.stringify(settings),
}),
}).then((r) => r.json());
await fetch(`${API}/api/v1/posts/${created.id}/approve`, { method: "POST", headers });
await fetch(`${API}/api/v1/posts/${created.id}/publish`, { method: "POST", headers });