---
title: "Propose a change to an entry"
method: POST
path: "/api/entity/{vhid}/proposals"
tags: ["Proposing changes"]
---

# Propose a change to an entry

`POST /api/entity/{vhid}/proposals`

## Request

- Base URL: `https://verifiedhandles.org`
- URL: `https://verifiedhandles.org/api/entity/{vhid}/proposals`
- Auth: HTTP bearer

Queues a change for a reviewer, who approves it into the entry or turns it down. Send only the fields that change, with the `baseRevisionId` you read as `base_revision_id`. Proposing the same change again while it waits answers the same proposal. `qid_link` proposes linking the entry to a Wikidata item (or, with `qid: null`, unlinking it). Handle ops (`add_handles` / `remove_handles`) are stored as ops and applied to the entry’s list as it is when the proposal is approved, so two proposals adding different handles both apply; a proposal of nothing but ops is never held to its base revision, and one whose every op would change nothing now is refused (`no_changes`). Takes an optional `Idempotency-Key`, so a request whose answer was lost can be sent again safely: the retry gets the first answer back.

## Path parameters

- `vhid` string, required

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `payload` EditPayload, required: A change to an entry, as the editor sends it. Every field is optional: send only what changes. `handles` and `gallery` are whole lists, which replace the entry’s; `add_handles` / `remove_handles` name single handles instead, applied to the list as it is then, so a change of nothing else needs no base revision and never undoes a handle saved meanwhile. A field the server does not know is ignored.
    - `title` string
    - `subtitle` string, nullable
    - `description` string, nullable: HTML, sanitised on the way in by the same allowlist as every description. At most 64 KiB.
    - `description_source` unknown
    - `wikipedia_credit` object: Credit the description “from Wikipedia”. The server reads the page itself (redirects followed, so a draft since moved is its article), and refuses it unless at least half of the description’s three-word phrases are the page’s lead (`409 credit_text_differs`, with `page` and `share`). Not with `description_source: null`. Errors: `400 bad_wikipedia_title` / `not_english_wikipedia`; `409 wikipedia_page_missing`, `wikipedia_namespace` (not an article or a draft), `wikipedia_section`, `wikipedia_no_lead`, `no_description`, `draft_has_article` (the entry’s item already has an English article, named in `article`: credit that); `502 wikipedia_unreachable`.
      - `page` string, required: An English Wikipedia article or draft, by title (`Draft:Dominic McLaughlin`) or address (`https://en.wikipedia.org/wiki/…`).
      - `let_wikidata_manage` boolean: For a draft only: `false` keeps the description site-authored. By default a draft’s credit hands the description to the Wikidata sync, which leaves it alone until the draft is an article linked from the entry’s Wikidata item.
    - `short` string, nullable: The short name (`/<short>`); `null` removes it.
    - `image_id` string, nullable: The lead image, by media id; `null` removes it.
    - `gallery` string[]: The whole gallery after the lead, in order, by media id: it replaces the one there.
    - `image_links` object: The whole map of links on the entry’s images, media id → absolute http(s) address. Needs the `link_images` capability.
    - `custom_handle_order` boolean: Keep `handles` in the order given, rather than the site’s order.
    - `handles` object[]: The whole list of handles, in order: it replaces the one there. Not with `add_handles` / `remove_handles`.
      - `platform_id` string, required
      - `value` string, required
      - `display` string, nullable: A label to show instead of the value.
    - `add_handles` object[]: Handles to put on the entry, at the end of its list as it is when the change is applied (a proposal: when it is approved). One the entry has already (the same value, in any case the platform folds) changes nothing. No base revision is needed for it. Not with `handles` (400 `handles_with_ops`).
      - `platform_id` string, required
      - `value` string, required: Not blank. A website is completed to an address, as in `handles`.
      - `display` string, nullable: A label to show instead of the value.
    - `remove_handles` object[]: Handles to take off, by value — or, for an email the caller may not see, by `ref` (an address is refused: `masked_handle_needs_ref`). One the entry does not have changes nothing. A value the Wikidata sync supplied is held back from it, as when a list leaves it out. Not with `handles`.
      - `platform_id` string, required: The platform’s id.
      - `value` string: The handle, as stored or in any case the platform folds.
      - `ref` string: Instead of `value`, for an email the caller may not see: the `ref` `GET /api/editor/{vhid}` gives it.
    - `relinquish` string[]: Fields to hand back to the Wikidata sync.
    - `takeover` string[]: Fields to take from the Wikidata sync, keeping their value.
    - `handle_takeover` object[]: Handles to take from the Wikidata sync.
      - `platform_id` string, required: The platform’s id.
      - `value` string, required: The handle as stored.
    - `handle_relinquish` object[]: Handles to hand to the Wikidata sync, while Wikidata lists them.
      - `platform_id` string, required: The platform’s id.
      - `value` string, required: The handle as stored.
    - `handle_release` object[]: Values held back from the sync that it may add again.
      - `platform_id` string, required: The platform’s id.
      - `value` string, required: The handle as stored.
  - `base_revision_id` string: The revision the change was made against (`baseRevisionId`).
  - `prepared_by` 'webmcp': Sent by the site’s own pages when an AI agent in the person’s browser prepared the change (WebMCP). Recorded on the audit row, and on a proposal shown to its reviewer; it changes nothing else. Any other value is ignored.
  - `qid_link` object
    - `qid` string, nullable, required: The item, `Q<n>`; null or empty unlinks.
    - `adopt` string[]: Fields to take from the item when the link is approved.

