Skip to content

Document scheduled_publish_at/unpublish_at on Articles API (Preview)#540

Open
kilian-tennyson wants to merge 1 commit into
mainfrom
kiliantennyson/parthas/0b193391-document-scheduled-publish-at-unpublish-at-on-articles-api-preview
Open

Document scheduled_publish_at/unpublish_at on Articles API (Preview)#540
kilian-tennyson wants to merge 1 commit into
mainfrom
kiliantennyson/parthas/0b193391-document-scheduled-publish-at-unpublish-at-on-articles-api-preview

Conversation

@kilian-tennyson
Copy link
Copy Markdown
Contributor

@kilian-tennyson kilian-tennyson commented Jun 5, 2026

Why?

The monolith PR intercom/intercom#515420 added scheduled_publish_at and scheduled_unpublish_at to the public Articles API behind the AddArticleScheduledPublishing Preview version change, but the OpenAPI spec in this repo did not describe them. Without this, SDK code generation and the developer-docs mirror have no reference for the new fields.

How?

Update descriptions/0/api.intercom.io.yaml to add the two fields to the Articles request and response schemas (with the deliberate input string vs. output integer asymmetry) and document the scheduling validation rules via field descriptions and new 400 response examples on POST and PUT /articles.

Decisions
  • Edited only descriptions/0/ since the feature is Preview-gated; left stable version directories untouched.
  • Modeled requests as string + format: date-time and responses as integer + format: date-time to mirror existing Intercom conventions (macro.created_at vs created_at/updated_at) rather than reusing the shared datetime oneOf schema, which is for runtime-polymorphic fields.
  • Placed response fields on article_list_item so they surface on both single and list responses via the shared ArticleResponse presenter, matching the placement pattern from PR Add audit, suggestion, audience, and Fin statistics fields to Article API (Preview) #530.
  • Marked response fields readOnly: true and nullable: true; kept request fields optional (not added to required:).
  • Extended the existing POST /articles 400 block with two new named examples (scheduling-conflict, conflicting-pending-schedules) and added a new 400 block to PUT /articles/{id} with three examples, covering the most discoverable validation failures while leaving empty-string/never-published rules in field descriptions.
  • Skipped a 400 on GET /articles/{id} since it takes no input that could trigger these validations.

Review Guidance

Dimension Score Reasoning
Complexity █░░░░░░░░░ 1.2
WhySingle-file, purely additive YAML changes to one spec version directory.
Unintuitiveness █░░░░░░░░░ 1.5
WhyDiff matches the PR description and plan exactly, including the called-out string-vs-integer asymmetry.
Risk Surface ░░░░░░░░░░ 0.8
WhyDocumentation-only OpenAPI spec edit; no runtime, auth, billing, or migration impact.

Attention: Routine review — Additive Preview-only OpenAPI documentation; CI fern check is the main gate.

🧪 This AI-generated review guidance is experimental. Share feedback

Implementation Plan
Worker Implementation Plan

Plan: Document scheduled_publish_at / scheduled_unpublish_at on Articles API (Preview)

Companion to intercom/intercom#515420 (already merged on 2026-06-05).

Context

PR intercom/intercom#515420 adds two new fields to the public Articles API behind the
AddArticleScheduledPublishing version change (gated to Preview / Unstable):
scheduled_publish_at and scheduled_unpublish_at. They appear on POST /articles,
PUT /articles/{id}, and the GET/list endpoints — and since the new builder preloads
schedules in batches, they also surface in list responses.

The machine-readable OpenAPI spec in this repo (descriptions/0/api.intercom.io.yaml)
is the source of truth for SDK code-generation (Fern) and is mirrored manually into
the developer-docs repo. Until this spec is updated, generated SDKs and the docs
site will not know about these fields, so customer-facing API isn't fully shipped.

Deliberate type asymmetry (locked in by the monolith spec):

  • Request body: ISO 8601 timestamp string or nulltype: string, format: date-time
  • Response body: Unix epoch integer seconds or nulltype: integer, format: date-time

This asymmetry matches existing Intercom-wide conventions: epoch integers on output
(like created_at, updated_at at lines 22946–22959), ISO 8601 strings on input
(see macro.created_at at line 31291–31294 for the string-form pattern).


Files to Modify

