Imports
Imports of posts, comments and votes from other tools.
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.
POST/v1/imports/uploads
Presign a CSV upload (≤ 10 MB) into the private exports bucket (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
| Name | Type | Required |
|---|---|---|
bytes | integervalue 1–10485760 | Yes |
Response 201: 8 fields
| Name | Type | Required |
|---|---|---|
asset_id | string (uuid) | Yes |
method | "PUT" | Yes |
upload_url | string (uri) | Yes |
expires_at | string (date-time) | Yes |
headers | object | Yes |
headers.content-type | "text/csv" | Yes |
headers.content-length | string | Yes |
headers.if-none-match | "*" | 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/imports/uploads' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"bytes": 1
}'const data = await upvoiced.post('/v1/imports/uploads', { body: { bytes: 1 } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/imports
Recent imports with progress (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 39 fields
| Name | Type | Required |
|---|---|---|
items | array of object | Yes |
items[].id | string (uuid) | Yes |
items[].source | "canny" | "featurebase" | "hearloop" | "generic" | Yes |
items[].entity | "posts" | "votes" | "comments" | Yes |
items[].status | "pending" | "running" | "completed" | "failed" | "canceled" | Yes |
items[].dry_run | boolean | Yes |
items[].board_id | string (uuid) | null | Yes |
items[].progress | object | Yes |
items[].progress.rows_total | integer | nullvalue ≥ 0 | Yes |
items[].progress.rows_processed | integervalue ≥ 0 | Yes |
items[].progress.rows_failed | integervalue ≥ 0 | Yes |
items[].progress.rows_warned | integervalue ≥ 0 | Yes |
items[].counts | object | Yes |
items[].counts.posts_created | integervalue ≥ 0 | Yes |
items[].counts.posts_updated | integervalue ≥ 0 | Yes |
items[].counts.posts_unchanged | integervalue ≥ 0 | Yes |
items[].counts.votes_created | integervalue ≥ 0 | Yes |
items[].counts.votes_existing | integervalue ≥ 0 | Yes |
items[].counts.comments_created | integervalue ≥ 0 | Yes |
items[].counts.comments_updated | integervalue ≥ 0 | Yes |
items[].counts.comments_unchanged | integervalue ≥ 0 | Yes |
items[].counts.end_users_created | integervalue ≥ 0 | Yes |
items[].counts.statuses_created | integervalue ≥ 0 | Yes |
items[].counts.tags_created | integervalue ≥ 0 | Yes |
items[].errors | array of objectitems ≤ 200 | Yes |
items[].errors[].row | integervalue ≥ 1 | Yes |
items[].errors[].column | string | nulllength ≤ 200 | Yes |
items[].errors[].code | "required" | "too_long" | "invalid_email" | "invalid_date" | "invalid_number" | "invalid_eta" | "unknown_board" | "board_archived" | "board_private_votes" | "post_not_found" | "parent_not_found" | "reply_depth" | "end_user_deleted" | "plan_limit" | "changed_since_import" | "duplicate_in_file" | "too_many_values" | "row_failed" | Yes |
items[].errors[].severity | "error" | "warning" | Yes |
items[].errors[].message | stringlength ≤ 300 | Yes |
items[].errors_truncated | boolean | Yes |
items[].error | object | null | Yes |
items[].error.code | "upload_missing" | "file_too_large" | "invalid_encoding" | "empty_file" | "too_many_columns" | "too_many_rows" | "field_too_long" | "record_too_long" | "unterminated_quote" | "missing_columns" | "delimiter_mismatch" | "duplicate_headers" | "board_unavailable" | "post_not_found" | "storage_unavailable" | "stalled" | "internal_error" | Yes |
items[].error.message | string | Yes |
items[].cancel_requested | boolean | Yes |
items[].created_at | string (date-time) | Yes |
items[].started_at | string (date-time) | null | Yes |
items[].finished_at | string (date-time) | null | 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/imports' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/imports');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/imports
Queue a CSV import or dry run (Canny/Featurebase/hearloop/generic mapping; owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
| Name | Type | Required |
|---|---|---|
asset_id | string (uuid) | Yes |
source | "canny" | "featurebase" | "hearloop" | "generic" | Yes |
entity | "posts" | "votes" | "comments"default "posts" | No |
mapping | objectdefault {"delimiter":",","columns":{},"status_map":{},"board_map":{},"body_format":"auto","follow":false} | No |
mapping.delimiter | "," | ";" | "\t" | "|"default "," | No |
mapping.columns | objectdefault {} | No |
mapping.columns.external_id | stringlength 1–200 | No |
mapping.columns.title | stringlength 1–200 | No |
mapping.columns.body | stringlength 1–200 | No |
mapping.columns.status | stringlength 1–200 | No |
mapping.columns.board | stringlength 1–200 | No |
mapping.columns.tags | stringlength 1–200 | No |
mapping.columns.category | stringlength 1–200 | No |
mapping.columns.author | stringlength 1–200 | No |
mapping.columns.author_email | stringlength 1–200 | No |
mapping.columns.author_name | stringlength 1–200 | No |
mapping.columns.created_at | stringlength 1–200 | No |
mapping.columns.url | stringlength 1–200 | No |
mapping.columns.vote_count | stringlength 1–200 | No |
mapping.columns.voter_emails | stringlength 1–200 | No |
mapping.columns.eta | stringlength 1–200 | No |
mapping.columns.post_ref | stringlength 1–200 | No |
mapping.columns.email | stringlength 1–200 | No |
mapping.columns.name | stringlength 1–200 | No |
mapping.columns.parent_ref | stringlength 1–200 | No |
mapping.status_map | objectdefault {} | No |
mapping.board_map | objectdefault {} | No |
mapping.post_id | string (uuid) | No |
mapping.body_format | "auto" | "markdown" | "html" | "text"default "auto" | No |
mapping.follow | booleandefault false | No |
board_id | string (uuid) | No |
dry_run | booleandefault true | No |
Response 202: 37 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
source | "canny" | "featurebase" | "hearloop" | "generic" | Yes |
entity | "posts" | "votes" | "comments" | Yes |
status | "pending" | "running" | "completed" | "failed" | "canceled" | Yes |
dry_run | boolean | Yes |
board_id | string (uuid) | null | Yes |
progress | object | Yes |
progress.rows_total | integer | nullvalue ≥ 0 | Yes |
progress.rows_processed | integervalue ≥ 0 | Yes |
progress.rows_failed | integervalue ≥ 0 | Yes |
progress.rows_warned | integervalue ≥ 0 | Yes |
counts | object | Yes |
counts.posts_created | integervalue ≥ 0 | Yes |
counts.posts_updated | integervalue ≥ 0 | Yes |
counts.posts_unchanged | integervalue ≥ 0 | Yes |
counts.votes_created | integervalue ≥ 0 | Yes |
counts.votes_existing | integervalue ≥ 0 | Yes |
counts.comments_created | integervalue ≥ 0 | Yes |
counts.comments_updated | integervalue ≥ 0 | Yes |
counts.comments_unchanged | integervalue ≥ 0 | Yes |
counts.end_users_created | integervalue ≥ 0 | Yes |
counts.statuses_created | integervalue ≥ 0 | Yes |
counts.tags_created | integervalue ≥ 0 | Yes |
errors | array of objectitems ≤ 200 | Yes |
errors[].row | integervalue ≥ 1 | Yes |
errors[].column | string | nulllength ≤ 200 | Yes |
errors[].code | "required" | "too_long" | "invalid_email" | "invalid_date" | "invalid_number" | "invalid_eta" | "unknown_board" | "board_archived" | "board_private_votes" | "post_not_found" | "parent_not_found" | "reply_depth" | "end_user_deleted" | "plan_limit" | "changed_since_import" | "duplicate_in_file" | "too_many_values" | "row_failed" | Yes |
errors[].severity | "error" | "warning" | Yes |
errors[].message | stringlength ≤ 300 | Yes |
errors_truncated | boolean | Yes |
error | object | null | Yes |
error.code | "upload_missing" | "file_too_large" | "invalid_encoding" | "empty_file" | "too_many_columns" | "too_many_rows" | "field_too_long" | "record_too_long" | "unterminated_quote" | "missing_columns" | "delimiter_mismatch" | "duplicate_headers" | "board_unavailable" | "post_not_found" | "storage_unavailable" | "stalled" | "internal_error" | Yes |
error.message | string | Yes |
cancel_requested | boolean | Yes |
created_at | string (date-time) | Yes |
started_at | string (date-time) | null | Yes |
finished_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/imports' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"asset_id": "0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d",
"source": "canny"
}'const data = await upvoiced.post('/v1/imports', {
body: {
asset_id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d',
source: 'canny',
},
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/imports/{id}
Import progress, counts and row errors (owner/admin)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 37 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
source | "canny" | "featurebase" | "hearloop" | "generic" | Yes |
entity | "posts" | "votes" | "comments" | Yes |
status | "pending" | "running" | "completed" | "failed" | "canceled" | Yes |
dry_run | boolean | Yes |
board_id | string (uuid) | null | Yes |
progress | object | Yes |
progress.rows_total | integer | nullvalue ≥ 0 | Yes |
progress.rows_processed | integervalue ≥ 0 | Yes |
progress.rows_failed | integervalue ≥ 0 | Yes |
progress.rows_warned | integervalue ≥ 0 | Yes |
counts | object | Yes |
counts.posts_created | integervalue ≥ 0 | Yes |
counts.posts_updated | integervalue ≥ 0 | Yes |
counts.posts_unchanged | integervalue ≥ 0 | Yes |
counts.votes_created | integervalue ≥ 0 | Yes |
counts.votes_existing | integervalue ≥ 0 | Yes |
counts.comments_created | integervalue ≥ 0 | Yes |
counts.comments_updated | integervalue ≥ 0 | Yes |
counts.comments_unchanged | integervalue ≥ 0 | Yes |
counts.end_users_created | integervalue ≥ 0 | Yes |
counts.statuses_created | integervalue ≥ 0 | Yes |
counts.tags_created | integervalue ≥ 0 | Yes |
errors | array of objectitems ≤ 200 | Yes |
errors[].row | integervalue ≥ 1 | Yes |
errors[].column | string | nulllength ≤ 200 | Yes |
errors[].code | "required" | "too_long" | "invalid_email" | "invalid_date" | "invalid_number" | "invalid_eta" | "unknown_board" | "board_archived" | "board_private_votes" | "post_not_found" | "parent_not_found" | "reply_depth" | "end_user_deleted" | "plan_limit" | "changed_since_import" | "duplicate_in_file" | "too_many_values" | "row_failed" | Yes |
errors[].severity | "error" | "warning" | Yes |
errors[].message | stringlength ≤ 300 | Yes |
errors_truncated | boolean | Yes |
error | object | null | Yes |
error.code | "upload_missing" | "file_too_large" | "invalid_encoding" | "empty_file" | "too_many_columns" | "too_many_rows" | "field_too_long" | "record_too_long" | "unterminated_quote" | "missing_columns" | "delimiter_mismatch" | "duplicate_headers" | "board_unavailable" | "post_not_found" | "storage_unavailable" | "stalled" | "internal_error" | Yes |
error.message | string | Yes |
cancel_requested | boolean | Yes |
created_at | string (date-time) | Yes |
started_at | string (date-time) | null | Yes |
finished_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/imports/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/imports/{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/imports/{id}/cancel
Cancel a queued import, or stop a running one after its current batch (owner/admin)
| 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: 37 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
source | "canny" | "featurebase" | "hearloop" | "generic" | Yes |
entity | "posts" | "votes" | "comments" | Yes |
status | "pending" | "running" | "completed" | "failed" | "canceled" | Yes |
dry_run | boolean | Yes |
board_id | string (uuid) | null | Yes |
progress | object | Yes |
progress.rows_total | integer | nullvalue ≥ 0 | Yes |
progress.rows_processed | integervalue ≥ 0 | Yes |
progress.rows_failed | integervalue ≥ 0 | Yes |
progress.rows_warned | integervalue ≥ 0 | Yes |
counts | object | Yes |
counts.posts_created | integervalue ≥ 0 | Yes |
counts.posts_updated | integervalue ≥ 0 | Yes |
counts.posts_unchanged | integervalue ≥ 0 | Yes |
counts.votes_created | integervalue ≥ 0 | Yes |
counts.votes_existing | integervalue ≥ 0 | Yes |
counts.comments_created | integervalue ≥ 0 | Yes |
counts.comments_updated | integervalue ≥ 0 | Yes |
counts.comments_unchanged | integervalue ≥ 0 | Yes |
counts.end_users_created | integervalue ≥ 0 | Yes |
counts.statuses_created | integervalue ≥ 0 | Yes |
counts.tags_created | integervalue ≥ 0 | Yes |
errors | array of objectitems ≤ 200 | Yes |
errors[].row | integervalue ≥ 1 | Yes |
errors[].column | string | nulllength ≤ 200 | Yes |
errors[].code | "required" | "too_long" | "invalid_email" | "invalid_date" | "invalid_number" | "invalid_eta" | "unknown_board" | "board_archived" | "board_private_votes" | "post_not_found" | "parent_not_found" | "reply_depth" | "end_user_deleted" | "plan_limit" | "changed_since_import" | "duplicate_in_file" | "too_many_values" | "row_failed" | Yes |
errors[].severity | "error" | "warning" | Yes |
errors[].message | stringlength ≤ 300 | Yes |
errors_truncated | boolean | Yes |
error | object | null | Yes |
error.code | "upload_missing" | "file_too_large" | "invalid_encoding" | "empty_file" | "too_many_columns" | "too_many_rows" | "field_too_long" | "record_too_long" | "unterminated_quote" | "missing_columns" | "delimiter_mismatch" | "duplicate_headers" | "board_unavailable" | "post_not_found" | "storage_unavailable" | "stalled" | "internal_error" | Yes |
error.message | string | Yes |
cancel_requested | boolean | Yes |
created_at | string (date-time) | Yes |
started_at | string (date-time) | null | Yes |
finished_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/imports/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/cancel' \
-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/imports/{id}/cancel', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: {},
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.