## Response `200`

Queued.

- ProposalQueued: The proposal is queued for a reviewer. Sending the same proposal again while it waits answers the same id rather than queuing it twice.
  - `ok` true, required
  - `submissionId` string, required: The proposal’s id.
  - `status` 'pending', required: It waits for a reviewer.

## Other responses

- `400`: The change is malformed, would change nothing, or names a platform the catalog does not have or has disabled (`invalid_platform`). Or the request sent a key and a session cookie together (`bearer_with_cookie`): send one. Or the Idempotency-Key is not 1–255 printable ASCII characters (`bad_idempotency_key`).
- `401`: No key was sent (`auth_required`), or the key was refused: unknown, revoked or expired, its holder may no longer use keys, or this instance takes none. Carries `WWW-Authenticate: Bearer`. After 60 refused keys from one IP address in a minute, every key from there is answered `429` (text, with `Retry-After`) until the minute is up.
- `403`: The entry takes no proposals from this account (`forbidden`), or a link on an image needs `link_images`. A read-only key tried to change something (`read_only_key`), or the account is blocked (`forbidden`).
- `404`: No public entry has that VHID.
- `409`: The Wikipedia page named in `wikipedia_credit` cannot be credited: missing, not an article or a draft, a section, no lead, a draft of an item that has an article, or the description is not the page’s text (`credit_text_differs`). Or the first request with this Idempotency-Key is still running (`idempotency_key_in_progress`): try again after `Retry-After` seconds.
- `413`: The body is over 512 KiB.
- `422`: This Idempotency-Key was used for a different request in the last 24 hours (`idempotency_key_reused`): nothing was written.
- `429`: Too many changes in the last minute: counted per IP address, per account and per key (an admin key’s per key alone, at the limit set on Console → Rate limits). Wait the number of seconds `Retry-After` gives.
- `502`: Wikipedia (or Wikidata) did not answer the read of a named page: try again.
- `503`: The site is down for maintenance. The body is the maintenance page, as HTML. Try again later: `Retry-After` gives the seconds.

---

[API](https://skmtc.dev/verifiedhandles/apis/verified-handles-api.md) · [All operations](https://skmtc.dev/verifiedhandles/apis/verified-handles-api/llms.txt) · [OpenAPI document](https://skmtc.dev/verifiedhandles/apis/verified-handles-api/revisions/40720de2e0ec?raw)
