Issue links and GitHub sync
GitHub, Linear and Jira issue links on posts, the GitHub status they sync in, suggested status changes, and the GitHub webhook settings.
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 });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/posts/{id}/links
GitHub, Linear and Jira issue links of a post with synced GitHub status and any status suggestion (any member)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 17 fields
| Name | Type | Required |
|---|---|---|
items | array of object | Yes |
items[].id | string (uuid) | Yes |
items[].post_id | string (uuid) | Yes |
items[].provider | "github" | "linear" | "jira" | Yes |
items[].kind | "issue" | "pull_request" | Yes |
items[].url | string | Yes |
items[].ref | string | Yes |
items[].status | "open" | "closed" | "merged" | null | Yes |
items[].status_synced_at | string (date-time) | null | Yes |
items[].suggestion | object | null | Yes |
items[].suggestion.status_id | string (uuid) | Yes |
items[].suggestion.status_name | string | Yes |
items[].suggestion.reason | "completed" | "not_planned" | "merged" | "reopened" | Yes |
items[].suggestion.at | string (date-time) | Yes |
items[].created_at | string (date-time) | Yes |
items[].created_by | string (uuid) | Yes |
next_cursor | 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/posts/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/links' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/posts/{id}/links', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/posts/{id}/links
Link a GitHub issue or pull request, Linear issue or Jira issue by URL; the provider is detected from the URL only (at most 20 per post)
| 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 |
| Name | Type | Required |
|---|---|---|
url | stringlength 1–2048 | Yes |
Response 201: 15 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
post_id | string (uuid) | Yes |
provider | "github" | "linear" | "jira" | Yes |
kind | "issue" | "pull_request" | Yes |
url | string | Yes |
ref | string | Yes |
status | "open" | "closed" | "merged" | null | Yes |
status_synced_at | string (date-time) | null | Yes |
suggestion | object | null | Yes |
suggestion.status_id | string (uuid) | Yes |
suggestion.status_name | string | Yes |
suggestion.reason | "completed" | "not_planned" | "merged" | "reopened" | Yes |
suggestion.at | string (date-time) | Yes |
created_at | string (date-time) | Yes |
created_by | string (uuid) | 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/posts/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/links' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/upvoiced"
}'const data = await upvoiced.post('/v1/posts/{id}/links', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: { url: 'https://example.com/upvoiced' },
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
DELETE/v1/post-links/{id}
Remove an issue link from its post
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 1 fields
| Name | Type | Required |
|---|---|---|
ok | true | 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/post-links/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.delete('/v1/post-links/{id}', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/post-links/{id}/suggestion/apply
Apply the status change a synced GitHub event suggested, through the normal post status update; clears the suggestion
| 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: 15 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
post_id | string (uuid) | Yes |
provider | "github" | "linear" | "jira" | Yes |
kind | "issue" | "pull_request" | Yes |
url | string | Yes |
ref | string | Yes |
status | "open" | "closed" | "merged" | null | Yes |
status_synced_at | string (date-time) | null | Yes |
suggestion | object | null | Yes |
suggestion.status_id | string (uuid) | Yes |
suggestion.status_name | string | Yes |
suggestion.reason | "completed" | "not_planned" | "merged" | "reopened" | Yes |
suggestion.at | string (date-time) | Yes |
created_at | string (date-time) | Yes |
created_by | string (uuid) | 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/post-links/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/suggestion/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/post-links/{id}/suggestion/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/post-links/{id}/suggestion/dismiss
Dismiss the status suggestion of an issue link (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: 15 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
post_id | string (uuid) | Yes |
provider | "github" | "linear" | "jira" | Yes |
kind | "issue" | "pull_request" | Yes |
url | string | Yes |
ref | string | Yes |
status | "open" | "closed" | "merged" | null | Yes |
status_synced_at | string (date-time) | null | Yes |
suggestion | object | null | Yes |
suggestion.status_id | string (uuid) | Yes |
suggestion.status_name | string | Yes |
suggestion.reason | "completed" | "not_planned" | "merged" | "reopened" | Yes |
suggestion.at | string (date-time) | Yes |
created_at | string (date-time) | Yes |
created_by | string (uuid) | 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/post-links/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/suggestion/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/post-links/{id}/suggestion/dismiss', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: {},
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/github-sync
GitHub status sync settings: whether the receiver is on, its webhook path, events, masked secret and last deliveries (any member)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 8 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
webhook_path | stringpattern ^\/webhooks\/github\/[0-9a-f-]{36}$ | Yes |
events | array of "issues" | "pull_request" | Yes |
content_type | "application/json" | Yes |
secret_hint | string | null | Yes |
created_at | string (date-time) | null | Yes |
last_delivery_at | string (date-time) | null | Yes |
last_ping_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/github-sync' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/github-sync');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/github-sync
Turn GitHub status sync on, or replace its webhook secret at once; the secret is shown in this response only (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 9 fields
| Name | Type | Required |
|---|---|---|
enabled | boolean | Yes |
webhook_path | stringpattern ^\/webhooks\/github\/[0-9a-f-]{36}$ | Yes |
events | array of "issues" | "pull_request" | Yes |
content_type | "application/json" | Yes |
secret_hint | string | null | Yes |
created_at | string (date-time) | null | Yes |
last_delivery_at | string (date-time) | null | Yes |
last_ping_at | string (date-time) | null | Yes |
secret | string | nullpattern ^uvgh_[0-9A-Za-z]{43}$ | 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/github-sync' \
-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/github-sync', { body: {} });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
DELETE/v1/github-sync
Turn GitHub status sync off: the receiver answers 404 and its secret is deleted (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 1 fields
| Name | Type | Required |
|---|---|---|
ok | true | 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/github-sync' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.delete('/v1/github-sync');No MCP tool for this operation. The MCP server covers the everyday reads and writes.