> ## Documentation Index
> Fetch the complete documentation index at: https://partner.tren.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Proposal Endpoints

> Create metadata change proposals, extend the mutability window, finalize metadata, and withdraw proposal bonds. Requires the governance:propose scope.

|                |                                                                     |
| -------------- | ------------------------------------------------------------------- |
| **Scope**      | `governance:propose`                                                |
| **Rate limit** | 60 / minute                                                         |
| **Requires**   | `tradingEnabled: true` on [`GET /partner/v1/me`](/api-reference/me) |

Creating a proposal locks a bond from the user's wallet, sized at `proposalBondBps` of supply, which is reclaimed with `bonds/withdraw` after settlement.

Only one proposal can be active per governance at a time, and a second returns `active_proposal_exists`.

## Shared Fields

| Field                 | Type   | Required | Notes                                               |
| --------------------- | ------ | -------- | --------------------------------------------------- |
| `voteDurationSeconds` | string | No       | Integer string, greater than 0. Defaults to `"600"` |
| `priorityFeeLamports` | string | Yes      | Minimum `1000`                                      |
| `tipLamports`         | string | Yes      | Minimum `1000000`                                   |

<Warning>
  The default vote duration is 600 seconds, which is ten minutes and short for a real governance decision. We recommend setting `voteDurationSeconds` explicitly rather than relying on the default.
</Warning>

## POST /partner/v1/governance/proposals/update-content

Proposes a change to token metadata. This is the only multipart endpoint in the Partner API, because it can carry a new image.

**Content type:** `multipart/form-data`

| Field                 | Required | Notes                                                             |
| --------------------- | -------- | ----------------------------------------------------------------- |
| `mint`                | Yes      | Base58 token mint                                                 |
| `priorityFeeLamports` | Yes      |                                                                   |
| `tipLamports`         | Yes      |                                                                   |
| `voteDurationSeconds` | No       | Defaults to `"600"`                                               |
| `name`                | No       | Must be non-empty if sent                                         |
| `symbol`              | No       | Must be non-empty if sent                                         |
| `description`         | No       | Send empty to clear                                               |
| `website`             | No       | Send empty to clear                                               |
| `twitter`             | No       | Send empty to clear                                               |
| `telegram`            | No       | Send empty to clear                                               |
| `image`               | No       | One file: `image/png`, `image/jpeg`, `image/webp`, or `image/gif` |

