AthenousDevelopers

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

MethodPathResult
POST/api/v1/posts201. Creates a draft.
GET/api/v1/postsPage. 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}/approveSets approvedAt.
POST/api/v1/posts/{id}/publishSends every channel now.
POST/api/v1/posts/{id}/retry-failedSends only channels whose last publish failed.
POST/api/v1/posts/{id}/syncPulls likes, comments, shares, impressions, views, and clicks.
GET/api/v1/posts/{id}/publicationsOne row per channel.
DELETE/api/v1/posts/{id}deleted and remoteDeleteWarnings.
GET/api/v1/posts/{id}/notesTeam notes on this post.
POST/api/v1/posts/{id}/notesBody {"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/mediaMultipart file. Returns mediaUrl.
POST/api/v1/posts/media/upload-sessionSigned upload for a large file.
GET/api/v1/posts/quota/monthlyMonthly post quota for this account.
GET/api/v1/posts/best-timesQuery channel, tzOffsetMinutes, top.
GET/api/v1/posts/platforms/threads/recentQuery connectorId, limit, optional after.
GET/api/v1/posts/platforms/tiktok/creator-infoQuery connectorId. Privacy options for that TikTok account.
GET/api/v1/posts/exportJSON export. Paid plans.
POST/api/v1/posts/importJSON import of an export payload. Paid plans. 201.
GET/api/v1/posts/export/csvCSV export. Paid plans.
POST/api/v1/posts/import/csvCSV body, Content-Type: text/csv. Paid plans. 201.
GET/api/v1/posts/import/templateCSV 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 });