Create a promoted activity — server token only
const url = 'http://localhost:3000/v1/promoted';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"actor":"example","verb":"example","object":"example","custom":{},"audience":["example"],"starts_at":"2026-04-15T12:00:00Z","expires_at":"2026-04-15T12:00:00Z"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:3000/v1/promoted \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "actor": "example", "verb": "example", "object": "example", "custom": {}, "audience": [ "example" ], "starts_at": "2026-04-15T12:00:00Z", "expires_at": "2026-04-15T12:00:00Z" }'Promoted content: an activity-shaped row that is served in the
promoted sidecar of a feed read regardless of the follow graph and
recency. Nothing is fanned out — one row serves every eligible reader,
which is what makes retraction and expiry instant and free.
Targeting is the follow graph (audience), because we hold no user
attributes: create a feed such as city:belgrade, follow users into it
from your backend, then target that feed. Omit audience to reach
everyone, including brand-new users whose feed is otherwise empty.
This is promoted content, not an ad platform: no bidding, no demographic targeting, no viewability tracking. The user-facing label (“Sponsored”, “Featured”) — and any disclosure obligation that comes with paid placement — is yours to render.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Free-form and never overwritten — these routes are server-token only, so there is no impersonation surface. “sponsor:nike”, “system:fcurban”.
object
Omit or null to reach everyone. Otherwise feed refs the caller must follow — targeting is the follow graph, since we hold no user attributes (spec §5). Create a feed such as city:belgrade, follow users into it from your backend, then target it. Semantics across entries is OR. An empty array is rejected: use null for “everyone”.
Defaults to now. Not served before this instant.
Must be after starts_at, and in the future. Null means “until retracted”. Expiry needs no cleanup — nothing was fanned out.
Examplegenerated
{ "actor": "example", "verb": "example", "object": "example", "custom": {}, "audience": [ "example" ], "starts_at": "2026-04-15T12:00:00Z", "expires_at": "2026-04-15T12:00:00Z"}Responses
Section titled “Responses”Created.
The full row as returned to the tenant’s backend by GET /v1/promoted.
object
object
Null means every user. Otherwise feed refs; a caller matching ANY of them is eligible.
Feed opens that received this row — deliveries, not views. Repeat placement within one feed session does not increment it, and a viewport impression is not observable server-side; count those client-side via the react SDK callbacks.
Set when retracted. Retracted rows stay listed here but are never served.
Example
{ "actor": "system:fcurban", "audience": [ "city:belgrade" ]}Invalid body — empty audience array (use null), more than 20
refs, a malformed feed ref, expires_at at or before starts_at
or in the past.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}Invalid, expired, revoked, or absent token.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}User tokens cannot manage promoted activities — these routes are server token only.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}This tenant already has 100 live (non-retracted) promoted activities. An abuse guard, not a plan limit — retract something.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}Rate limit exceeded.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}