Send only the fields you want to change, since omitted fields are left alone and an empty string clears a text field.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.tren.ch/partner/v1/governance/proposals/update-content' \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -F mint=… \
    -F voteDurationSeconds=86400 \
    -F priorityFeeLamports=20000 \
    -F tipLamports=1000000 \
    -F name="New Name" \
    -F image=@logo.png
  ```

  ```javascript Node.js theme={null}
  const form = new FormData();
  form.set("mint", mint);
  form.set("voteDurationSeconds", "86400");
  form.set("priorityFeeLamports", "20000");
  form.set("tipLamports", "1000000");
  form.set("name", "New Name");
  form.set("image", new Blob([imageBytes], { type: "image/png" }), "logo.png");

  const res = await fetch(
    "https://api.tren.ch/partner/v1/governance/proposals/update-content",
    { method: "POST", headers: { Authorization: `Bearer ${accessToken}` }, body: form }
  );
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
    "https://api.tren.ch/partner/v1/governance/proposals/update-content",
    headers={"Authorization": f"Bearer {access_token}"},
    data={
      "mint": mint,
      "voteDurationSeconds": "86400",
      "priorityFeeLamports": "20000",
      "tipLamports": "1000000",
      "name": "New Name",
    },
    files={"image": ("logo.png", open("logo.png", "rb"), "image/png")},
  )
  ```
</CodeGroup>

<Note>
  There are two distinct empty-change errors and they are not interchangeable. Sending no patch fields at all fails fast with `400 invalid_input`, whereas sending fields whose values match what is already on-chain reaches the builder and fails with `no_metadata_changes`.
</Note>

<Warning>
  Validation failures on `website`, `twitter`, and `telegram` currently return `500 internal` rather than `400`. These are permanent failures rather than transient ones, so they should not be placed on an automatic retry path. We recommend validating these three fields on your own side before submitting. See [Error Handling](/concepts/errors).
</Warning>

On success, the response additionally carries `metadataUri` and `imageUri` for the newly pinned content.

## POST /partner/v1/governance/proposals/extend-mutability

Proposes extending the window during which metadata can still be changed.

| Field                 | Required | Notes                          |
| --------------------- | -------- | ------------------------------ |
| `mint`                | Yes      |                                |
| `additionalSeconds`   | Yes      | Integer string, greater than 0 |
| `voteDurationSeconds` | No       | Defaults to `"600"`            |

A governance can only be extended once, and a second attempt returns `mutability_already_extended`. Extensions are also capped, so an over-long request returns `mutability_extension_exceeds_cap`.

## POST /partner/v1/governance/proposals/finalize-metadata

Proposes locking metadata permanently. Once metadata is finalized, no further content proposals are possible for that token.

| Field                 | Required |
| --------------------- | -------- |
| `mint`                | Yes      |
| `voteDurationSeconds` | No       |

## POST /partner/v1/governance/metadata/finalize-expired

Finalizes metadata whose mutability window has already lapsed. This is housekeeping rather than a governance decision, since it records on-chain what is already true.

| Field  | Required |
| ------ | -------- |
| `mint` | Yes      |

<Note>
  This endpoint accepts `voteDurationSeconds` for schema consistency but ignores it, since there is nothing to vote on.
</Note>

Calling it before the window expires returns `metadata_still_mutable`.

## POST /partner/v1/governance/bonds/withdraw

Reclaims the bond locked when creating a proposal, after settlement.

| Field      | Required |
| ---------- | -------- |
| `proposal` | Yes      |

Returns `no_proposal_bond` if the proposal never had one, and `proposal_bond_already_withdrawn` if it was already reclaimed.

## Response

```json theme={null}
{
  "signature": "…",
  "status": "processed",
  "mint": "…",
  "governance": "…",
  "proposal": "…",
  "metadataUri": "ipfs://…",
  "imageUri": "ipfs://…"
}
```

The `metadataUri` and `imageUri` fields appear only on `update-content`, and other identifiers are present where they apply.

<Warning>
  A status of `processed` means the transaction was included in a block rather than rooted. Confirm the signature against Solana through your own RPC before showing the user a settled result.
</Warning>

## Errors

| Status | Error                              | Cause                                                  |
| ------ | ---------------------------------- | ------------------------------------------------------ |
| `400`  | `invalid_input`                    | Schema validation failed, or no patch fields sent      |
| `400`  | `invalid_multipart`                | The form could not be parsed                           |
| `400`  | `invalid_image_type`               | Unsupported image MIME type                            |
| `403`  | `insufficient_scope`               | Token lacks `governance:propose`                       |
| `403`  | `delegation_missing`               | User has not enabled 1-click trading                   |
| `404`  | `governance_not_found`             | Token has no governance                                |
| `422`  | `active_proposal_exists`           | One proposal at a time per governance                  |
| `422`  | `insufficient_proposal_bond`       | User cannot cover the bond                             |
| `422`  | `missing_proposal_token_account`   | User holds none of this token                          |
| `422`  | `governance_finalized`             | Metadata locked forever                                |
| `422`  | `metadata_not_mutable`             | Metadata can no longer change                          |
| `422`  | `metadata_still_mutable`           | Window has not expired yet                             |
| `422`  | `content_field_locked`             | A changed field is permanently locked                  |
| `422`  | `no_metadata_changes`              | Nothing actually differs from on-chain state           |
| `422`  | `stale_content_revision`           | Metadata changed while the proposal was being authored |
| `422`  | `mutability_expired`               | Window has closed                                      |
| `422`  | `mutability_already_extended`      | Already extended once                                  |
| `422`  | `mutability_extension_exceeds_cap` | Requested extension is too long                        |
| `422`  | `proposal_window_closing`          | Not enough time left before metadata locks             |
| `422`  | `no_proposal_bond`                 | Nothing to withdraw                                    |
| `422`  | `proposal_bond_already_withdrawn`  | Bond already reclaimed                                 |
| `502`  | `pinata_unavailable`               | Image or metadata upload failed                        |
