---
title: "Publish a news article"
method: PUT
path: "/api/news/article/{id}/publish"
tags: ["News"]
---

# Publish a news article

`PUT /api/news/article/{id}/publish`

## Request

- Base URL: `https://api.sponsorunited.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.sponsorunited.com/api/news/article/{id}/publish`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/sponsorunited/apis/sponsorunited-api/docs/api/news/article/:id/publish/put.ts> (a function with typed inputs; its response is checked against the schema)

Publishes an article (was 'approve'): status becomes published, it is made active and appears on linked brand/property profiles. Allowed from analysed or unpublished. Only the reporter who submitted the article can publish it.

## Path parameters

- `id` integer, required

## Response `200`

Article published successfully

- NewsSubmissionResource
  - `id` integer
  - `url` string
  - `title` string, nullable
  - `description` string, nullable
  - `image` string, nullable: Thumbnail URL
  - `status` 'pending' | 'analysed' | 'published' | 'unpublished' | 'declined' | 'failed'
  - `error_message` string, nullable: Failure reason for a failed submission, sourced from the failed data-ingestion task; null unless status is failed.
  - `is_active` boolean, nullable
  - `reporter_id` integer
  - `submitted_by` object, nullable: The user who submitted the article (sourced from reporter_id). Present when the reporter relation is loaded.
    - `id` integer
    - `name` string
  - `content_markdown` string, nullable
  - `ingestion_request_id` string, nullable
  - `logo_id` integer, nullable
  - `published_at` string, date-time, nullable
  - `created_at` string, date-time, nullable
  - `updated_at` string, date-time, nullable
  - `contacts_status` 'needs_review' | 'added', nullable: Rollup of the article's extracted contacts for the queue's Contacts column: `added` once every extracted person is created, updated or dismissed, `needs_review` while any still needs a decision, null when the article extracted nobody. Present only on the list endpoint, which carries the counts it is derived from.
  - `assets_added_at` string, date-time, nullable: When this article's assets were first added through Quick Add. Null until a bulk add names the article.
  - `created_by_agent` string, nullable: Name of the agent that created the article, sent in the X-Agent-Name header with an agent token. Null when a person created it.
  - `updated_by_agent` string, nullable: Name of the agent that last edited the article through PUT /api/news/article/{id}. Null when the last edit was made by a person, or when the article was never edited.
  - `companies` object[]
    - `id` integer
    - `name` string
    - `approved` boolean
    - `created_by_agent` string, nullable: Name of the agent that added this link. Null when a person or the extraction added it.
  - `properties` object[]
    - `id` integer
    - `name` string
    - `league_id` integer
    - `created_by_agent` string, nullable: Name of the agent that added this link. Null when a person or the extraction added it.
  - `agencies` object[]
    - `id` integer
    - `name` string
    - `created_by_agent` string, nullable: Name of the agent that added this link. Null when a person or the extraction added it.
  - `tags` object[]
    - `id` integer
    - `name` string
    - `created_by_agent` string, nullable: Name of the agent that added this tag. Null when a person added it.
  - `funding` object, nullable: Structured funding data for the article, backing the New Funding tab's columns. Null when the article has no funding record — the same shape on the list and show endpoints.
    - `source` 'submitted' | 'extracted'
    - `company` string, nullable
    - `website` string, nullable
    - `amount_raised` NewsSubmissionFundingMoney: One money figure: as written in the article, parsed in its original currency, and normalized to USD by the submitter. Display `raw`, sort on `usd`. All four are null when the article discloses no figure — not every round discloses a valuation.
      - `raw` string, nullable
      - `value` number, float, nullable
      - `currency` string, nullable
      - `usd` number, float, nullable
    - `valuation` NewsSubmissionFundingMoney: One money figure: as written in the article, parsed in its original currency, and normalized to USD by the submitter. Display `raw`, sort on `usd`. All four are null when the article discloses no figure — not every round discloses a valuation.
      - `raw` string, nullable
      - `value` number, float, nullable
      - `currency` string, nullable
      - `usd` number, float, nullable
    - `funding_round` 'seed' | 'series_a' | 'series_b' | 'series_c' | 'series_d' | 'series_e' | 'other', nullable
    - `funding_round_label` string, nullable: The round exactly as reported, present only when funding_round is `other`.
    - `sector` string, nullable: Business sector, for example Fintech. Not a geography — `markets` means geography elsewhere in this API.
    - `investors` string[]: Investor names, in the order they were recorded.
  - `extracted_contacts` NewsContactResource[]: People extracted from the article, ordered by extraction confidence DESC. Present only on the show endpoint.
    - `id` integer: One extracted person, in the shape the article payload has always used. The submission payload renders every row through this and the single-contact routes render one, so a reviewer editing a contact reads back exactly the shape the article page will show them (OP-1514, OP-1515).
    - `name` string
    - `title` string, nullable
    - `company` string, nullable
    - `role_context` string, nullable
    - `confidence` number, float
    - `source` 'extracted' | 'submitted': Whether the row came from the article's own extraction or arrived with the submission.
    - `submitted_linkedin_url` string, nullable: A LinkedIn profile supplied with the submission. Distinct from the `add.linkedin_url` the sourcing agent finds.
    - `match` NewsExtractionMatch: Entity-mapping resolver verdict for one extracted person (OP-966/967/968).
      - `status` 'pending' | 'auto_match' | 'needs_review' | 'no_match'
      - `confidence` number, float, nullable
      - `resolved_at` string, date-time, nullable
      - `person_profile_id` integer, nullable
      - `contact_id` integer, nullable
      - `contactable_type` 'agency' | 'brand' | 'property', nullable: Morph alias of the resolved entity; pairs with contactable_id to open the Administration Contacts context.
      - `contactable_id` integer, nullable
      - `entity_name` string, nullable: Name of the resolved agency / brand / property.
      - `candidates` object[]: Resolver top-K candidates; empty when the row was never resolved.
    - `current` NewsExtractionMatchedContact, nullable: Present-day platform values of the matched contact, for side-by-side comparison against the extracted ones. Null when the extraction resolved to no contact.
      - `id` integer
      - `name` string, nullable
      - `email` string, nullable
      - `position` string, nullable
      - `linkedin` string, nullable
    - `add` NewsExtractionAdd: Outcome of adding this extracted person to the platform. Contacts only — a quote becomes a note on a contact, never a contact of its own. `not_added` is the only selectable state; `needs_review` means the sourcing agent found profiles but could not choose between them.
      - `status` 'not_added' | 'processing' | 'needs_review' | 'created' | 'updated' | 'failed'
      - `added_at` string, date-time, nullable: When the reviewer clicked Add contacts selected.
      - `contact_id` integer, nullable: The platform contact this row produced.
      - `error` string, nullable: Readable failure reason when status is failed.
      - `linkedin_url` string, nullable: The profile in play for this row: the one it was created from when status is created, or the one last tried when status is processing or failed. With `candidates` still populated, a failed row can offer the same pick again instead of re-running the search.
      - `candidates` object[]: Profiles the agent found but could not choose between. Present when status is needs_review; the reviewer picks one and posts it to the resolve endpoint.
        - `slug` string
        - `linkedin_url` string
        - `title` string
        - `snippet` string
        - `position` integer
  - `extracted_quotes` object[]: Quotes extracted from the article, ordered by extraction confidence DESC. Present only on the show endpoint.
    - `id` integer
    - `name` string
    - `title` string, nullable
    - `company` string, nullable
    - `context` string, nullable
    - `confidence` number, float
    - `quote_text` string
    - `quote_type` string
    - `match` NewsExtractionMatch: Entity-mapping resolver verdict for one extracted person (OP-966/967/968).
      - `status` 'pending' | 'auto_match' | 'needs_review' | 'no_match'
      - `confidence` number, float, nullable
      - `resolved_at` string, date-time, nullable
      - `person_profile_id` integer, nullable
      - `contact_id` integer, nullable
      - `contactable_type` 'agency' | 'brand' | 'property', nullable: Morph alias of the resolved entity; pairs with contactable_id to open the Administration Contacts context.
      - `contactable_id` integer, nullable
      - `entity_name` string, nullable: Name of the resolved agency / brand / property.
      - `candidates` object[]: Resolver top-K candidates; empty when the row was never resolved.
    - `current` NewsExtractionMatchedContact, nullable: Present-day platform values of the matched contact, for side-by-side comparison against the extracted ones. Null when the extraction resolved to no contact.
      - `id` integer
      - `name` string, nullable
      - `email` string, nullable
      - `position` string, nullable
      - `linkedin` string, nullable
  - `extracted_assets` object[]: Assets extracted from the article (dismissed rows excluded), ordered by extraction confidence DESC. Present only on the show endpoint.
    - `id` integer
    - `suggested_name` string
    - `confidence` number, float
    - `match_status` 'pending' | 'auto_match' | 'needs_review' | 'no_match' | 'confirmed'
    - `source` 'extracted' | 'manual'
    - `context` string, nullable
    - `item` object, nullable: Canonical catalog row when the asset has been resolved (auto_match/confirmed).
      - `id` integer
      - `name` string, nullable
      - `description` string, nullable
      - `item_platform_id` integer
      - `item_subplatform_id` integer

## Other responses

- `403`: Forbidden — missing view-news permission
- `404`: Article not found
- `422`: Transition not allowed from the current status

---

[API](https://skmtc.dev/sponsorunited/apis/sponsorunited-api.md) - [All operations](https://skmtc.dev/sponsorunited/apis/sponsorunited-api/llms.txt) - [OpenAPI document](https://skmtc.dev/sponsorunited/apis/sponsorunited-api/revisions/cbf1ca5bff8f?raw)
