# Governance votes

Source: https://docs.settlemint.com/docs/api-reference/guides/governance
Create a holder vote on a token, cast ballots, queue and execute the result, and run signed off-chain polls with the governance routes of the Platform API.

The governance routes run a holder vote on a token and the execution that follows. They sit under `/api/v2/addons/governance`. A resolution (`mode: "binary"`) asks holders to approve or reject one decision, a multi-option vote (`mode: "multiOption"`) asks them to choose between named options, and a poll records signed ballots off the ledger. Only a resolution can carry token changes. The token needs the voting-power feature attached before it can carry a vote. [Governance votes in the Console](https://docs.settlemint.com/docs/console/my-assets/votes) drives the same routes from screens.

## Routes [#routes]

| Route | Use |
| --- | --- |
| `GET /proposals` and `POST /proposals` | List votes, or create one. |
| `GET /governors/{governorAddress}/proposals/{proposalId}` | Read one vote. |
| `GET .../proposals/{proposalId}/ballots`, `.../turnout`, `.../export` | Read the ballots, the turnout over time, or an export of the ballots. |
| `POST /votes` | Cast a ballot on a vote. |
| `POST /queue-operations`, `POST /executions`, `POST /cancellations` | Queue a succeeded vote, execute a queued one, or cancel a vote. |
| `GET /instances/{assetId}`, `POST /preflights` | Read a token's governor and timelock, or check a vote before creating it. |
| `POST /document-uploads`, `POST /document-downloads` | Get a signed URL to upload a vote document, or to download one. |
| `POST /polls` and `GET /polls`, `GET /polls/{pollId}`, `POST /polls/{pollId}/close`, `GET .../export` | Create, list, read, close, and export a poll. |
| `POST /polls/{pollId}/ballots`, `POST /polls/{pollId}/ballots/sign` | Record a signed ballot, or sign and record one with the caller's wallet. |

Creating, casting, queueing, executing and cancelling a vote each send a transaction. Each returns the settled result, or an accepted answer to follow while the transaction is still queued, like the other writes on the [idempotency and async settlement](https://docs.settlemint.com/docs/api-reference/guides/idempotency-async-settlement) contract. From a signed-in session each also needs a `walletVerification` block. A request sent with an API key needs none, because creating the key took one, as [API keys](https://docs.settlemint.com/docs/api-reference/guides/authentication-api-keys) describes. The governor checks the governance role on the token, so creating or cancelling a vote needs that role. Queueing and executing a vote need no role. A create that breaks the rules below, or a step the vote's current state does not allow, is refused with `422` [DALP-14259](https://docs.settlemint.com/docs/api-reference/errors/platform-api-error-reference#domain-rule-errors). A refused create names the rule it broke in `data.reason`: `recordDateTooEarly`, `recordDateTooLate`, `votingPowerMissing`, `documentNotOwned`, `optionsInvalid`, `votingPeriodOutOfRange`, `executionDelayOutOfRange`, `quorumOutOfRange`, `approvalThresholdOutOfRange`, `treasuryIsToken` or `metadataKeyLocked`. A refusal of a later step carries no `reason`. `metadataKeyLocked` means a `setMetadata` entry sets a key the token has locked, a key the token's template keeps off chain, or a key an earlier entry in the same vote locks. `POST /preflights` lists each such entry in `metadataKeyLocks`, by `actionIndex` and `entryIndex`, before you create the vote.

## Create a vote [#create-a-vote]

`POST /api/v2/addons/governance/proposals` takes the `tokenAddress`, the `mode`, a `title`, a `descriptionRichText`, the `recordDateAt`, and `quorumBps`. It also takes an optional `approvalThresholdBps`, `votingPeriodSeconds`, and `executionDelaySeconds`, the `options` of a multi-option vote, the `actions` of a resolution that carries token changes, and `documents`. A document's `objectKey` must come from an upload grant issued to you. Quorum and threshold are in basis points. The platform refuses a record date less than 1 day or more than 90 days ahead, and a voting period or execution delay outside 1 to 30 days. It also refuses a multi-option vote whose `options` repeat a label, compared without case and surrounding spaces, with `portfolio.governance.PREFLIGHT_FAILED` and a message that names both options. Without a value, the voting period is 7 days and the execution delay 2 days. The Console offers a fixed set of these lengths, which [Create a vote](https://docs.settlemint.com/docs/console/my-assets/votes#create-a-vote) lists.

```bash
curl -X POST "https://your-instance.example.com/api/v2/addons/governance/proposals" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Prefer: wait=30" \
  -H "Content-Type: application/json" \
  -d '{
    "tokenAddress": "0x8a791620dd6260079bf849dc5567adc3f2fdc318",
    "mode": "binary",
    "title": "Approve the 2027 dividend policy",
    "descriptionRichText": "Holders approve paying 40 percent of 2026 net profit as a dividend in 2027.",
    "recordDateAt": "2026-11-23T00:00:00.000Z",
    "quorumBps": 2000
  }'
```

A settled create answers `200` with the token's governor and timelock and the vote's `proposalId`, which the cast, queue, execute and cancel calls take:

```json
{
  "data": {
    "transactionHash": "0x7d1e4b2a9c3f5e6d8a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f",
    "governorAddress": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
    "timelockAddress": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
    "proposalId": "48219370651823409171526401837265940018273645501928374650192837465019283746"
  },
  "meta": {
    "txHashes": ["0x7d1e4b2a9c3f5e6d8a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f"],
    "transactionId": "0198f3c2-5a1b-7c4d-8e9f-0a1b2c3d4e5f"
  },
  "links": { "self": "/api/v2/addons/governance/proposals" }
}
```

Without `Prefer: wait` the create answers `202` with `transactionId`, `status: "queued"` and `statusUrl`.

The first vote on a token also deploys the token's governor, the contract that counts its votes, and its timelock, the contract that holds a passed vote's actions for the execution delay and then executes them. The body of that first vote can carry `quorumFloorBps`, 500 by default, and `approvalThresholdFloorBps`, 0 by default. These floors are the lowest quorum and approval threshold that this vote and every later vote on the token can set.

The token's timelock executes a vote's `actions`. Create is refused with `409` [DALP-9155](https://docs.settlemint.com/docs/api-reference/errors/platform-api-error-reference#client-request-errors), `GOVERNANCE_TIMELOCK_MISSING_ROLE`, while the timelock lacks the governance role on the token's access manager. Grant that role to the timelock, then send the create again. A vote without `actions` needs no timelock role.

## How a vote is counted [#how-a-vote-is-counted]

The weight of a ballot is the holder's voting power at the record date. Voting power counts only when the holder has activated it and has not delegated it away. `GET /api/v2/tokens/{assetId}/voting-power/snapshot-weights` returns voting power per holder as it stood at a past block.

- **Quorum** is `ceil(quorumBps × total supply at the record date / 10000)`. A basis point is a hundredth of a percent, so `quorumBps: 2000` is 20 percent of the supply. It counts every ballot cast, abstentions included, and it is met at exactly that figure. The supply is the token's whole supply at the record date, not only the activated voting power.
- **Approval** applies to a resolution. The `for` total must exceed the `against` total. When `approvalThresholdBps` is set, `for` must also reach `ceil(approvalThresholdBps × (for + against) / 10000)`. Abstentions are outside both terms, and the threshold can only tighten the simple majority.
- **A multi-option vote** succeeds on quorum alone. The winning option is read from the tallies, and a tie reports no winner.

## Cast, queue, and execute [#cast-queue-and-execute]

`POST /api/v2/addons/governance/votes` takes the `tokenAddress`, the `governorAddress`, the `proposalId`, and either `support` (`for`, `against`, or `abstain`) or `optionIndex` for a multi-option vote, never both. An optional `reason` travels with the ballot. The vote must be open, and the wallet must hold voting power at the record date. A ballot is final once cast.

`GET .../ballots` lists each ballot with its `voter`, `weight`, `reason`, and `castAt`. On a resolution, `support` is `for`, `against`, or `abstain`, and `optionIndex` and `optionLabel` are `null`. On a multi-option vote, `support` is `option`, `optionIndex` is the chosen option's position counted from zero, and `optionLabel` is that option's text. `GET .../export` returns the same ballots as CSV, with an `optionLabel` column after `castAt`. The lines above the ballots give a resolution's `forVotes`, `againstVotes`, and `abstainVotes` totals, or one `optionVotes` line per option of a multi-option vote: its position, its text, and its votes.

When the vote has succeeded, `POST /queue-operations` queues it on the timelock, which starts the execution delay. After the delay, `POST /executions` applies everything the vote holds in one transaction. `POST /cancellations` cancels a vote while it is pending or queued. Any current holder of the token's governance role can cancel, not only the person who created the vote, so the role can stop a vote the holders passed before it executes. All three take the `tokenAddress`, the `governorAddress`, and the `proposalId`. Read the vote before each step to see its status.

## Polls [#polls]

`POST /api/v2/addons/governance/polls` takes the `tokenAddress`, a `title`, an optional `description`, the `options`, and the `recordDateAt`, `opensAt`, and `closesAt` times. The record date must come before `opensAt`, and `opensAt` before `closesAt`. Each option must differ from the others in the same way as a multi-option vote's, or the create fails with `portfolio.governance.PREFLIGHT_FAILED`. Creating, closing and exporting a poll need the governance role on the token. A poll ballot names the `voter`, the `optionIndex`, and the poll's `definitionHash`, and carries the voter's signature. The platform refuses a ballot whose hash differs from the poll's definition, whose voter is not one of the caller's wallets, or whose signature does not verify as the voter's. It also refuses a voter with no voting power at the record date. `GET /polls/{pollId}` returns `callerWalletVotes`, each of the caller's wallets that held voting power at the record date with that power, largest first, so a client can tell before signing whether the caller can vote: an empty list means none of their wallets can. A poll never reaches the queue and execute routes.

## Next steps [#next-steps]

- [Governance votes in the Console](https://docs.settlemint.com/docs/console/my-assets/votes) for the screens, the statuses, and the rules a vote follows.
- [Platform API error reference](https://docs.settlemint.com/docs/api-reference/errors/platform-api-error-reference) for the refusal codes.
- [Idempotency and async settlement](https://docs.settlemint.com/docs/api-reference/guides/idempotency-async-settlement) for the header contract every write shares.
