# Public Successors Bulk Upsert

Bulk-upsert successor relationships (async).
Accepts up to 100,000 rows per call. Successors are 1:1 per source
article: re-sending a source with a new successor replaces the previous
one. Rows whose articles don't exist are skipped (see the job's
`skipped_count`).
**Response.** `202 Accepted` with `JobAck` on the first call;
`200 OK` with the same `JobAck` on an idempotent replay. Poll
`GET /jobs/{job_id}` for terminal status.
**Idempotency.** Pass an `Idempotency-Key` header (≤ 255 chars); the
same key with a *different* body returns `422`.

Endpoint: POST /articles/successors
Version: 1.79.2

## Header parameters:

  - `authorization` (string | null)
    Bearer <mrc_live_…> API key

  - `Idempotency-Key` (string | null)
    Optional client-supplied key for safe retries; <= 255 chars

## Request fields (application/json):

  - `successors` (array, required)

  - `successors.source_article_number` (string, required)
    The discontinued / predecessor article.

  - `successors.successor_article_number` (string, required)
    The article that replaces the source.

  - `successors.order` (integer | null)
    Optional display order (a source has one active successor).

## Response 200:

  - `200` (unknown)
    Idempotent replay — same job returned

## Response 200 fields (application/json):

  - `job_id` (string, required)

  - `status_url` (string, required)
    Relative URL to poll for this job's status

  - `warnings` (array)
    Non-fatal notices about the accepted payload — e.g. article translations for unsupported languages that were dropped. The job runs regardless; nothing listed here blocks processing. Deterministic for a given body, so idempotent replays return the same warnings.

## Response 202:

  - `202` (unknown)
    Accepted; poll status_url for completion

## Response 400:

  - `400` (unknown)
    Validation failed

## Response 422:

  - `422` (unknown)
    Idempotency-Key reused with a different body

