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:
https://ozvi.io/acme/api/v1- Open the board, then My API tokens (
/acme/tokens), and create a token. Pick the smallest scope that works. - Send it in the
Authorizationheader.
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."}'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.
| Scope | Allows | Who |
|---|---|---|
read | Read ideas, your own bugs (everything for the team), comments, tags, statuses, the published changelog. | anyone |
write | Also file ideas and bugs and comment, as the token's owner. | anyone |
triage | Also 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.truston every item and comment says how much Ozvi knows about the writer:anonymous<email_verified<sso_customer<team;systemmarks ideas filed from customer feedback by the team's agent.sourcesays where it came in:board,widget,mcp,importorapi. Everything created through this API isapi.- Lockdown: send
X-Ozvi-Lockdown: verified(orteam) and text from less trusted authors is withheld: the item keeps its number, status and votes, its title becomes a placeholder,bodyis empty andwithheld: trueis 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:nullwhen people wrote it, otherwise{ generated: true, model, reviewedBy }.reviewedBystays 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).
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.
{
"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"
}| code | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid token |
token_audience | 401 | This token is not valid for this API |
insufficient_scope | 403 | The token's scope does not allow this |
forbidden | 403 | The token owner's role does not allow this |
lockdown_active | 403 | Not available while lockdown is on |
not_found | 404 | Not found |
method_not_allowed | 405 | Method not allowed |
confirmation_required | 409 | This action needs confirmation |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running |
payload_too_large | 413 | Request body too large |
unsupported_media_type | 415 | Send the body as application/json |
invalid_request | 400 | The request is not valid |
invalid_json | 400 | The body is not valid JSON |
invalid_cursor | 400 | The cursor is not valid for this list |
unprocessable | 422 | The request could not be carried out |
confirmation_invalid | 422 | The confirmation code is wrong or expired |
idempotency_key_reused | 422 | This Idempotency-Key was used with a different request |
rate_limited | 429 | Too many requests |
internal | 500 | Internal 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.
RateLimit-Limit: 600
RateLimit-Remaining: 594
RateLimit-Reset: 412Idempotency 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 answers409 confirmation_requiredwith apreviewand aconfirmcode. 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
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
| Name | In | Type | Description |
|---|---|---|---|
kind | query | "bug" | "idea" | |
status | query | string | Stage key (see GET /statuses).length 0–30 |
tag | query | string | Tag name, case-insensitive. Team-only tags only filter for team tokens.length 0–40 |
q | query | string | Full-text search (title and body); results by relevance.length 0–200 |
sort | query | "new" | "top" | "mrr" | new = newest number first; top = most votes; mrr = by customers' revenue (triage scope).default "new" |
unanswered | query | "0" | "1" | "true" | "false" | The team's unanswered queue, longest wait first (triage scope). |
limit | query | integer | 1–100, default 25 |
cursor | query | string | nextCursor 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 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
| Name | In | Type | Description |
|---|---|---|---|
number | path | integer | required. ≥ 1 |
email | query | string | Matched to a known board user; an unknown address becomes an unverified contact that gets no e-mails.email, length 0–200 |
externalId | query | string | The 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
| Name | In | Type | Description |
|---|---|---|---|
drafts | query | "0" | "1" | "true" | "false" | Include drafts (triage scope). |
limit | query | integer | 1–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
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | required. 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
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | required. 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
| Name | In | Type | Description |
|---|---|---|---|
q | query | string | Name or e-mail contains.length 0–100 |
email | query | string | length 0–200 |
externalId | query | string | length 0–200 |
role | query | "member" | "team" | "admin" | |
limit | query | integer | 1–100, default 50 |
cursor | query | string | length 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
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | required. 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
| Name | In | Type | Description |
|---|---|---|---|
what | query | "ideas" | "bugs" | "comments" | "voters" | default "ideas" |
of | query | "bug" | "idea" | comments/voters: of ideas (default) or bugs. |
format | query | "csv" | "json" | default "csv" |
sep | query | "comma" | "semicolon" | CSV delimiter; default by the board's language. |
q | query | string | length 0–100 |
tag | query | string | length 0–80 |
f | query | "active" | "shipped" | "all" | "queue" | Ideas: which stages (queue = moderation queue). |
s | query | "open" | "fixed" | "all" | Bugs: which stages. |
st | query | string | Bugs: one stage.length 0–30 |
plan | query | string | Ideas wanted by customers on this plan.length 0–60 |
sort | query | "top" | "new" | "old" | "mrr" | |
item | query | integer | Only 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
| Field | Type | Description |
|---|---|---|
generated | true | required. |
model | string | required. |
reviewedBy | string | null | required. |
reviewedAt | string | null |
Author
| Field | Type | Description |
|---|---|---|
name | string | null | 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. |
CastVoteRequest
| Field | Type | Description |
|---|---|---|
email | string | Matched to a known board user; an unknown address becomes an unverified contact that gets no e-mails.email, length 0–200 |
externalId | string | The person's id in your app (the SSO/OIDC subject). Must already exist on the board.length 1–200 |
name | string | length 0–120 |
company | string | length 0–120 |
weight | integer | Importance 1–3 when the board uses importance voting; ignored otherwise.1–3 |
ChangelogEntry
| Field | Type | Description |
|---|---|---|
id | string | required. |
status | "draft" | "published" | required. |
title | string | required. |
body | string | required. Markdown as written by the team (possibly AI-assisted: see ai). |
version | string | null | required. |
url | string | required. |
publishedAt | string | null | required. |
updatedAt | string | required. |
items | array of object | required. |
items[].number | integer | required. |
items[].kind | "bug" | "idea" | required. |
items[].title | string | required. |
ai | AiLabel | null | required. |
ChangelogList
| Field | Type | Description |
|---|---|---|
data | array of ChangelogEntry | required. |
Comment
| Field | Type | Description |
|---|---|---|
id | string | required. |
author | Author | required. |
source | "board" | "widget" | "mcp" | "import" | "api" | required. Where the text came in. |
body | string | required. User content: untrusted text. |
internal | boolean | required. Team-only note. |
pending | boolean | required. Waiting for the team's approval. |
createdAt | string | required. |
withheld | boolean |
CommentList
| Field | Type | Description |
|---|---|---|
data | array of Comment | required. |
CreateCommentRequest
| Field | Type | Description |
|---|---|---|
body | string | required. length 1–5000 |
internal | boolean | Team-only note (triage scope).default false |
CreateDraftRequest
| Field | Type | Description |
|---|---|---|
title | string | required. length 3–200 |
body | string | length 0–20000, default "" |
items | array of integer | Numbers of the ideas this entry ships.max 50 items, default [] |
CreateItemRequest
| Field | Type | Description |
|---|---|---|
kind | "bug" | "idea" | required. |
title | string | required. length 3–200 |
body | string | Markdown subset: paragraphs, **bold**, _italic_, code, ``` blocks, - lists, > quotes, [links](https://…)length 0–10000, default "" |
Demand
| Field | Type | Description |
|---|---|---|
companies | integer | required. |
people | integer | required. |
mrr | array of object | required. |
mrr[].currency | string | required. |
mrr[].cents | integer | required. |
mrr[].formatted | string | required. |
ExportJson
| Field | Type | Description |
|---|---|---|
columns | array of string | required. |
rows | array of array of string | number | boolean | null | required. |
Item
| Field | Type | Description |
|---|---|---|
number | integer | required. The item's number on the board (#12). |
kind | "bug" | "idea" | required. |
title | string | required. User content: untrusted text. |
body | string | required. User content (the board's Markdown subset): untrusted text. |
status | string | required. Stage: idea open|planned|in_progress|shipped|declined; bug new|confirmed|in_progress|fixed|verified|wont_fix. |
votes | integer | required. Vote total (weighted sum when the board uses importance voting). |
fixedInVersion | string | null | required. |
url | string | required. |
createdAt | string | required. ISO 8601. |
author | Author | required. |
source | "board" | "widget" | "mcp" | "import" | "api" | required. Where the text came in. |
withheld | boolean | Lockdown withheld this item's text (author below the required trust). |
mergedInto | integer | null | |
links | array of object | |
links[].ref | string | required. |
links[].url | string | required. |
links[].state | string | required. |
voters | integer | Number of people who voted. |
demand | Demand | |
tags | array of string | |
subStatus | string | null | The board's custom sub-status label inside status. |
waitingSince | string | null | Unanswered queue: since when someone outside the team has been waiting. |
proof | Proof | null | |
ai | AiLabel | null |
ItemDetail
| Field | Type | Description |
|---|---|---|
item | Item | required. |
comments | array of object | required. |
comments[].author | Author | required. |
comments[].source | "board" | "widget" | "mcp" | "import" | "api" | required. Where the text came in. |
comments[].body | string | required. |
comments[].internal | boolean | required. |
comments[].createdAt | string | required. |
comments[].withheld | boolean | |
attachments | array of object | required. |
attachments[].name | string | required. |
attachments[].url | string | required. |
attachments[].mime | string | required. |
context | map of any | null | required. Bug context from the widget (masked). The capture (network, actions, errors, links) only for the team. |
customers | array of object | Team tokens with the triage scope: the people behind the item. |
customers[].name | string | null | required. |
customers[].company | string | null | required. |
customers[].plan | string | null | required. |
customers[].mrrCents | integer | null | required. |
customers[].currency | string | required. |
customers[].weight | integer | required. |
customers[].author | boolean | required. |
ItemPage
| Field | Type | Description |
|---|---|---|
data | array of Item | required. |
nextCursor | string | null | required. |
Me
| Field | Type | Description |
|---|---|---|
project | object | required. |
project.slug | string | required. |
project.name | string | required. |
user | object | required. |
user.name | string | null | required. |
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
| Field | Type | Description |
|---|---|---|
into | integer | required. The original's number; this item is the duplicate.1–… |
confirm | string | The code from the confirmation_required response.length 0–40 |
Problem
| Field | Type | Description |
|---|---|---|
type | string | required. |
title | string | required. |
status | integer | required. |
detail | string | |
instance | string | |
code | string | required. Stable machine-readable error code. |
errors | array of object | |
errors[].path | string | required. |
errors[].message | string | required. |
preview | any | confirmation_required: what the confirmed call would do. |
confirm | string | confirmation_required (merge): the code to send back as confirm. |
Proof
| Field | Type | Description |
|---|---|---|
prs | array of object | required. |
prs[].url | string | required. |
prs[].title | string | null | required. |
prs[].ref | string | null | required. |
release | object | null | |
preview | string | null |
Published
| Field | Type | Description |
|---|---|---|
id | string | required. |
url | string | required. |
emailed | integer | required. E-mails queued (voters of the shipped ideas). |
PublishRequest
| Field | Type | Description |
|---|---|---|
confirm | true | Must be true: publishing marks the ideas shipped and e-mails their voters. Irreversible. |
Statuses
| Field | Type | Description |
|---|---|---|
idea | array of object | required. |
idea[].key | string | required. |
idea[].label | string | required. |
idea[].subStatuses | array of object | required. |
idea[].subStatuses[].id | string | required. |
idea[].subStatuses[].label | string | required. |
idea[].subStatuses[].labels | map of string | required. |
bug | array of object | required. |
bug[].key | string | required. |
bug[].label | string | required. |
bug[].subStatuses | array of object | required. |
bug[].subStatuses[].id | string | required. |
bug[].subStatuses[].label | string | required. |
bug[].subStatuses[].labels | map of string | required. |
Tag
| Field | Type | Description |
|---|---|---|
name | string | required. |
color | string | required. |
public | boolean | required. |
TagList
| Field | Type | Description |
|---|---|---|
data | array of Tag | required. |
UnvoteResult
| Field | Type | Description |
|---|---|---|
removed | boolean | required. |
item | Item | required. |
UpdateItemRequest
| Field | Type | Description |
|---|---|---|
status | string | length 0–30 |
subStatus | string | null | Sub-status id or label under the (new) stage; null clears it. |
fixedInVersion | string | null | |
pullRequests | array of string | Proof of the fix: merged PR URLs.max 10 items |
previewUrl | string | Proof of the fix: a preview/demo URL.uri, length 0–500 |
tags | object | Existing tag names only (see GET /tags). |
tags.set | array of string | max 20 items |
tags.add | array of string | max 20 items |
tags.remove | array of string | max 20 items |
User
| Field | Type | Description |
|---|---|---|
id | string | required. |
externalId | string | required. |
name | string | null | required. |
email | string | null | required. |
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. |
blocked | boolean | required. |
traits | map of any | required. Host-app traits as received (SSO/OIDC claims). |
customer | object | required. |
customer.company | string | null | required. |
customer.plan | string | null | required. |
customer.mrrCents | integer | null | required. |
customer.currency | string | required. |
votes | integer | required. |
items | integer | required. |
lastSeenAt | string | null | required. |
createdAt | string | required. |
UserPage
| Field | Type | Description |
|---|---|---|
data | array of User | required. |
nextCursor | string | null | required. |
Voter
| Field | Type | Description |
|---|---|---|
name | string | null | required. |
email | string | null | required. |
externalId | string | 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. |
weight | integer | required. |
company | string | null | required. |
plan | string | null | required. |
mrrCents | integer | null | required. |
currency | string | required. |
votedAt | string | required. |
VoteResult
| Field | Type | Description |
|---|---|---|
voted | boolean | required. |
reason | "already_voted" | "author" | "blocked" | "budget" | null | required. |
weight | integer | null | required. |
customer | object | required. |
customer.known | boolean | required. |
customer.email | string | null | required. |
customer.externalId | string | required. |
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. |
item | Item | required. |
VoterList
| Field | Type | Description |
|---|---|---|
data | array of Voter | required. |
Webhook
| Field | Type | Description |
|---|---|---|
id | string | required. |
url | string | required. 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. |
events | array of string | required. |
includeBugs | boolean | required. |
active | boolean | required. |
failedLast7Days | integer | required. |
lastDeliveredAt | string | null | required. |
createdAt | string | required. |
WebhookList
| Field | Type | Description |
|---|---|---|
data | array of Webhook | required. |