Content model
Every publishable unit in NativPost is a content object. Whether it lands on X as a text post, on Instagram as a Reel, or on YouTube as a Short, the same underlying record drives it.
Anatomy
Section titled “Anatomy”{ "id": "9f2c8e7b-1a3d-4f60-b2c1-7e5a9d3c4b81", "object": "content", "caption": "Ship faster with NativPost.", "hashtags": ["saas", "buildinpublic"], "content_type": "reel", "topic": "Launch week", "media_urls": ["https://res.cloudinary.com/nativpost/video/upload/v1234/example.mp4"], "target_platforms": ["instagram", "tiktok"], "platform_specific": {}, "status": "scheduled", "scheduled_for": "2026-07-19T13:00:00Z", "published_at": null, "aspect_ratio": "9:16", "duration_seconds": 12.5, "campaign_id": null, "created_at": "2026-07-19T12:04:11Z", "updated_at": "2026-07-19T12:07:52Z"}That is the complete object — the serializer maps fields explicitly, so nothing else appears. Ids are plain UUIDs with no type prefix; object is what tells you the resource kind.
Fields
Section titled “Fields”| Field | Type | Notes |
|---|---|---|
id | string (UUID) | |
object | string | Always "content". |
caption | string | The post copy. Required on create, 1 to 4000 characters. |
hashtags | string[] | Without the leading #. Max 50. Defaults to []. |
content_type | string | See Content types. Defaults to single_image. |
topic | string | null | Free-text grouping label, up to 280 characters. |
media_urls | string[] | Absolute URLs, in order. Max 20. Defaults to []. |
target_platforms | string[] | Destinations. Max 10. Defaults to []. See Platforms. |
platform_specific | object | Per-platform overrides keyed by platform id. Defaults to {}. |
status | string | See Statuses. Defaults to draft. |
scheduled_for | string | null | ISO 8601. When the publish cron should pick it up. |
published_at | string | null | ISO 8601. Set by the publish cron. |
aspect_ratio | string | null | For example 9:16, 1:1. |
duration_seconds | number | null | Video length. Set by the engine, not by clients. |
campaign_id | string | null | UUID of the owning campaign, if any. |
created_at | string | ISO 8601. |
updated_at | string | ISO 8601. |
There is no workspace_id on the object. The API key already scopes every request to one workspace, so the field would be redundant.
Field groups
Section titled “Field groups”- Identity:
id,object,campaign_id. - Lifecycle:
status,scheduled_for,published_at,created_at,updated_at. - Copy:
caption,hashtags,topic. - Rendering:
content_type,media_urls,aspect_ratio,duration_seconds. - Targeting:
target_platformsplus per-platform overrides inplatform_specific.
Copy lives in one field
Section titled “Copy lives in one field”There is no separate body_text, hook_text, or slide_copy. All post copy goes in caption; hashtags are split out into their own array so the pipeline can place them per platform. Where a network needs different wording, override it in platform_specific rather than creating a second content object.
Media is attached by URL
Section titled “Media is attached by URL”media_urls holds absolute URLs, not media-asset ids. Browse the media library and copy the url off each asset. Rendered video and image output produced by the engine is written back into the same field.
Relationship to campaigns
Section titled “Relationship to campaigns”A content object can exist on its own or belong to a campaign via campaign_id. Campaigns define a plan — cadence, length, content mix — and generate child content asynchronously after launch. campaign_id is set by the generator; it is not a field you can pass on create or update. See Campaigns.
Editing after publish
Section titled “Editing after publish”The API does not freeze fields once status reaches published — a PATCH will still be accepted and will still update the stored record. It just has no effect on the live post: the platform already has its own copy. To change what is live, edit it in the platform’s own interface.
Note that PATCH will not accept published as a status value. That transition belongs to the publish cron.