AI settings
AI features, their provider credentials and a connection test.
Set up the SDK client used in the examples
API key or Dashboard session:
import { UpVoiced } from '@upvoiced/sdk';
const apiKey = process.env['UPVOICED_API_KEY']; // uv_live_… (Settings → API keys)
const workspace = process.env['UPVOICED_WORKSPACE_ID']; // sent as X-Workspace
if (!apiKey || !workspace) throw new Error('Set UPVOICED_API_KEY and UPVOICED_WORKSPACE_ID.');
const upvoiced = new UpVoiced({ apiKey, workspace });Dashboard session only (API keys get 403 session_required):
import { UpVoiced } from '@upvoiced/sdk';
// Dashboard-session operations: the Clerk session token of a signed-in operator.
const upvoiced = new UpVoiced({
headers: { authorization: `Bearer ${sessionToken}` },
workspace: workspaceId,
});curl examples read $UPVOICED_API_KEY and $UPVOICED_WORKSPACE_IDfrom your shell. MCP examples are the tools/call request an MCP client sends to the MCP server with the same API key.
GET/v1/ai/settings
AI credential source, monthly call cap, connected credentials (fingerprints only) and credits (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 17 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
source | "platform" | "own_anthropic" | "openrouter" | Yes |
monthly_call_cap | integer | nullvalue 1–1000000 | Yes |
calls_this_month | integervalue ≥ 0 | Yes |
platform_available | boolean | Yes |
credentials | array of object | Yes |
credentials[].provider | "anthropic" | "openrouter" | Yes |
credentials[].fingerprint | stringpattern ^(sk-ant-|sk-or-|key-)…[A-Za-z0-9_-]{4}$ | Yes |
credentials[].status | "active" | "invalid" | Yes |
credentials[].last_error | string | null | Yes |
credentials[].last_validated_at | string (date-time) | Yes |
credentials[].last_used_at | string (date-time) | null | Yes |
credentials[].created_at | string (date-time) | Yes |
credits | object | Yes |
credits.balance | integervalue ≥ 0 | Yes |
credits.available | integervalue ≥ 0 | Yes |
credits.low | boolean | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/ai/settings' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/ai/settings');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
PATCH/v1/ai/settings
Choose UpVoiced credits, the own Anthropic key or the OpenRouter connection; set the monthly call cap (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
source | "platform" | "own_anthropic" | "openrouter" | No |
monthly_call_cap | integer | nullvalue 1–1000000 | No |
Response 200: 17 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
source | "platform" | "own_anthropic" | "openrouter" | Yes |
monthly_call_cap | integer | nullvalue 1–1000000 | Yes |
calls_this_month | integervalue ≥ 0 | Yes |
platform_available | boolean | Yes |
credentials | array of object | Yes |
credentials[].provider | "anthropic" | "openrouter" | Yes |
credentials[].fingerprint | stringpattern ^(sk-ant-|sk-or-|key-)…[A-Za-z0-9_-]{4}$ | Yes |
credentials[].status | "active" | "invalid" | Yes |
credentials[].last_error | string | null | Yes |
credentials[].last_validated_at | string (date-time) | Yes |
credentials[].last_used_at | string (date-time) | null | Yes |
credentials[].created_at | string (date-time) | Yes |
credits | object | Yes |
credits.balance | integervalue ≥ 0 | Yes |
credits.available | integervalue ≥ 0 | Yes |
credits.low | boolean | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X PATCH 'https://api.upvoiced.com/v1/ai/settings' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"source": "platform"
}'const data = await upvoiced.patch('/v1/ai/settings', { body: { source: 'platform' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/ai/test
Free connection check through the current credential source (10 per workspace and hour; owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 6 fields
| Name | Type | Required |
|---|---|---|
ok | true | Yes |
source | "platform" | "own_anthropic" | "openrouter" | Yes |
model | string | Yes |
prompt_version | string | Yes |
latency_ms | integervalue ≥ 0 | Yes |
credits | integervalue ≥ 0 | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/ai/test' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)"const data = await upvoiced.post('/v1/ai/test');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
PUT/v1/ai/credentials/anthropic
Save the workspace’s own Anthropic API key after a validation call; stored encrypted, shown only as a fingerprint (session only)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
api_key | stringpattern ^sk-ant-[A-Za-z0-9_-]{20,300}$ | Yes |
Response 200: 7 fields
| Name | Type | Required |
|---|---|---|
provider | "anthropic" | "openrouter" | Yes |
fingerprint | stringpattern ^(sk-ant-|sk-or-|key-)…[A-Za-z0-9_-]{4}$ | Yes |
status | "active" | "invalid" | Yes |
last_error | string | null | Yes |
last_validated_at | string (date-time) | Yes |
last_used_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X PUT 'https://api.upvoiced.com/v1/ai/credentials/anthropic' \
-H "Authorization: Bearer $UPVOICED_SESSION_TOKEN" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"api_key": "sk-ant-REPLACE_WITH_YOUR_KEY_000000"
}'const data = await upvoiced.put('/v1/ai/credentials/anthropic', { body: { api_key: 'sk-ant-REPLACE_WITH_YOUR_KEY_000000' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
DELETE/v1/ai/credentials/anthropic
Delete the stored Anthropic key (revoke it in the Anthropic Console too)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 2 fields
| Name | Type | Required |
|---|---|---|
ok | true | Yes |
revoke_url | string (uri) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X DELETE 'https://api.upvoiced.com/v1/ai/credentials/anthropic' \
-H "Authorization: Bearer $UPVOICED_SESSION_TOKEN" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.delete('/v1/ai/credentials/anthropic');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/ai/credentials/openrouter/connect
Start "Connect OpenRouter" (OAuth PKCE S256): the authorize URL to send the browser to (session only)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 2 fields
| Name | Type | Required |
|---|---|---|
authorize_url | string (uri) | Yes |
expires_at | string (date-time) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/ai/credentials/openrouter/connect' \
-H "Authorization: Bearer $UPVOICED_SESSION_TOKEN" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)"const data = await upvoiced.post('/v1/ai/credentials/openrouter/connect');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/ai/credentials/openrouter/callback
Finish "Connect OpenRouter": exchange the returned code with the sealed verifier and store the key (session only)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
| Name | Type | Required |
|---|---|---|
code | stringlength 1–512pattern ^[A-Za-z0-9._~-]+$ | Yes |
state | stringlength 16–2048pattern ^[A-Za-z0-9_-]+$ | Yes |
Response 200: 7 fields
| Name | Type | Required |
|---|---|---|
provider | "anthropic" | "openrouter" | Yes |
fingerprint | stringpattern ^(sk-ant-|sk-or-|key-)…[A-Za-z0-9_-]{4}$ | Yes |
status | "active" | "invalid" | Yes |
last_error | string | null | Yes |
last_validated_at | string (date-time) | Yes |
last_used_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/ai/credentials/openrouter/callback' \
-H "Authorization: Bearer $UPVOICED_SESSION_TOKEN" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"code": "acme",
"state": "feature-requests"
}'const data = await upvoiced.post('/v1/ai/credentials/openrouter/callback', { body: { code: 'acme', state: 'feature-requests' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
DELETE/v1/ai/credentials/openrouter
Disconnect OpenRouter: delete our copy of the key (revoke it on OpenRouter too)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 2 fields
| Name | Type | Required |
|---|---|---|
ok | true | Yes |
revoke_url | string (uri) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X DELETE 'https://api.upvoiced.com/v1/ai/credentials/openrouter' \
-H "Authorization: Bearer $UPVOICED_SESSION_TOKEN" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.delete('/v1/ai/credentials/openrouter');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/ai/usage
AI usage: credential source, plan grant window, purchased and held credits, calls by operation (any member; AI-006/BIL-003)
| Name | Type | Required |
|---|---|---|
period | "this_month" | "last_month" | "last_30_days"default "this_month" | No |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 35 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
source | "platform" | "own_anthropic" | "openrouter" | Yes |
plan | "free" | "starter" | "growth" | "business" | Yes |
grant | object | Yes |
grant.monthly | integervalue ≥ 0 | Yes |
grant.window_start | string (date-time) | null | Yes |
grant.window_end | string (date-time) | null | Yes |
grant.granted | integervalue ≥ 0 | Yes |
grant.remaining | integervalue ≥ 0 | Yes |
grant.expires_at | string (date-time) | null | Yes |
credits | object | Yes |
credits.balance | integervalue ≥ 0 | Yes |
credits.purchased | integervalue ≥ 0 | Yes |
credits.held | integervalue ≥ 0 | Yes |
credits.available | integervalue ≥ 0 | Yes |
credits.low | boolean | Yes |
credits.exhausted | boolean | Yes |
can_purchase | boolean | Yes |
period | object | Yes |
period.name | "this_month" | "last_month" | "last_30_days" | Yes |
period.from | string (date-time) | Yes |
period.to | string (date-time) | Yes |
totals | object | Yes |
totals.calls | integervalue ≥ 0 | Yes |
totals.credits | integervalue ≥ 0 | Yes |
operations | array of object | Yes |
operations[].kind | stringpattern ^[a-z][a-z_]{1,39}$ | Yes |
operations[].source | "platform" | "own_anthropic" | "openrouter" | Yes |
operations[].calls | integervalue ≥ 0 | Yes |
operations[].succeeded | integervalue ≥ 0 | Yes |
operations[].failed | integervalue ≥ 0 | Yes |
operations[].pending | integervalue ≥ 0 | Yes |
operations[].credits | integervalue ≥ 0 | Yes |
operations[].input_tokens | integervalue ≥ 0 | Yes |
operations[].output_tokens | integervalue ≥ 0 | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/ai/usage' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/ai/usage');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/ai/usage/ledger
AI credit ledger history, newest first, paged by sequence (any member)
| Name | Type | Required |
|---|---|---|
before | integervalue > 0 | No |
limit | integervalue 1–100default 25 | No |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 9 fields
| Name | Type | Required |
|---|---|---|
entries | array of object | Yes |
entries[].seq | integervalue > 0 | Yes |
entries[].delta | integer | Yes |
entries[].reason | "plan_grant" | "purchase" | "consume" | "refund" | "adjust" | "expire" | Yes |
entries[].operation | string | nullpattern ^[a-z][a-z_]{1,39}$ | Yes |
entries[].plan | "free" | "starter" | "growth" | "business" | null | Yes |
entries[].balance_after | integervalue ≥ 0 | Yes |
entries[].created_at | string (date-time) | Yes |
next_before | integer | nullvalue > 0 | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/ai/usage/ledger' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/ai/usage/ledger');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/ai/triage
AI triage of up to 100 posts: summary, sentiment and board/tag/duplicate suggestions with their state (any member)
| Name | Type | Required |
|---|---|---|
post_ids | stringlength ≤ 3700pattern ^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}(,[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}){0,99}$ | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 16 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
items | array of objectitems ≤ 100 | Yes |
items[].post_id | string (uuid) | Yes |
items[].status | "pending" | "ready" | "paused" | "failed" | Yes |
items[].reason | string | null | Yes |
items[].summary | string | nulllength ≤ 160 | Yes |
items[].sentiment | "positive" | "neutral" | "negative" | "mixed" | null | Yes |
items[].ai_call_id | string (uuid) | null | Yes |
items[].updated_at | string (date-time) | Yes |
items[].suggestions | array of objectitems ≤ 20 | Yes |
items[].suggestions[].id | string (uuid) | Yes |
items[].suggestions[].kind | "board" | "tag" | "duplicate" | Yes |
items[].suggestions[].state | "open" | "applied" | "dismissed" | Yes |
items[].suggestions[].confidence | number | nullvalue 0–1 | Yes |
items[].suggestions[].target | object | Yes |
items[].suggestions[].decided_at | string (date-time) | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/ai/triage?post_ids=x' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/ai/triage', { query: { post_ids: 'x' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/ai/suggestions/{id}/apply
Apply one AI suggestion through the normal post update or merge path; audited with its AI call (idempotent)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 16 fields
| Name | Type | Required |
|---|---|---|
post_id | string (uuid) | Yes |
status | "pending" | "ready" | "paused" | "failed" | Yes |
reason | string | null | Yes |
summary | string | nulllength ≤ 160 | Yes |
sentiment | "positive" | "neutral" | "negative" | "mixed" | null | Yes |
ai_call_id | string (uuid) | null | Yes |
updated_at | string (date-time) | Yes |
suggestions | array of objectitems ≤ 20 | Yes |
suggestions[].id | string (uuid) | Yes |
suggestions[].kind | "board" | "tag" | "duplicate" | Yes |
suggestions[].state | "open" | "applied" | "dismissed" | Yes |
suggestions[].confidence | number | nullvalue 0–1 | Yes |
suggestions[].target | object | Yes |
suggestions[].target.id | string (uuid) | Yes |
suggestions[].target.label | stringlength 1–300 | Yes |
suggestions[].decided_at | string (date-time) | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/ai/suggestions/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/apply' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'const data = await upvoiced.post('/v1/ai/suggestions/{id}/apply', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: {},
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/ai/suggestions/{id}/dismiss
Dismiss one AI suggestion; remembered, so a re-run never offers it again
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 16 fields
| Name | Type | Required |
|---|---|---|
post_id | string (uuid) | Yes |
status | "pending" | "ready" | "paused" | "failed" | Yes |
reason | string | null | Yes |
summary | string | nulllength ≤ 160 | Yes |
sentiment | "positive" | "neutral" | "negative" | "mixed" | null | Yes |
ai_call_id | string (uuid) | null | Yes |
updated_at | string (date-time) | Yes |
suggestions | array of objectitems ≤ 20 | Yes |
suggestions[].id | string (uuid) | Yes |
suggestions[].kind | "board" | "tag" | "duplicate" | Yes |
suggestions[].state | "open" | "applied" | "dismissed" | Yes |
suggestions[].confidence | number | nullvalue 0–1 | Yes |
suggestions[].target | object | Yes |
suggestions[].target.id | string (uuid) | Yes |
suggestions[].target.label | stringlength 1–300 | Yes |
suggestions[].decided_at | string (date-time) | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/ai/suggestions/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/dismiss' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'const data = await upvoiced.post('/v1/ai/suggestions/{id}/dismiss', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: {},
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.