Only edit descriptions/0/api.intercom.io.yaml. No other version directories
(2.7/2.15/) — the feature is Preview-only. Do not touch developer-docs
(that's a separate order). Do not touch the monolith (PR #515420 already done).


Edit 1 — Response schema: article_list_item

Location: Insert after the existing help_center_audience block, immediately
before the next schema (internal_article_list_item: at line 23039).
So the insertion point is after line 23038.

This is the same placement convention PR #530 used when it added created_by_id,
updated_by_id, exclude_from_article_suggestions, and help_center_audience to
article_list_item. The shared monolith presenter (ArticleResponse) surfaces
these new fields on both single-article responses (article schema, allOf-composed
from article_list_item at line 22676) and list responses, so this placement
captures both.

        scheduled_publish_at:
          type: integer
          format: date-time
          nullable: true
          readOnly: true
          description: >-
            The Unix timestamp (in seconds) at which the article is scheduled to be
            published. `null` when no publish is scheduled. Mutually exclusive with
            `scheduled_unpublish_at` — at most one pending schedule exists per article.
          example: 1769443200
        scheduled_unpublish_at:
          type: integer
          format: date-time
          nullable: true
          readOnly: true
          description: >-
            The Unix timestamp (in seconds) at which the article is scheduled to be
            unpublished. `null` when no unpublish is scheduled. Mutually exclusive
            with `scheduled_publish_at` — at most one pending schedule exists per article.
          example: 1769443200

Why integer + format: date-time: matches existing created_at (line 22946) and
updated_at (line 22953) on the same schema. Validators accept this combination
(Intercom-wide convention).


Edit 2 — Request schema: create_article_request

Location: Insert after the ai_sales_agent_availability block (lines 26637–26641)
and before the required: block at line 26642.

The exact insertion point is after line 26641, i.e. between the closing
example: true of ai_sales_agent_availability and the required: line.

        scheduled_publish_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            ISO 8601 timestamp at which to schedule a future publish of the article.
            When set together with `state: "published"`, the article is scheduled
            instead of published immediately. Setting `null` cancels a pending
            publish schedule. Combining with `state: "draft"` returns 400
            `INVALID_PARAMETER`. Sending in the same request as `scheduled_unpublish_at`
            returns 400 — only one pending schedule per article. Empty string returns
            400 `INVALID_PARAMETER`.
          example: '2026-12-31T09:00:00Z'
        scheduled_unpublish_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            ISO 8601 timestamp at which to schedule a future unpublish of the article.
            Setting `null` cancels a pending unpublish schedule. Rejected with 400
            `INVALID_PARAMETER` if the article has never been published. Sending in
            the same request as `scheduled_publish_at` returns 400 — only one pending
            schedule per article. Empty string returns 400 `INVALID_PARAMETER`.
          example: '2026-12-31T17:00:00Z'

Do not add these to required: — both fields are optional.


Edit 3 — Request schema: update_article_request

Location: Insert after the ai_sales_agent_availability block (lines 33575–33579).
The update_article_request schema ends at line 33579 (no required: block, unlike
create_article_request), and the next schema begins at line 33580
(update_internal_article_request:).

Insertion point: after line 33579.

Use the same field definitions as Edit 2 (the validation rules are identical
for create and update — both call into the same service-layer scheduling logic).


Edit 4 — Augment POST /articles 400 response

Location: Lines 1334–1348 (the existing 400 block on POST /articles).

Currently has a single named example Bad Request showing a parameter_not_found
case. Convert to multi-example by adding two more named examples for the new
scheduling rules. The conversion keeps the existing example verbatim and adds two
siblings under the same examples: map.

After conversion, the examples: block looks like:

              examples:
                Bad Request:
                  value:
                    type: error.list
                    request_id: e522ca8a-cd15-404e-84b3-7f7536003d4a
                    errors:
                    - code: parameter_not_found
                      message: author_id must be in the main body or default locale
                        translated_content object
                Scheduling conflict:
                  value:
                    type: error.list
                    request_id: 8d11c1f4-0a55-4f7f-8e2f-4f6c1f3b2c11
                    errors:
                    - code: parameter_invalid
                      field: scheduled_publish_at
                      message: Cannot schedule a publish while setting state to draft
                Conflicting pending schedules:
                  value:
                    type: error.list
                    request_id: 0a82a3a4-94f1-4f1a-bea1-aaf41a8d3c22
                    errors:
                    - code: parameter_invalid
                      field: scheduled_unpublish_at
                      message: Cannot set scheduled_publish_at and scheduled_unpublish_at
                        in the same request
              schema:
                "$ref": "#/components/schemas/error"

Why two new examples (not three): The empty-string rule and never-published rule
are covered in field descriptions. Two named examples is enough to make the most
common conflicts discoverable in generated SDK docs without bloating the response.


Edit 5 — Add 400 response to PUT /articles/{id}

Location: Currently the PUT endpoint at lines 1500–1564 has only 200, 404,
401. Add a 400 block between 200 (ends line 1536) and 404 (starts line 1537).

Insertion point: after line 1536, before line 1537.

        '400':
          description: Bad Request
          content:
            application/json:
              examples:
                Scheduling conflict:
                  value:
                    type: error.list
                    request_id: 8d11c1f4-0a55-4f7f-8e2f-4f6c1f3b2c11
                    errors:
                    - code: parameter_invalid
                      field: scheduled_publish_at
                      message: Cannot schedule a publish while setting state to draft
                Conflicting pending schedules:
                  value:
                    type: error.list
                    request_id: 0a82a3a4-94f1-4f1a-bea1-aaf41a8d3c22
                    errors:
                    - code: parameter_invalid
                      field: scheduled_unpublish_at
                      message: Cannot set scheduled_publish_at and scheduled_unpublish_at
                        in the same request
                Unpublish before publish:
                  value:
                    type: error.list
                    request_id: 11aa22bb-33cc-44dd-55ee-66ff77889900
                    errors:
                    - code: parameter_invalid
                      field: scheduled_unpublish_at
                      message: Cannot schedule unpublish for an article that has never
                        been published
              schema:
                "$ref": "#/components/schemas/error"

Three named examples for PUT because updating is where most scheduling actions happen
(creating a schedule, switching publish↔unpublish, encountering a never-published
article). This satisfies the acceptance criterion that validation rules be described
"in field descriptions and/or in '400' response examples" for PUT.

Do not add a 400 block to GET /articles/{id} — GET doesn't accept input that
could fail these validations.


Field Convention Summary (recap)

Surface Field shape Reasoning
Request body string + format: date-time ISO 8601 input — matches macro.created_at (line 31291)
Response body integer + format: date-time Unix epoch — matches created_at (line 22946)
Both nullable: true null cancels schedule on input, indicates "no schedule" on output
Response only readOnly: true Computed from pending schedules; not directly settable on the resource

Do NOT use the shared datetime schema at line 21776. That oneOf schema is for
fields where either shape is accepted at runtime, which is not our case — the request
side accepts only strings and the response side returns only integers.


Verification

After making the edits, verify in this order:

  1. YAML syntax: node -e "require('js-yaml').load(require('fs').readFileSync('descriptions/0/api.intercom.io.yaml','utf8'))" or simply rely on git diff to catch obviously broken indentation. The repo has yaml@2.8.3 in node_modules already.

  2. Spec validation: The CLAUDE.md mentions fern check. There is no npm run validate script in package.json (confirmed). Try:

    fern check

    If the fern-api CLI isn't installed locally, this is fine — .github/workflows/fern_check.yml runs it on every push, so CI catches issues. Do not install fern-api globally on the worker just for this. Just push and let CI report.

  3. Diff sanity check: git diff --stat descriptions/0/api.intercom.io.yaml should show ~70–80 insertions, zero deletions (we're appending fields and a 400 block; we are not replacing existing content).

  4. Read-back: After editing, re-read the four modified regions and confirm:

    • article_list_item now ends with scheduled_unpublish_at
    • create_article_request has the two new fields above its required: block
    • update_article_request has the two new fields at the end of its properties: block
    • POST /articles 400 has three named examples
    • PUT /articles/{id} has a new 400 response with three named examples
  5. No collateral edits: git status should show only one modified file (descriptions/0/api.intercom.io.yaml). Other version directories (2.7/ through 2.15/) must be untouched.


Commit + Submit

  1. Commit with a clear message that cross-references the monolith PR:
    Add scheduled_publish_at / scheduled_unpublish_at to Articles API (Preview)
    
    Documents the new scheduling fields gated behind AddArticleScheduledPublishing
    in the Preview spec. Requests accept ISO 8601 strings; responses return Unix
    epoch integers. Adds 400 examples for scheduling validation conflicts on POST
    and PUT.
    
    Companion to intercom/intercom#515420.
    
  2. Run parthas submit.
  3. The auto-spawned reviewer will look at the diff. If it requests changes
    (e.g. wants the descriptions to be shorter, prefers different example
    timestamps), update and resubmit.

The PR description that parthas submit ultimately creates must follow this repo's
template (.github/PULL_REQUEST_TEMPLATE/pull_request_template.md):

  • What Changed: brief description of the new fields and where they appear
  • API Versions Affected: Preview only
  • Cross-reference: Companion to intercom/intercom#515420

Critical Files Touched

  • descriptions/0/api.intercom.io.yaml (only file edited)

Critical Files Read for Reference (not edited)

  • descriptions/0/api.intercom.io.yaml lines 1283–1602 (Articles paths)
  • descriptions/0/api.intercom.io.yaml lines 22663–23038 (article, article_list_item)
  • descriptions/0/api.intercom.io.yaml lines 26545–26644 (create_article_request)
  • descriptions/0/api.intercom.io.yaml lines 33482–33579 (update_article_request)
  • descriptions/0/api.intercom.io.yaml line 31291 (macro.created_at ISO 8601 string pattern)
  • descriptions/0/api.intercom.io.yaml lines 22946–22959 (epoch integer pattern)
  • Commit a188e46 (PR Add audit, suggestion, audience, and Fin statistics fields to Article API (Preview) #530) — the exact reference pattern for adding fields to article_list_item

Out of Scope

  • Other version dirs (descriptions/2.7/descriptions/2.15/) — Preview-only feature.
  • developer-docs repo — separate order.
  • The intercom monolith — already done in PR #515420.
  • Webhook payload docs — not affected by this change.

Guidance Carried Forward from the Order Prompt

  • Only edit descriptions/0/. Confirmed Preview-only.
  • Branch: work/0b193391/worker (already set).
  • Working directory: /Users/kiliantennyson/.parthas/worktrees/intercom-openapi/0b193391/worker.
  • Validation: fern check (no npm run validate); rely on CI if fern-api isn't
    installed locally.
  • PR description must cross-reference intercom/intercom#515420.
  • Do not push to remote — the First Officer handles that after parthas submit.
Parthas Order (task/issue)

Document scheduled_publish_at / scheduled_unpublish_at on Articles API (companion to intercom #515420)

Problem

PR intercom/intercom#515420 adds scheduled_publish_at and scheduled_unpublish_at fields to the public Articles API (POST/PUT/GET on /articles), gated behind a new AddArticleScheduledPublishing version change available in Preview. The machine-readable OpenAPI specification in this repo (descriptions/0/api.intercom.io.yaml) does not yet describe these fields, so SDK generation and the developer-docs spec mirror will not pick them up.

Why This Matters

The monolith PR can merge independently, but the public API is not fully shipped until the OpenAPI spec and developer-docs companion PRs land. The OpenAPI spec in this repo is the source of truth for both auto-generated SDKs and the spec that developer-docs mirrors — without it, customers writing against the new fields have no reference.

Goal

Update descriptions/0/api.intercom.io.yaml (Preview/Unstable version) so the Articles request and response schemas accurately describe the new fields, the validation behavior, and the error responses. Open a PR cross-referencing the monolith PR.

Context

  • Monolith PR (already merged): https://github.com/intercom/intercom/pull/515420
  • Affected endpoints: POST /articles, PUT /articles/{id}, GET /articles/{id} (Articles tag).
  • Field shapes — note the deliberate input/output type asymmetry:
    • Request body: scheduled_publish_at and scheduled_unpublish_at accept an ISO 8601 timestamp string or null.
    • Response body: the same two fields are integer Unix epoch seconds or null.
  • Validation rules to document:
    • Empty string → 400 INVALID_PARAMETER.
    • state: "draft" combined with scheduled_publish_at → 400 (conflict).
    • scheduled_unpublish_at on a never-published article → 400.
    • Publish + unpublish in the same request → 400 (only one pending schedule per article).
    • null value cancels a pending schedule (not an error).
  • Read ~/src/intercom/.claude/skills/ship-public-api/SKILL.md and ~/src/intercom/.claude/skills/ship-public-api/references/documentation.md before generating any spec edits — they contain the OpenAPI conventions, the "OpenAPI Best Practices" section, and the three-repo-pattern guidance.
  • Only edit descriptions/0/. The feature is gated to Preview; stable version directories do not have it.
  • Do NOT copy this spec into developer-docs — that's a separate order. The developer-docs sync is a targeted edit, not a file copy.

Acceptance Criteria

  • descriptions/0/api.intercom.io.yaml describes both fields on the Articles request and response schemas, with the input ISO 8601 string vs output Unix epoch integer difference reflected accurately in the schema definitions.
  • The validation rules above are described in field descriptions and/or in '400' response examples.
  • The repo's spec linter (npm run validate or equivalent) passes.
  • A PR is open against intercom/Intercom-OpenAPI whose description cross-references intercom/intercom#515420.

Non-goals

  • Do not touch other version directories (descriptions/2.10/, descriptions/2.15/, etc.) — the feature is Preview-only.
  • Do not modify any files in intercom/ (the monolith) — that work is already done in PR #515420.
  • Do not write the developer-docs companion PR — that is a separate order.

Generated with Claude Code, zen coded with Parthas

…eview)

Documents the new scheduling fields gated behind AddArticleScheduledPublishing
in the Preview spec. Requests accept ISO 8601 strings; responses return Unix
epoch integers. Adds 400 examples for scheduling validation conflicts on POST
and PUT.

Companion to intercom/intercom#515420.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant