Preskočiť na obsah

Dokumentácia pre vývojárov je v angličtine, rovnako ako API.

REST API

Read and change one board over HTTP: ideas, bug reports, comments, votes, the changelog and your customers. The API follows the same rules as the board and the MCP server, from the same token. Machine-readable: openapi.json (OpenAPI 3.1, every board serves its own).

Quick start

Every board has its API at /<board>/api/v1 on the board's address, including its own domain. In these examples the board is acme:

Base URL
https://ozvi.io/acme/api/v1
  1. Open the board, then My API tokens (/acme/tokens), and create a token. Pick the smallest scope that works.
  2. Send it in the Authorization header.
curl
curl https://ozvi.io/acme/api/v1/items?kind=idea&sort=top&limit=5 \
  -H "Authorization: Bearer $OZVI_TOKEN"

# File an idea (retry-safe with Idempotency-Key)
curl -X POST https://ozvi.io/acme/api/v1/items \
  -H "Authorization: Bearer $OZVI_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"kind":"idea","title":"Export to Excel","body":"Our accountant needs XLSX."}'
fetch (Node 18+, Deno, Bun)
const res = await fetch("https://ozvi.io/acme/api/v1/items?sort=top&limit=10", {
  headers: { Authorization: `Bearer ${process.env.OZVI_TOKEN}` },
});
if (!res.ok) throw new Error((await res.json()).detail); // problem+json
const { data, nextCursor } = await res.json();

Authentication and scopes

The API takes personal access tokens (ozvi_pat_<board>_…) of the same board. A token acts as the person who created it, with the smaller of the token's scope and that person's current role: a member's token never gets team rights, and demoting someone downgrades their tokens at once. GET /me tells you what a token can do.

ScopeAllowsWho
readRead ideas, your own bugs (everything for the team), comments, tags, statuses, the published changelog.anyone
writeAlso file ideas and bugs and comment, as the token's owner.anyone
triageAlso status, sub-status, tags, merges, internal notes, voters and votes on behalf of customers, people with their traits, changelog drafts and publishing, exports. Webhooks list: admins only.team members

OAuth tokens issued to AI assistants are bound to the MCP resource (/<board>/mcp, RFC 8707) and are refused here with token_audience; an integration that calls the REST API should use a personal token of a dedicated account, so it can be revoked on its own. Tokens never work across boards. Requests from browsers are not supported (no CORS): keep the token on your server.

User content and provenance

Titles, bodies and comments are written by your users and come back as plain JSON strings, as written (the board's small Markdown subset). Treat them as untrusted data: escape them before rendering HTML, and never paste them into an AI model's instructions without fencing them off as data.

  • author.trust on every item and comment says how much Ozvi knows about the writer: anonymous < email_verified < sso_customer < team; system marks ideas filed from customer feedback by the team's agent.
  • source says where it came in: board, widget, mcp, import or api. Everything created through this API is api.
  • Lockdown: send X-Ozvi-Lockdown: verified (or team) and text from less trusted authors is withheld: the item keeps its number, status and votes, its title becomes a placeholder, body is empty and withheld: true is set. A project can turn lockdown on for every token (admin → Integrácie); the stricter setting wins.
  • AI label (EU AI Act art. 50): changelog entries and duplicate suggestions carry ai: null when people wrote it, otherwise { generated: true, model, reviewedBy }. reviewedBy stays null until a person approves the text. Show the label wherever you republish such text.

Pagination

Lists answer { data: [...], nextCursor }. Pass nextCursor back as cursor with the same filters and sort until it is null. Cursors are opaque; a cursor from another sort answers invalid_cursor. limit is 1–100 (items, people) or 1–50 (changelog).

Every idea, page by page (Node)
let cursor = null;
do {
  const url = new URL("https://ozvi.io/acme/api/v1/items");
  url.search = new URLSearchParams({ kind: "idea", limit: "100", ...(cursor && { cursor }) });
  const page = await (await fetch(url, { headers: { Authorization: `Bearer ${token}` } })).json();
  for (const item of page.data) console.log(item.number, item.votes, item.title);
  cursor = page.nextCursor;
} while (cursor);

Errors

Errors are RFC 9457 problem details (application/problem+json). Branch on code, which never changes; detail is for people. Invalid requests list the fields in errors.

401
{
  "type": "https://ozvi.io/docs/api#error-unauthorized",
  "title": "Missing or invalid token",
  "status": 401,
  "code": "unauthorized",
  "detail": "Send a personal access token as Authorization: Bearer ozvi_pat_…",
  "instance": "/acme/api/v1/items"
}
codeStatusMeaning
unauthorized401Missing or invalid token
token_audience401This token is not valid for this API
insufficient_scope403The token's scope does not allow this
forbidden403The token owner's role does not allow this
lockdown_active403Not available while lockdown is on
not_found404Not found
method_not_allowed405Method not allowed
confirmation_required409This action needs confirmation
idempotency_in_progress409A request with this Idempotency-Key is still running
payload_too_large413Request body too large
unsupported_media_type415Send the body as application/json
invalid_request400The request is not valid
invalid_json400The body is not valid JSON
invalid_cursor400The cursor is not valid for this list
unprocessable422The request could not be carried out
confirmation_invalid422The confirmation code is wrong or expired
idempotency_key_reused422This Idempotency-Key was used with a different request
rate_limited429Too many requests
internal500Internal error

Rate limits

Each token may make 600 requests per 10 minutes (about 60 a minute on average); creating posts, comments and votes has the same lower limits as on the board. Every answer carries the standard headers; over the limit you get 429 with Retry-After in seconds.

Headers
RateLimit-Limit: 600
RateLimit-Remaining: 594
RateLimit-Reset: 412

Idempotency and ETags

Requests that create something (POST /items, comments, votes, changelog drafts) accept an Idempotency-Key header (up to 255 characters, a UUID is ideal). For 24 hours a retry with the same key and the same body gets the first answer again, marked Idempotent-Replayed: true; the same key with a different body answers idempotency_key_reused, and a retry while the first request still runs answers idempotency_in_progress. Server errors are not remembered, so they can be retried with the same key.

GET answers carry an ETag. Send it back as If-None-Match and an unchanged item answers 304 Not Modified with no body: cheap polling.

Destructive actions

Two calls cannot be undone, so they need an explicit confirmation, like in MCP:

  • Merge (POST /items/{number}/merge): the first call answers 409 confirmation_required with a preview and a confirm code. Show the preview to a person, then repeat the call with "confirm": "<code>" within about 10 minutes.
  • Publish a changelog entry (POST /changelog/{id}/publish): requires {"confirm": true}; without it you get the preview with the number of people who will be e-mailed. The token's owner is recorded as the reviewer of AI-assisted text.

Voting never subscribes

Votes cast through the API follow the board: voters are not e-mailed on status changes, only once when a changelog entry ships their idea.

Endpoints

Relative to https://ozvi.io/acme/api/v1. 21 operations; the same list is in openapi.json for code generators, Postman or Insomnia.

Items

GET/items

List ideas and bug reports

Bugs are private: a member's token sees ideas and their own bugs; team tokens see everything. Cursor pagination: pass nextCursor back as cursor with the same filters. With the triage scope ideas also carry demand (customers' companies and MRR).

Scope: read (or higher, see description).

Parameters

NameInTypeDescription
kindquery"bug" | "idea"
statusquerystringStage key (see GET /statuses).length 0–30
tagquerystringTag name, case-insensitive. Team-only tags only filter for team tokens.length 0–40
qquerystringFull-text search (title and body); results by relevance.length 0–200
sortquery"new" | "top" | "mrr"new = newest number first; top = most votes; mrr = by customers' revenue (triage scope).default "new"
unansweredquery"0" | "1" | "true" | "false"The team's unanswered queue, longest wait first (triage scope).
limitqueryinteger1–100, default 25
cursorquerystringnextCursor from the previous page.length 0–500

