Composing a post
Open the post editor
From the dashboard, click New Post to open the post composer. You can also navigate directly to
/dashboard and use the compose button in the top navigation.Write your content
Enter your post text in the main content area. The
content.text field is the primary copy that goes to all selected platforms. You can use hashtags inline — they are passed as plain text and each platform renders them natively.Upload media (optional)
Click Add Media to attach images or videos. Hayon generates a pre-signed S3 upload URL for each file before it is stored. See Media uploads below for the full flow.
Select platforms
Choose one or more platforms to publish to. At least one platform must be selected. The
selectedPlatforms field accepts any combination of:bluesky · threads · instagram · facebook · mastodon · tumblrAdd platform-specific content (optional)
Expand the Per-platform settings panel to override the default caption or attach different media for a specific platform. These overrides are stored in
platformSpecificContent as a keyed object (e.g. platformSpecificContent.instagram.text).Media uploads
Hayon uses a pre-signed URL flow to upload media directly from your browser to AWS S3, keeping large files off the application server.Request an upload URL
Your client calls The server returns a time-limited pre-signed
POST /api/posts/media/upload with the file’s MIME type:request body
uploadUrl, a public s3Url, and an s3Key.response
Upload directly to S3
Perform a
PUT request to the uploadUrl with the raw file bytes and a Content-Type header matching the requested MIME type. No backend involvement is needed at this step.Allowed MIME types:
image/png, image/jpeg, image/jpg, image/webp, video/mp4, video/quicktime. Any other type returns a 400 error.The post data model
Every post is stored in MongoDB with the following structure.Top-level fields
Top-level fields
| Field | Type | Description |
|---|---|---|
userId | ObjectId | Reference to the owning user. |
content.text | string | Main post body. Required. |
content.mediaItems | array | Attached media. See media item fields below. |
selectedPlatforms | string[] | Platforms this post targets. |
platformSpecificContent | object | Per-platform text/media overrides. |
scheduledAt | Date | Future publish time. Null for immediate posts. |
timezone | string | IANA timezone string. Defaults to UTC. |
status | string | Overall post status. See status lifecycle. |
platformStatuses | array | Per-platform delivery status. |
correlationId | string | Unique ID for deduplication (sparse index). |
metadata.source | string | web or api. |
createdAt | Date | Managed by Mongoose timestamps. |
updatedAt | Date | Managed by Mongoose timestamps. |
Media item fields
Media item fields
| Field | Type | Description |
|---|---|---|
s3Key | string | S3 object key: posts/user123/image.jpg. |
s3Url | string | Full CDN URL to the file. |
mimeType | string | File MIME type. |
originalFilename | string | Original filename from the upload. |
sizeBytes | number | File size in bytes. |
width | number | Image width in pixels (optional). |
height | number | Image height in pixels (optional). |
duration | number | Video length in seconds (optional). |
Platform status fields
Platform status fields
Each entry in
platformStatuses tracks delivery for a single platform.| Field | Type | Description |
|---|---|---|
platform | string | Platform name (e.g. bluesky). |
status | string | pending · processing · completed · failed · deleted |
platformPostId | string | ID returned by the platform API after publishing. |
platformPostUrl | string | Direct URL to the published post. |
error | string | Error message if delivery failed. |
attemptCount | number | Number of delivery attempts. |
lastAttemptAt | Date | Timestamp of the most recent attempt. |
completedAt | Date | Timestamp when the platform accepted the post. |
lastAnalyticsFetch | Date | When analytics were last fetched for this platform. |
Post status lifecycle
Thestatus field on a post reflects its overall delivery state across all selected platforms.
DRAFT
Saved but not submitted to the queue. The post can be edited freely and published later.
PENDING
Queued for immediate delivery. The worker picks it up as soon as a consumer is available.
SCHEDULED
Has a future
scheduledAt time. The scheduler enqueues it at the right moment.PROCESSING
At least one platform worker is actively publishing.
COMPLETED
All platforms accepted the post successfully.
PARTIAL_SUCCESS
Some platforms succeeded and at least one failed.
FAILED
All platforms failed. Use retry to re-enqueue.
CANCELLED
Manually cancelled before the worker consumed it.
Platform-specific content
You can supply different text or media for individual platforms usingplatformSpecificContent. The worker merges this with the base content at publish time: platform-specific text overrides content.text, and platform-specific mediaItems override content.mediaItems.
Example: different captions per platform
Creating a post via API
POST /api/posts
postId, the resolved status, and the scheduledAt value:
201 response
Saving as draft
Pass"status": "DRAFT" in the request body to save without publishing. Draft posts are stored in MongoDB but never enqueued. You can edit them later and change the status to PENDING or SCHEDULED to trigger delivery.
Draft request body
DRAFT to PENDING or SCHEDULED), Hayon resets createdAt to the current time so it appears at the top of the post history.
Viewing post history
The History page at/history lists all your posts with their current status. You can filter by status and sort by creation date. Use GET /api/posts with optional query parameters:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number. |
limit | 20 | Results per page. |
status | — | Filter by post status. |
sortBy | createdAt | Field to sort by. |
sortOrder | desc | asc or desc. |
