Submitting Feedback via API¶
Submit feedback programmatically from your server, CI pipeline, or any backend that can make HTTP requests.
When to Use This¶
- Your app has its own feedback UI and you want to forward submissions to Canviq
- You're migrating historical feedback from another tool
- You want to create submissions from automated monitoring (e.g., error thresholds trigger a problem report)
For user-facing feedback, consider the iframe embed first — it requires zero server code.
Prerequisites¶
- An org API key with the
submissions:writescope — see Authentication - Your
category_idvalues if you want to tag submissions (fetch them fromGET /api/organizations/{id}/categories)
Create a Submission¶
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Short title (5–200 chars) |
description | string | Yes | Full description (20–5000 chars) |
type | "idea" | "problem" | Yes | Feedback category |
category_id | UUID | No | Assign to a board category |
author_email | string | No | Email for attribution and notifications |
author_name | string | No | Display name for the author |
source | string | No | Tag by origin (e.g. "ios-app", "android", "web") |
Example¶
curl -X POST https://canviq.app/api/submissions \
-H "Authorization: Bearer $CANVIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Offline mode support",
"description": "When I lose connectivity, I want to be able to view my saved items and queue changes for sync.",
"type": "idea",
"category_id": "3f6a1b2c-...",
"author_email": "[email protected]",
"author_name": "Jane Smith",
"source": "ios-app"
}'
async function submitFeedback(params: {
title: string
description: string
type: 'idea' | 'problem'
authorEmail?: string
authorName?: string
categoryId?: string
source?: string
}) {
const response = await fetch('https://canviq.app/api/submissions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CANVIQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: params.title,
description: params.description,
type: params.type,
category_id: params.categoryId,
author_email: params.authorEmail,
author_name: params.authorName,
source: params.source,
}),
})
if (!response.ok) {
const err = await response.json()
throw new Error(`Canviq error ${response.status}: ${err.error}`)
}
return response.json() as Promise<{ id: string; url: string }>
}
import httpx, os
from typing import Optional
def submit_feedback(
title: str,
description: str,
type: str, # 'idea' or 'problem'
author_email: Optional[str] = None,
author_name: Optional[str] = None,
category_id: Optional[str] = None,
source: Optional[str] = None,
) -> dict:
resp = httpx.post(
'https://canviq.app/api/submissions',
headers={'Authorization': f'Bearer {os.environ["CANVIQ_API_KEY"]}'},
json={
'title': title,
'description': description,
'type': type,
'author_email': author_email,
'author_name': author_name,
'category_id': category_id,
'source': source,
},
)
resp.raise_for_status()
return resp.json()
Response¶
Bulk Import¶
For migrating large volumes of historical feedback, contact [email protected] for elevated rate limits and a bulk import endpoint.
The standard endpoint accepts up to 100 requests per minute per API key.
Rate Limits¶
See Rate Limits for the full table. On the standard plan:
- 100 submissions/min per API key
- Responses include
X-RateLimit-Remainingheaders
If you exceed the limit, you'll receive a 429 response. Back off exponentially and retry.
Error Handling¶
See Error Reference for the full list. Common cases:
| Error | Code | What to do |
|---|---|---|
| Missing required field | validation_error (400) | Check the field in the error message |
| Invalid API key | unauthorized (401) | Verify CANVIQ_API_KEY is set correctly |
| Wrong scope | forbidden (403) | Regenerate key with submissions:write |
| Rate limited | rate_limited (429) | Back off and retry; check headers |
| Plan limit reached | plan_limit_reached (402) | Upgrade or contact support |