200 A page of items. → ItemPage

Errors: 400 invalid_request; invalid_cursor · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

POST/items

Create an idea or a bug report

Filed as the token's owner with source api. Ideas from outside the team may wait in the moderation queue. The same title from the same person within a few minutes returns the first item (and 200 instead of 201).

Scope: write.

Body (application/json): CreateItemRequest

201 The new item. → Item

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 idempotency_in_progress · 413 payload_too_large · 415 unsupported_media_type · 422 idempotency_key_reused; unprocessable · 429 rate_limited

Headers: Idempotency-Key, X-Ozvi-Lockdown

GET/items/{number}

Get one item

With comments, attachments and (bugs) the masked browser context. Answers with an ETag; send it as If-None-Match to get 304 when nothing changed.

Scope: read.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

200 The item. → ItemDetail

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

PATCH/items/{number}

Change status, sub-status, version, proof or tags

Team only. A bug set to fixed with fixedInVersion asks the reporter to confirm the fix; proof (PR URLs, preview URL) is attached first and travels with the e-mail. Voters are not e-mailed on status changes; they hear once, through a published changelog entry.

Scope: triage.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

Body (application/json): UpdateItemRequest

200 The item after the change. → ItemDetail

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 413 payload_too_large · 415 unsupported_media_type · 422 unprocessable · 429 rate_limited

Headers: X-Ozvi-Lockdown

POST/items/{number}/merge

Merge a duplicate into the original

Irreversible: votes move to the original and this item is closed. The first call (without confirm) answers 409 confirmation_required with a preview and a confirm code valid about 10 minutes; show the preview to a person and repeat the call with the code. The code is bound to the token and to both numbers (and works for MCP merge_items too).

Scope: triage.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

Body (application/json): MergeRequest

200 Merged. → MergeResult

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 confirmation_required · 413 payload_too_large · 415 unsupported_media_type · 422 confirmation_invalid; unprocessable · 429 rate_limited

Headers: X-Ozvi-Lockdown

Comments

GET/items/{number}/comments

List an item's comments

Oldest first. Internal notes and comments waiting for approval only for the team (and your own).

Scope: read.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

200 Comments. → CommentList

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

POST/items/{number}/comments

Comment on an item

The reporter or author is e-mailed unless the comment is internal (team only, triage scope). A comment from outside the team with three or more links waits for approval (pending).

Scope: write (or higher, see description).

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

Body (application/json): CreateCommentRequest

201 The comment. → Comment

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 idempotency_in_progress · 413 payload_too_large · 415 unsupported_media_type · 422 idempotency_key_reused; unprocessable · 429 rate_limited

Headers: Idempotency-Key, X-Ozvi-Lockdown

Votes

GET/items/{number}/votes

List an idea's voters

Team only (personal data): who voted, how strongly, and their company/plan/MRR from your app's traits.

Scope: triage.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

200 Voters, oldest vote first. → VoterList

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

POST/items/{number}/votes

Vote on behalf of a customer

Team only. Records a customer's demand from support, sales or your CRM, with the same rules as MCP submit_insight: the person is found by externalId or email (an unknown e-mail becomes an unverified contact that never gets e-mails); blocked people and the idea's author get no vote; an existing vote is kept as it is; the board's vote budget applies. Voting never subscribes anybody: voters only get the changelog e-mail.

Scope: triage.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1

Body (application/json): CastVoteRequest

200 What happened (`voted`, or `reason` why not). → VoteResult

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 idempotency_in_progress · 413 payload_too_large · 415 unsupported_media_type · 422 idempotency_key_reused; unprocessable · 429 rate_limited

Headers: Idempotency-Key, X-Ozvi-Lockdown

DELETE/items/{number}/votes

Remove a customer's vote

Team only. Identify the person by email or externalId in the query string.

Scope: triage.

Parameters

