Skip to content

Publishing a scheduled post (curl)

This guide walks a complete happy path: pick media, create a scheduled post, publish it, and confirm the webhook fired.

  • An API key exported as NATIVPOST_API_KEY.
  • At least one connected social account. This guide targets X, whose platform id is twitter.
  • jq for reading JSON out of the responses.

Two setup steps happen in the dashboard, not over the API:

  • Media — the API cannot upload. Add files under Content → Media, or host them yourself and use your own URLs.
  • Webhooks — the API cannot create endpoints. Add one under Settings → Webhooks, subscribe it to content.published and content.publish_failed, and copy its signing secret from there.
Terminal window
MEDIA_URL=$(curl -sS "https://app.nativpost.com/api/v1/media-library?asset_type=image&limit=1" \
-H "Authorization: Bearer $NATIVPOST_API_KEY" \
| jq -r ".data[0].url")
echo "Using $MEDIA_URL"

Content references media by URL, not by asset id, so url is the field you want. Any publicly reachable HTTPS URL works here — the library is just the convenient source.

Terminal window
curl -sS https://app.nativpost.com/api/v1/webhooks \
-H "Authorization: Bearer $NATIVPOST_API_KEY" | jq
{
"object": "list",
"data": [
{
"id": "c81f0d3a-52b7-4e19-9f26-8a7b1c0d4e35",
"object": "webhook_endpoint",
"url": "https://webhook.site/your-uuid-here",
"events": ["content.published", "content.publish_failed"],
"description": null,
"enabled": true,
"created_at": "2026-07-19T11:40:00Z",
"updated_at": "2026-07-19T11:40:00Z"
}
],
"has_more": false,
"next_cursor": null
}

Signing secrets are never returned by the API — read yours from the dashboard.

If events is an empty array, the endpoint receives every event rather than none.

3. Create the post, scheduled for one hour from now

Section titled “3. Create the post, scheduled for one hour from now”
Terminal window
SCHEDULED_FOR=$(date -u -d "+1 hour" +"%Y-%m-%dT%H:%M:%SZ")
CONTENT_ID=$(curl -sS -X POST https://app.nativpost.com/api/v1/content \
-H "Authorization: Bearer $NATIVPOST_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg m "$MEDIA_URL" \
--arg s "$SCHEDULED_FOR" \
'{
caption: "Launch day.",
content_type: "single_image",
target_platforms: ["twitter"],
media_urls: [$m],
status: "scheduled",
scheduled_for: $s
}')" \
| jq -r ".id")
echo "Created $CONTENT_ID for $SCHEDULED_FOR"

The field names matter: caption for the copy, target_platforms for destinations, media_urls for media, scheduled_for for the time. content_type is single_image — there is no photo type.

CONTENT_ID comes back as a plain UUID.

Terminal window
curl -sS https://app.nativpost.com/api/v1/content/$CONTENT_ID \
-H "Authorization: Bearer $NATIVPOST_API_KEY" | jq '.status, .scheduled_for'

Expect "scheduled" and the timestamp from step 3.

Terminal window
curl -sS -X POST https://app.nativpost.com/api/v1/content/$CONTENT_ID/publish \
-H "Authorization: Bearer $NATIVPOST_API_KEY" | jq

Returns 202 Accepted: the serialized content with status flipped to approved and one extra flag.

{
"id": "9f2c8e7b-1a3d-4f60-b2c1-7e5a9d3c4b81",
"object": "content",
"status": "approved",
"scheduled_for": "2026-07-19T12:31:00Z",
"publish_queued": true
}

This queues the post — it does not call X inline, so there are no per-platform results in the response. The cron picks it up on its next tick, typically under a minute.

If target_platforms were empty, this call would fail instead:

{
"error": {
"code": "no_target_platforms",
"message": "Set target_platforms on the content item before publishing.",
"docs_url": "https://docs.nativpost.com/errors"
}
}

Poll until the cron has run:

Terminal window
watch -n 15 "curl -sS https://app.nativpost.com/api/v1/content/$CONTENT_ID \
-H 'Authorization: Bearer $NATIVPOST_API_KEY' | jq -r '.status'"

approvedpublishingpublished. If every platform rejects the post, the status returns to approved rather than moving to a failure state — so approved after a publish attempt means “failed, try again”.

Meanwhile your endpoint receives the real answer:

{
"id": "b3d1f0a6-77c2-4e88-9a41-2f6e8c5d0b19",
"event": "content.published",
"created_at": "2026-07-19T12:31:41Z",
"data": {
"content": { "id": "9f2c8e7b-1a3d-4f60-b2c1-7e5a9d3c4b81", "object": "content" },
"published_at": "2026-07-19T12:31:40Z",
"platforms": [
{ "platform": "twitter", "success": true, "platform_post_id": "1789...", "error": null }
]
}
}

There is no delivery-log endpoint on /api/v1. Check deliveries in the dashboard under Settings → Webhooks, which shows status code, response body, and duration per attempt.

Worth knowing: each event is attempted once, with a 10-second timeout. There is no retry on failure. After 20 consecutive failures the endpoint is disabled automatically.

Wrap steps 3 to 5 in your own script, or move to the Zapier and n8n guides for no-code versions of the same flow.