Get Post
Retrieve details and publishing status of a post group.
Endpoint
GET https://api.publora.com/api/v1/get-post/:postGroupIdHeaders
| Header | Required | Description |
|---|---|---|
x-publora-key |
Yes | Your API key |
x-publora-user-id |
No | Managed user ID (workspace only) |
x-publora-client |
No | Client identifier (e.g., "mcp") |
Path Parameters
| Parameter | Type | Description |
|---|---|---|
postGroupId |
string | The post group ID returned from create-post |
Response
{
"success": true,
"postGroupId": "507f1f77bcf86cd799439011",
"status": "published",
"scheduledTime": "2026-02-22T14:30:00.000Z",
"platforms": ["twitter-123456789", "linkedin-ABC123"],
"platformSettings": {
"youtube": { "privacy": "public" }
},
"firstComment": {
"text": "Full write-up with the numbers: https://example.com/launch",
"platforms": ["linkedin"]
},
"firstComments": [
{ "text": "Full write-up with the numbers: https://example.com/launch", "platforms": ["linkedin"] }
],
"posts": [
{
"_id": "663a1b2c3d4e5f6a7b8c9d01",
"platform": "twitter",
"platformId": "123456789",
"content": "Excited to share our new product launch! 🚀",
"status": "published",
"postedId": "1234567890123456789",
"permalink": null,
"isThread": false,
"threadParts": [],
"firstCommentResult": null,
"firstCommentResults": []
},
{
"_id": "663a1b2c3d4e5f6a7b8c9d02",
"platform": "linkedin",
"platformId": "ABC123",
"content": "Excited to share our new product launch! 🚀",
"status": "published",
"postedId": "urn:li:share:7654321",
"permalink": null,
"isThread": false,
"threadParts": [],
"firstCommentResult": {
"status": "posted",
"commentId": "urn:li:comment:(urn:li:activity:7654321,1234)",
"skipReason": null,
"error": null,
"attemptedAt": "2026-02-22T14:30:04.120Z",
"postedAt": "2026-02-22T14:30:05.310Z"
},
"firstCommentResults": [
{
"status": "posted",
"commentId": "urn:li:comment:(urn:li:activity:7654321,1234)",
"skipReason": null,
"error": null,
"attemptedAt": "2026-02-22T14:30:04.120Z",
"postedAt": "2026-02-22T14:30:05.310Z"
}
]
}
],
"media": []
}Group-level fields
| Field | Type | Description |
|---|---|---|
postGroupId |
string | The post group ID |
status |
string | Group status (see Post Status Values) |
scheduledTime |
string/null | The effective scheduled time as actually stored by the server — null if the group has none |
platforms |
array | The effective platform list stored on the group (compound platform-platformId form) |
platformSettings |
object | The stored per-platform settings ({} if none were stored) |
firstComment |
object/null | Legacy alias for firstComments[0], or null when none is set |
firstComments |
array | Stored ordered first comments { text, platforms?, delaySeconds? }; old one-comment documents read as a one-element array without a migration |
posts |
array | One entry per platform target (see below) |
media |
array | Attached media inventory in group order; always present and empty when none is attached |
Read-after-write:
scheduledTime,platformsandplatformSettingsecho what the server actually stored, not what you sent. A permitted past-time clamp may changescheduledTime; schedule-horizon violations are rejected, not adjusted. See past scheduled times.
Media inventory (media[])
One entry per attached file, in the order the files are attached to the post.
| Field | Type | Description |
|---|---|---|
mediaId |
string | The media ID, used by DELETE /media/:mediaId and complete-media |
sourceFileName |
string/null | The client-facing file name: the cleaned fileName from get-upload-url or attach-media, or the URL's file name for mediaUrls |
fileName |
string/null | The storage key, e.g. images/1790000000000-0-1a2b3c4d-spring-launch-banner.png |
type |
string/null | image, video or document |
mimeType |
string/null | The MIME type of the stored file |
status |
string | uploading (not verified yet), ready, failed (see failureReason) or deleting. Files attached before statuses existed report ready |
failureReason |
string/null | Why validation failed, when status is failed |
url |
string/null | The public URL of the stored file |
Per-platform fields (posts[])
| Field | Type | Description |
|---|---|---|
_id |
string | The individual platform post ID |
platform |
string | Platform name (e.g., twitter) |
platformId |
string/null | Raw platform account ID — null when absent |
content |
string | The content published to this platform |
status |
string | Per-platform status |
postedId |
string/null | The platform's own ID for the published post — null until published |
permalink |
string/null | Public URL of the published post — null when unavailable |
isThread |
boolean | true when this target was published as a multi-part chain. Always present |
threadParts |
array | Per-part publication progress — see Thread parts. Always present, [] when the target is not a chain |
firstCommentResult |
object/null | Outcome of the first comment on this target — see First comment result. null when no comment applies to this target or the post is not published yet |
firstCommentResults |
array | Per-comment outcomes in order on this target; the singular field is an alias for item 0. An empty array means no result applies yet |
Stable shape:
platformId,postedIdandpermalinkare always present on everyposts[]entry. When a value is unavailable it is explicitlynull— the key never disappears, so you can read it without existence checks.
permalinkis not yet populated. The field is delivered end-to-end, but is currentlynullfor essentially all posts pending a separate backfill. Treat a non-nullpermalinkas a bonus, never a guarantee — to locate the live post today, resolve it fromplatform+platformId+postedId.
Note: With
.lean(), theerrorfield may be absent (undefined) for non-failed posts if the error subdocument was never populated — it will not necessarily benull. When a post has failed, theerrorfield contains an error object with details about the failure.
Note: For draft and scheduled posts,
postedIdisnull(present, but empty) since the post has not yet been published to the platform. Published posts normally carry a non-nullpostedId; a partially published X thread is the exception where afailedtarget can retain the head tweet ID inpostedId.
Note: The
platformIdfield returns the raw platform ID (e.g.,"123456789"), not the compound format used by list-posts (e.g.,"twitter-123456789"). See the list-posts endpoint for details on this difference.
Thread parts
X/Twitter and Meta Threads targets can publish one post as a chain of connected
parts. get-post reports that progress per target.
| Field | Type | Description |
|---|---|---|
index |
number | Zero-based position of the part in the chain |
content |
string | The text of that part, exactly as published |
status |
string | pending, published, or failed |
publishedId |
string/null | The platform's own ID for that part — null until confirmed |
{
"_id": "663a1b2c3d4e5f6a7b8c9d04",
"platform": "threads",
"platformId": "17841412345678",
"content": "The whole story, as submitted.\n\n---\n\nThe second half.",
"status": "published",
"postedId": "17900000000000001",
"permalink": null,
"isThread": true,
"threadParts": [
{ "index": 0, "content": "The whole story, as submitted.", "status": "published", "publishedId": "17900000000000001" },
{ "index": 1, "content": "The second half.", "status": "published", "publishedId": "17900000000000002" }
]
}postedId on the target is the first part's platform ID, which is also
threadParts[0].publishedId. There is no single ID that addresses the whole chain.
After a partial failure, read the parts before acting. A chain that published some parts and then failed leaves those parts live on the platform, with
status: "published"and apublishedId; the rest staypendingorfailedand the target carries the error. A partially published chain is terminal and is not retried, and on Threads a re-entry guard additionally refuses any chain that already has published parts. Recreating the post would duplicate the parts that already exist — inspectthreadPartsand the live account, then publish only what is missing.
Note:
threadPartsis exposed byget-postonly.list-postsdoes not include it, so fetch the group to inspect chain progress.
First comment result (posts[].firstCommentResult)
Read firstCommentResults[] in the same order as group firstComments[]. The legacy firstCommentResult mirrors item 0 and is null when no result applies. Comments become due on scheduler ticks in order; item N+1 waits until N is posted, and failure stops later items. A pending result may have a future dueAt.
| Field | Type | Description |
|---|---|---|
status |
string | pending (waiting for its due time or attempt), posted, failed, skipped |
dueAt |
string/null | Earliest scheduler time for this comment (ISO 8601 UTC) |
commentId |
string/null | The platform's ID of the comment: LinkedIn urn:li:comment:… URN, X tweet ID, Threads media ID, Bluesky at:// URI, Mastodon status ID |
skipReason |
string/null | Why nothing was attempted, such as unsupported_platform, feature_disabled, or an unavailable connection. Older records may retain workspace_post or unsupported_account_type |
attemptedAt |
string/null | When the attempt was claimed (ISO 8601 UTC) |
postedAt |
string/null | When the platform confirmed the comment |
error |
object/null | For failed: { code, message, platformStatusCode, outcomeUnknown }. outcomeUnknown: true means the request was sent but no definitive answer came back — the comment may exist; check the platform before commenting manually. |
Error codes specific to the comment: FIRST_COMMENT_PERMISSION_REQUIRED (the connection lacks the reply permission — reconnect it), FIRST_COMMENT_TARGET_UNAVAILABLE (the platform returned no ID for the published post), FIRST_COMMENT_NOT_ATTEMPTED (the publish was recovered late, so no comment was posted), FIRST_COMMENT_INTERRUPTED (the publishing worker stopped mid-way), FIRST_COMMENT_DEADLINE_EXCEEDED (the platform did not answer within the time budget). The general codes below (PLATFORM_AUTH_EXPIRED, RATE_LIMITED, X_REPLY_NOT_AUTHORIZED, …) can appear here too. A comment is never retried; the post itself is unaffected by a failed comment.
Failed Post Response
When a post fails, the response includes detailed error information:
{
"success": true,
"postGroupId": "507f1f77bcf86cd799439011",
"posts": [
{
"_id": "663a1b2c3d4e5f6a7b8c9d03",
"platform": "threads",
"platformId": "17841412345678",
"content": "Check out our new feature!",
"status": "failed",
"postedId": null,
"permalink": null,
"error": {
"code": "PLATFORM_AUTH_EXPIRED",
"message": "Error validating access token",
"platformStatusCode": 401,
"platformError": "Invalid OAuth access token",
"failedAt": "2026-02-22T14:30:00.000Z",
"retryable": false
}
}
]
}Post Status Values
| Status | Meaning |
|---|---|
draft |
Saved but not yet scheduled |
scheduled |
Waiting for scheduled time |
pending |
Being processed by scheduler |
processing |
Currently publishing to platform |
published |
Successfully posted |
failed |
Publishing failed (see error object for details) |
Note: The statuses above apply to individual platform posts. The post group itself can also have a status of
partially_published, which occurs when some platform posts in the group succeeded while others failed. For example, if a post group targets Twitter, LinkedIn, and Threads, and the Threads post fails while the others succeed, the group status will bepartially_published. Query individual posts within the group to determine which platforms succeeded or failed.
Error Codes
When status is failed, the error object contains:
| Field | Type | Description |
|---|---|---|
code |
string | Error classification code (see table below) |
message |
string | Human-readable error message |
platformStatusCode |
number/undefined | HTTP status code from the platform API (field may be absent rather than explicitly null) |
platformError |
string/null | Raw error message from the platform |
failedAt |
string | Timestamp when the failure occurred |
retryable |
boolean | Whether the error might succeed on retry |
| Error Code | Description | Retryable |
|---|---|---|
PLATFORM_AUTH_EXPIRED |
Access token expired or revoked | No |
RATE_LIMITED |
Platform rate limit exceeded | Yes |
INVALID_CONTENT |
Content rejected by platform (e.g., too long, banned words) | No |
CONTENT_TOO_LARGE |
Media file exceeds platform limits | No |
PLATFORM_SERVER_ERROR |
Platform API returned 5xx error | Yes |
NETWORK_ERROR |
Could not reach platform API | Yes |
TIMEOUT_ERROR |
Request timed out | Yes |
X_REPLY_NOT_AUTHORIZED |
X refused a platformSettings.twitter reply or quote: on self-serve API tiers the target's author must have mentioned this account in that post, quoted one of its posts, or the account must have written the target itself |
No |
X_TARGET_REJECTED |
The platformSettings.twitter reply/quote target is unusable — deleted, protected, or its author blocked this account |
No |
UNKNOWN_ERROR |
Unclassified error | Maybe |
Note: The error codes listed above are not exhaustive. The schema does not validate error codes, so additional codes beyond these eight may appear in the response. Always handle unknown codes gracefully.
Examples
JavaScript (fetch)
const postGroupId = '507f1f77bcf86cd799439011';
const response = await fetch(
`https://api.publora.com/api/v1/get-post/${postGroupId}`,
{ headers: { 'x-publora-key': 'YOUR_API_KEY' } }
);
const data = await response.json();
for (const post of data.posts) {
console.log(`${post.platform}: ${post.status}`);
if (post.postedId) {
console.log(` Platform post ID: ${post.postedId}`);
}
}Python (requests)
import requests
post_group_id = '507f1f77bcf86cd799439011'
response = requests.get(
f'https://api.publora.com/api/v1/get-post/{post_group_id}',
headers={'x-publora-key': 'YOUR_API_KEY'}
)
data = response.json()
for post in data['posts']:
print(f"{post['platform']}: {post['status']}")cURL
curl https://api.publora.com/api/v1/get-post/507f1f77bcf86cd799439011 \
-H "x-publora-key: YOUR_API_KEY"Node.js (axios)
const axios = require('axios');
const { data } = await axios.get(
`https://api.publora.com/api/v1/get-post/${postGroupId}`,
{ headers: { 'x-publora-key': 'YOUR_API_KEY' } }
);
// Check if all posts published successfully
const allPublished = data.posts.every(p => p.status === 'published');
console.log(`All published: ${allPublished}`);Errors
| Status | Error | Cause |
|---|---|---|
| 400 | "Invalid post group ID" |
postGroupId is not a valid ObjectId |
| 400 | "Invalid x-publora-user-id" |
The x-publora-user-id header value is not a valid ObjectId format |
| 401 | "API key is required" |
Missing x-publora-key header |
| 401 | "Invalid API key" |
The provided API key is not valid |
| 401 | "Invalid API key owner" |
The API key exists but its owner account could not be found |
| 403 | "API access is not enabled for this account" |
The account does not have API access enabled |
| 403 | "MCP access is not enabled for this account" |
The account does not have MCP access enabled (MCP-only keys) |
| 403 | "Workspace access is not enabled for this key" |
The API key does not have workspace/managed-user permissions |
| 403 | "User is not managed by key" |
The x-publora-user-id references a user not managed by this API key |
| 404 | "Post group not found" |
No post with this ID belongs to the acting user |
| 500 | "Failed to fetch post group" |
Internal server error |
| 500 | "Internal server error" |
Unexpected server error in middleware |
Note: If
x-publora-user-idmatches the API key owner, no workspace check is triggered — the header is effectively a no-op in that case.
Note: A
postGroupIdthat is not a valid MongoDB ObjectId returns400 { "error": "Invalid post group ID" }before any lookup. Pass the IDs returned by create-post or list-posts.
Publora is built by Creative Content Crafts, Inc. Need AI-powered content creation for LinkedIn, Threads, and X? Try Co.Actor — the best AI service for authentic thought leadership at scale.