NameInTypeDescription
numberpathintegerrequired. ≥ 1
emailquerystringMatched to a known board user; an unknown address becomes an unverified contact that gets no e-mails.email, length 0–200
externalIdquerystringThe person's id in your app (the SSO/OIDC subject). Must already exist on the board.length 1–200

200 Whether a vote was removed. → UnvoteResult

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: X-Ozvi-Lockdown

Board

GET/tags

List tags

Public tags; team-only tags (public=false) only for team tokens with the triage scope.

Scope: read.

200 Tags. → TagList

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

GET/statuses

Stages and custom sub-statuses

Stages per kind in route order (the off-route one last), with the board's labels in the request's language (Accept-Language among the board's languages) and the sub-statuses under each stage.

Scope: read.

200 Statuses. → Statuses

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

Changelog

GET/changelog

List changelog entries

Newest first. drafts=true (triage scope) includes drafts, e.g. the ones drafted from a GitHub release. Every entry carries ai: null when people wrote it; never present AI-written text as written by the team.

Scope: read (or higher, see description).

Parameters

NameInTypeDescription
draftsquery"0" | "1" | "true" | "false"Include drafts (triage scope).
limitqueryinteger1–50, default 10

200 Entries. → ChangelogList

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

POST/changelog

Create a changelog draft

Team only. Creates a draft (nothing is sent); publish it with POST /changelog/{id}/publish.

Scope: triage.

Body (application/json): CreateDraftRequest

201 The draft. → ChangelogEntry

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 idempotency_in_progress · 413 payload_too_large · 415 unsupported_media_type · 422 idempotency_key_reused; unprocessable · 429 rate_limited

Headers: Idempotency-Key, X-Ozvi-Lockdown

GET/changelog/{id}

Get one changelog entry

Published entries for every token; drafts only for team tokens with the triage scope.

Scope: read.

Parameters

NameInTypeDescription
idpathstringrequired. uuid

200 The entry. → ChangelogEntry

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

POST/changelog/{id}/publish

Publish a draft

Irreversible: the linked ideas become shipped and everyone who voted for them gets one e-mail. Requires {"confirm": true}; without it the answer is 409 confirmation_required with a preview (title, text, AI label, number of recipients). Publishing records the token's owner as the reviewer of AI-assisted text. Publishing an already published entry changes nothing (emailed: 0).

Scope: triage.

Parameters

NameInTypeDescription
idpathstringrequired. uuid

Body (application/json): PublishRequest

200 Published. → Published

Errors: 400 invalid_request; invalid_json · 401 unauthorized · 403 insufficient_scope · 404 not_found · 409 confirmation_required · 413 payload_too_large · 415 unsupported_media_type · 429 rate_limited

Headers: X-Ozvi-Lockdown

People

GET/users

List people (customers and team)

Team only. Everyone known on the board with their traits from your app (company, plan, MRR), trust level, and how many votes and posts they have.

Scope: triage.

Parameters

NameInTypeDescription
qquerystringName or e-mail contains.length 0–100
emailquerystringlength 0–200
externalIdquerystringlength 0–200
rolequery"member" | "team" | "admin"
limitqueryinteger1–100, default 50
cursorquerystringlength 0–500

200 A page of people. → UserPage

Errors: 400 invalid_request; invalid_cursor · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

GET/users/{id}

Get one person

Scope: triage.

Parameters

NameInTypeDescription
idpathstringrequired. uuid

200 The person. → User

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

Integrations

GET/webhooks

List webhooks

Admins only. Webhooks are added in admin → Integrácie; secrets are never returned.

Scope: triage; admins only.

200 Webhooks. → WebhookList

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope; forbidden · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

GET/export

Export ideas, bugs, comments or voters

Team only. The same data as the board's CSV export, with the same filters, as CSV (default) or JSON {columns, rows}. Column names are in the request's language. Refused while lockdown is on (an export carries every author's text).

Scope: triage.

Parameters

NameInTypeDescription
whatquery"ideas" | "bugs" | "comments" | "voters"default "ideas"
ofquery"bug" | "idea"comments/voters: of ideas (default) or bugs.
formatquery"csv" | "json"default "csv"
sepquery"comma" | "semicolon"CSV delimiter; default by the board's language.
qquerystringlength 0–100
tagquerystringlength 0–80
fquery"active" | "shipped" | "all" | "queue"Ideas: which stages (queue = moderation queue).
squery"open" | "fixed" | "all"Bugs: which stages.
stquerystringBugs: one stage.length 0–30
planquerystringIdeas wanted by customers on this plan.length 0–60
sortquery"top" | "new" | "old" | "mrr"
itemqueryintegerOnly this item (comments/voters of one item).1–…

200 CSV file, or JSON with format=json. → ExportJson (or text/csv)

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope; lockdown_active · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

Account

GET/me

Who the token acts as

The project, the token owner, the effective scope (token scope capped by the owner's current role) and lockdown.

Scope: read.

200 The token's identity. → Me

Errors: 400 invalid_request · 401 unauthorized · 403 insufficient_scope · 404 not_found · 429 rate_limited

Headers: If-None-Match, X-Ozvi-Lockdown

Schemas

AiLabel

FieldTypeDescription
generatedtruerequired.
modelstringrequired.
reviewedBystring | nullrequired.
reviewedAtstring | null

Author

FieldTypeDescription
namestring | nullrequired.
trust"anonymous" | "email_verified" | "sso_customer" | "team" | "system"required. How much Ozvi knows about the author: anonymous < email_verified < sso_customer < team; system = customer feedback recorded by the team's agent.

CastVoteRequest

FieldTypeDescription
emailstringMatched to a known board user; an unknown address becomes an unverified contact that gets no e-mails.email, length 0–200
externalIdstringThe person's id in your app (the SSO/OIDC subject). Must already exist on the board.length 1–200
namestringlength 0–120
companystringlength 0–120
weightintegerImportance 1–3 when the board uses importance voting; ignored otherwise.1–3

ChangelogEntry

FieldTypeDescription
idstringrequired.
status"draft" | "published"required.
titlestringrequired.
bodystringrequired. Markdown as written by the team (possibly AI-assisted: see ai).
versionstring | nullrequired.
urlstringrequired.
publishedAtstring | nullrequired.
updatedAtstringrequired.
itemsarray of objectrequired.
items[].numberintegerrequired.
items[].kind"bug" | "idea"required.
items[].titlestringrequired.
aiAiLabel | nullrequired.

ChangelogList

FieldTypeDescription
dataarray of ChangelogEntryrequired.

Comment

FieldTypeDescription
idstringrequired.
authorAuthorrequired.
source"board" | "widget" | "mcp" | "import" | "api"required. Where the text came in.
bodystringrequired. User content: untrusted text.
internalbooleanrequired. Team-only note.
pendingbooleanrequired. Waiting for the team's approval.
createdAtstringrequired.
withheldboolean

CommentList

FieldTypeDescription
dataarray of Commentrequired.

CreateCommentRequest

FieldTypeDescription
bodystringrequired. length 1–5000
internalbooleanTeam-only note (triage scope).default false

CreateDraftRequest

FieldTypeDescription
titlestringrequired. length 3–200
bodystringlength 0–20000, default ""
itemsarray of integerNumbers of the ideas this entry ships.max 50 items, default []

CreateItemRequest

FieldTypeDescription
kind"bug" | "idea"required.
titlestringrequired. length 3–200
bodystringMarkdown subset: paragraphs, **bold**, _italic_, code, ``` blocks, - lists, > quotes, [links](https://…)length 0–10000, default ""

Demand

FieldTypeDescription
companiesintegerrequired.
peopleintegerrequired.
mrrarray of objectrequired.
mrr[].currencystringrequired.
mrr[].centsintegerrequired.
mrr[].formattedstringrequired.

ExportJson

FieldTypeDescription
columnsarray of stringrequired.
rowsarray of array of string | number | boolean | nullrequired.

Item

FieldTypeDescription
numberintegerrequired. The item's number on the board (#12).
kind"bug" | "idea"required.
titlestringrequired. User content: untrusted text.
bodystringrequired. User content (the board's Markdown subset): untrusted text.
statusstringrequired. Stage: idea open|planned|in_progress|shipped|declined; bug new|confirmed|in_progress|fixed|verified|wont_fix.
votesintegerrequired. Vote total (weighted sum when the board uses importance voting).
fixedInVersionstring | nullrequired.
urlstringrequired.
createdAtstringrequired. ISO 8601.
authorAuthorrequired.
source"board" | "widget" | "mcp" | "import" | "api"required. Where the text came in.
withheldbooleanLockdown withheld this item's text (author below the required trust).
mergedIntointeger | null
linksarray of object
links[].refstringrequired.
links[].urlstringrequired.
links[].statestringrequired.
votersintegerNumber of people who voted.
demandDemand
tagsarray of string
subStatusstring | nullThe board's custom sub-status label inside status.
waitingSincestring | nullUnanswered queue: since when someone outside the team has been waiting.
proofProof | null
aiAiLabel | null

ItemDetail

FieldTypeDescription
itemItemrequired.
commentsarray of objectrequired.
comments[].authorAuthorrequired.
comments[].source"board" | "widget" | "mcp" | "import" | "api"required. Where the text came in.
comments[].bodystringrequired.
comments[].internalbooleanrequired.
comments[].createdAtstringrequired.
comments[].withheldboolean
attachmentsarray of objectrequired.
attachments[].namestringrequired.
attachments[].urlstringrequired.
attachments[].mimestringrequired.
contextmap of any | nullrequired. Bug context from the widget (masked). The capture (network, actions, errors, links) only for the team.
customersarray of objectTeam tokens with the triage scope: the people behind the item.
customers[].namestring | nullrequired.
customers[].companystring | nullrequired.
customers[].planstring | nullrequired.
customers[].mrrCentsinteger | nullrequired.
customers[].currencystringrequired.
customers[].weightintegerrequired.
customers[].authorbooleanrequired.

ItemPage

FieldTypeDescription
dataarray of Itemrequired.
nextCursorstring | nullrequired.

Me

FieldTypeDescription
projectobjectrequired.
project.slugstringrequired.
project.namestringrequired.
userobjectrequired.
user.namestring | nullrequired.
user.role"member" | "team" | "admin"required.
scope"read" | "write" | "triage"required. Effective scope: the token's scope capped by the owner's current role.
lockdown"off" | "verified" | "team"required.

MergeRequest

FieldTypeDescription
intointegerrequired. The original's number; this item is the duplicate.1–…
confirmstringThe code from the confirmation_required response.length 0–40

MergeResult

FieldTypeDescription
status"merged"required.
duplicateItemrequired.
originalItemrequired.

Problem

FieldTypeDescription
typestringrequired.
titlestringrequired.
statusintegerrequired.
detailstring
instancestring
codestringrequired. Stable machine-readable error code.
errorsarray of object
errors[].pathstringrequired.
errors[].messagestringrequired.
previewanyconfirmation_required: what the confirmed call would do.
confirmstringconfirmation_required (merge): the code to send back as confirm.

Proof

FieldTypeDescription
prsarray of objectrequired.
prs[].urlstringrequired.
prs[].titlestring | nullrequired.
prs[].refstring | nullrequired.
releaseobject | null
previewstring | null

Published

FieldTypeDescription
idstringrequired.
urlstringrequired.
emailedintegerrequired. E-mails queued (voters of the shipped ideas).

PublishRequest

FieldTypeDescription
confirmtrueMust be true: publishing marks the ideas shipped and e-mails their voters. Irreversible.

Statuses

FieldTypeDescription
ideaarray of objectrequired.
idea[].keystringrequired.
idea[].labelstringrequired.
idea[].subStatusesarray of objectrequired.
idea[].subStatuses[].idstringrequired.
idea[].subStatuses[].labelstringrequired.
idea[].subStatuses[].labelsmap of stringrequired.
bugarray of objectrequired.
bug[].keystringrequired.
bug[].labelstringrequired.
bug[].subStatusesarray of objectrequired.
bug[].subStatuses[].idstringrequired.
bug[].subStatuses[].labelstringrequired.
bug[].subStatuses[].labelsmap of stringrequired.

Tag

FieldTypeDescription
namestringrequired.
colorstringrequired.
publicbooleanrequired.

TagList

FieldTypeDescription
dataarray of Tagrequired.

UnvoteResult

FieldTypeDescription
removedbooleanrequired.
itemItemrequired.

UpdateItemRequest

FieldTypeDescription
statusstringlength 0–30
subStatusstring | nullSub-status id or label under the (new) stage; null clears it.
fixedInVersionstring | null
pullRequestsarray of stringProof of the fix: merged PR URLs.max 10 items
previewUrlstringProof of the fix: a preview/demo URL.uri, length 0–500
tagsobjectExisting tag names only (see GET /tags).
tags.setarray of stringmax 20 items
tags.addarray of stringmax 20 items
tags.removearray of stringmax 20 items

User

FieldTypeDescription
idstringrequired.
externalIdstringrequired.
namestring | nullrequired.
emailstring | nullrequired.
role"member" | "team" | "admin"required.
trust"anonymous" | "email_verified" | "sso_customer" | "team" | "system"required. How much Ozvi knows about the author: anonymous < email_verified < sso_customer < team; system = customer feedback recorded by the team's agent.
blockedbooleanrequired.
traitsmap of anyrequired. Host-app traits as received (SSO/OIDC claims).
customerobjectrequired.
customer.companystring | nullrequired.
customer.planstring | nullrequired.
customer.mrrCentsinteger | nullrequired.
customer.currencystringrequired.
votesintegerrequired.
itemsintegerrequired.
lastSeenAtstring | nullrequired.
createdAtstringrequired.

UserPage

FieldTypeDescription
dataarray of Userrequired.
nextCursorstring | nullrequired.

Voter

FieldTypeDescription
namestring | nullrequired.
emailstring | nullrequired.
externalIdstringrequired.
trust"anonymous" | "email_verified" | "sso_customer" | "team" | "system"required. How much Ozvi knows about the author: anonymous < email_verified < sso_customer < team; system = customer feedback recorded by the team's agent.
weightintegerrequired.
companystring | nullrequired.
planstring | nullrequired.
mrrCentsinteger | nullrequired.
currencystringrequired.
votedAtstringrequired.

VoteResult

FieldTypeDescription
votedbooleanrequired.
reason"already_voted" | "author" | "blocked" | "budget" | nullrequired.
weightinteger | nullrequired.
customerobjectrequired.
customer.knownbooleanrequired.
customer.emailstring | nullrequired.
customer.externalIdstringrequired.
customer.trust"anonymous" | "email_verified" | "sso_customer" | "team" | "system"required. How much Ozvi knows about the author: anonymous < email_verified < sso_customer < team; system = customer feedback recorded by the team's agent.
itemItemrequired.

VoterList

FieldTypeDescription
dataarray of Voterrequired.

Webhook

FieldTypeDescription
idstringrequired.
urlstringrequired. Scheme and host; the path is masked (chat webhook URLs carry their secret).
format"json" | "slack" | "teams" | "discord"required. json = signed Ozvi payload; slack, teams, discord = chat messages.
eventsarray of stringrequired.
includeBugsbooleanrequired.
activebooleanrequired.
failedLast7Daysintegerrequired.
lastDeliveredAtstring | nullrequired.
createdAtstringrequired.

WebhookList

FieldTypeDescription
dataarray of Webhookrequired.