---
name: put-an-item-on-a-public-feed
description: Publish one item to a feed strangers read, with the address assigned once by the server and kept even after the item comes off again.
runbook:
  schema_version: v1
  semver: 1.0.0
  profile: P1
  min_runtime_profile: R1
  domain: data-pipelines
  targets:
    - postgres
  trigger: manual
  execution: human-with-agent
  risk: reversible-write
  blast_radius: global
  duration: <5m
  capabilities:
    - cli:curl
  publisher: std
  lang: en
  license: CC-BY-4.0
  inputs:
    item_id:
      type: string
      required: true
      description: the item being considered for the feed
    admin_api:
      type: string
      required: true
      description: base address of the administrative API that owns the flag
  steps:
    - id: s1
      kind: decision
      title: Is this fit for a stranger to read?
      question: Not "what stage are we at with it" — would somebody with no context be well served by it?
      branches:
        not_fit: s8
        default: s2
    - id: s2
      kind: check
      title: Whether it has been on the feed before
      tool: cli:curl
      command: curl -sf "{{inputs.admin_api}}/items/{{inputs.item_id}}"
      expect: the item's record, showing whether it already carries a public address
      assert:
        exit_code: 0
      on_fail: escalate:s7
      next: s3
    - id: s3
      kind: action
      title: Turn the visible flag on
      risk: reversible-write
      tool: cli:curl
      command: curl -sf -X PATCH "{{inputs.admin_api}}/items/{{inputs.item_id}}" -d '{"public":true}'
      expect: the item is accepted onto the feed
      rollback_ref: s6
      on_fail: escalate:s7
      next: s4
    - id: s4
      kind: check
      title: The server assigned the address, not the caller
      tool: cli:curl
      command: curl -sf "{{inputs.admin_api}}/items/{{inputs.item_id}}"
      expect: an address and a publication time the server computed, unchanged by anything sent
      assert:
        exit_code: 0
      on_fail: escalate:s7
      next: s5
    - id: s5
      kind: decision
      title: Does it read correctly to a stranger?
      question: Opened without an account, does the public page show the item as intended?
      branches:
        wrong: s6
        default: s9
    - id: s6
      kind: action
      title: Take it off the feed, keeping its address
      risk: reversible-write
      tool: cli:curl
      command: curl -sf -X PATCH "{{inputs.admin_api}}/items/{{inputs.item_id}}" -d '{"public":false}'
      expect: the item leaves the feed, the address it was given stays with it
      on_fail: escalate:s7
      next: end:aborted
    - id: s9
      kind: action
      title: Note the address it now holds
      risk: read-only
      expect: the address is recorded where the item is managed, since it will not change again
      next: end:success
    - id: s7
      kind: escalate
      title: Escalate to whoever owns the feed
      to: team:platform
    - id: s8
      kind: action
      title: Record why it is not for the feed
      risk: read-only
      expect: the decision is written down, so it is not reconsidered from scratch next week
      next: end:aborted
  source:
    url: https://runbooks.directory/r/std/put-an-item-on-a-public-feed/source.md
    fetched_at: 2026-09-04
    upstream_state: current
---

## When to reach for this

Something in an internal system is going to be shown to people outside it — a listing, a
profile, a post, a case study. The item already exists and already has a life inside; what
is being decided is whether it gets an address of its own that strangers can hold.

## Visibility is not status

The most common way to get this wrong is to derive the public flag from the internal one.
They answer different questions:

- **Status** says where *we* are with the item: in progress, answered, rejected, withdrawn.
- **Visibility** says whether the item serves a reader who knows nothing about us.

An item can be interesting internally and unfit to show — half-written, specific to one
conversation, unfair to somebody. And an item can be finished internally, its status long
since closed, and still be exactly what an outside reader wants. Neither implies the other,
which is why the flag is separate and why it is set by a person.

Deriving it is attractive because it removes a decision. What it actually removes is the
place where somebody looks at the item and asks whether it should be public.

## The address is assigned once and outlives the item's stay

Three rules, and each of them has a failure behind it.

**The server assigns the address.** Not the caller. A caller that can choose its own address
can take one that belongs to something else and can rewrite the order of the feed. Values
sent for the address or the publication time are ignored rather than rejected — an
ignored field is a field that cannot be smuggled through by a client that stops reading
error responses.

**Editing the item does not recompute the address.** If the address is derived from the
title, then correcting a typo in the title breaks every search result, every link somebody
saved and every repost — all of them into a 404, silently, days later. Compute it once, at
the first publication, and never again.

**Taking the item off the feed keeps its address.** Clear the publication time, leave the
address alone. Items come back, and when they do they must come back where they were: the
same links have to resolve to the same thing. An address reused for something else is worse
than a 404, because it answers.

## Check it without an account

The person publishing is signed in, and signed in is not what a stranger sees. Open the
public address in a browser that has no session, or in a private window — a feed that is
correct for its author and empty for everybody else is a common enough failure that it is
worth the ten seconds.

## Publishing a batch

The same rules, with two additions. Count before you publish: a dry run that reports what
*would* go out is the only place to notice that the selection is wider than intended, and it
costs one command. And name the environment in the command rather than relying on a default
— a batch published to the wrong environment is a batch somebody has to take back one item
at a time.

An automated run may publish what it has just processed, and that is fine as long as it is
narrowed to those items. What should stay a deliberate human act is the sweeping run that
takes everything eligible in the system: that is the one whose blast radius is the whole
backlog.
