Feedback ingest API
Submit bug reports and feature requests from external apps into project feedback.
Feedback ingest API
The feedback ingest API lets external applications submit bug reports and feature requests into a project's feedback inbox. Project admins triage items in the dashboard and can convert them to tickets.
Enablement
Project admins open Settings → Feedback:
- Toggle Feedback enabled (gates sidebar entry and API access)
- Create and revoke ingest API tokens (plaintext shown once)
- Toggle Allow in-app submission for dashboard forms (the external ingest API still works when in-app submission is off)
In-app project submission and the platform self-report flow use the same service layer — browsers do not hold ingest tokens for those paths.
Agents on ArxDeck MCP must call get_project_info and confirm capabilities.feedback before assuming ingest is enabled.
Overview
The Feedback ingest API lets external applications submit bug reports and feature requests into a project. All endpoints require server-side calls — never expose your ingest token in client-side code or mobile apps.
Authentication
Send a project ingest token in the Authorization header:
Authorization: Bearer arx_fb_YOUR_TOKENCreate tokens in Settings → Feedback. Tokens use the arx_fb_ prefix and are shown in plaintext only once at creation.
Base URL
{your dashboard origin}/api/feedback/v1Replace {your dashboard origin} with the HTTPS origin of your ArxDeck dashboard (for example https://workspace.example.com).
Typical submit flow
- Presign —
POST /uploads/presignto get a presigned PUT URL andstorageKeyfor each image. - Upload — PUT image bytes directly to the presigned
uploadUrlwith the matchingContent-Typeheader (not an ingest API route). - Submit —
POST /submissionswith the feedback body andimageKeysfrom step 1.
External user identity
Every request identifies the submitter with an externalUserId string (your application's user ID). List, detail, comment, and close endpoints only return or modify items owned by that ID.
Error responses
Failed requests return JSON with an error message. Service errors may include a code field; rate limits include retryAfterSeconds.
{
"error": "Not found",
"code": "not_found"
}| Field | Type | Required | Description |
|---|---|---|---|
error | string | Yes | Human-readable error message. |
code | string | No | Machine-readable code (for example not_found, forbidden, invalid_image_keys, rate limit reasons). |
retryAfterSeconds | number | No | Present on 429 responses — seconds to wait before retrying. |
Common status codes: 401 missing or invalid token; 403 revoked token, feedback disabled, or forbidden; 404 not found; 422 invalid status transition; 429 rate limited; 503 image storage not configured for this deployment.
Rate limits
| Scope | Limit |
|---|---|
| Project | 100 requests per minute across all ingest routes. |
| Submissions | 10 accepted submissions per externalUserId per hour. |
| Comments | 60 accepted comments per externalUserId per hour. |
Endpoints
POST /uploads/presign
Request a presigned PUT URL for uploading one image. The returned storageKey must be included in a submission's imageKeys after the upload completes.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Your application's user identifier. |
filename | string | Yes | Original filename (1–255 characters). |
contentType | string | Yes | One of image/png, image/jpeg, image/webp. |
Response — 201 Created
| Field | Type | Description |
|---|---|---|
id | string | Pending upload record ID. |
storageKey | string | Key to pass in submission imageKeys after upload. |
uploadUrl | string | Presigned URL — PUT image bytes here. |
expiresAt | string | ISO 8601 expiry for the presigned URL (15 minutes). |
Example
curl -X POST "{your dashboard origin}/api/feedback/v1/uploads/presign" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user-123",
"filename": "screenshot.png",
"contentType": "image/png"
}'{
"id": "clx9up0000000000000000001",
"storageKey": "feedback/PROJECT_ID/a1b2c3d4-e5f6-7890-abcd-ef1234567890/screenshot.png",
"uploadUrl": "https://storage.example.com/feedback/...?X-Amz-Signature=...",
"expiresAt": "2026-07-06T01:45:00.000Z"
}Then upload the image (not an ingest API route):
curl -X PUT "<uploadUrl from response>" \
-H "Content-Type: image/png" \
--data-binary @screenshot.pngPOST /submissions
Create a new feedback item. Upload images via presigned URLs first and pass their storageKeys.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Your application's user identifier. |
title | string | Yes | Short summary (1–500 characters). |
type | string | No | bug or feature_request. Defaults to bug. |
body | string | No | Detailed description (max 50,000 characters). |
displayName | string | No | Submitter display name (max 200 characters). |
email | string | No | Submitter email (valid email or empty string). |
imageKeys | string[] | No | Storage keys from presign (max 5). Each must be uploaded, unconsumed, and not expired. |
contextUrl | string | No | Page URL where feedback was submitted (max 2,000 characters). |
contextUserAgent | string | No | Client user agent (max 2,000 characters). |
contextAppVersion | string | No | App version string (max 100 characters). |
Response — 201 Created
| Field | Type | Description |
|---|---|---|
id | string | Feedback item ID. |
type | string | bug or feature_request. |
title | string | Item title. |
status | string | Always new on create. |
createdAt | string | ISO 8601 creation timestamp. |
Example
curl -X POST "{your dashboard origin}/api/feedback/v1/submissions" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user-123",
"title": "Button does not work",
"type": "bug",
"body": "Steps to reproduce:\n1. Open settings\n2. Click Save",
"displayName": "Jane",
"email": "jane@example.com",
"imageKeys": ["feedback/PROJECT_ID/a1b2c3d4-e5f6-7890-abcd-ef1234567890/screenshot.png"],
"contextUrl": "https://myapp.com/settings",
"contextUserAgent": "MyApp/1.2.0",
"contextAppVersion": "1.2.0"
}'{
"id": "clx9sub00000000000000001",
"type": "bug",
"title": "Button does not work",
"status": "new",
"createdAt": "2026-07-06T01:30:00.000Z"
}GET /submissions?externalUserId=
List all feedback items submitted by an external user in this project.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Your application's user identifier. |
Response — 200 OK
| Field | Type | Description |
|---|---|---|
items | object[] | Array of summary objects, newest first. |
items[].id | string | Feedback item ID. |
items[].type | string | bug or feature_request. |
items[].title | string | Item title. |
items[].status | string | new, acknowledged, planned, in_progress, resolved, closed, wont_fix, or closed_by_submitter. |
items[].createdAt | string | ISO 8601 creation timestamp. |
items[].updatedAt | string | ISO 8601 last update timestamp. |
Example
curl "{your dashboard origin}/api/feedback/v1/submissions?externalUserId=user-123" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN"{
"items": [
{
"id": "clx9sub00000000000000001",
"type": "bug",
"title": "Button does not work",
"status": "acknowledged",
"createdAt": "2026-07-06T01:30:00.000Z",
"updatedAt": "2026-07-06T02:00:00.000Z"
}
]
}GET /submissions/:id?externalUserId=
Get full detail for one feedback item, including images (with presigned view URLs), comments, and linked ticket if converted. Only returns items owned by the given externalUserId.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Feedback item ID. |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Must match the item owner. |
Response — 200 OK
| Field | Type | Description |
|---|---|---|
item | object | Full feedback item. |
item.id | string | Feedback item ID. |
item.type | string | bug or feature_request. |
item.title | string | Item title. |
item.body | string | Detailed description. |
item.status | string | Current workflow status. |
item.externalUserId | string | Owner's external user ID. |
item.displayName | string | null | Submitter display name. |
item.email | string | null | Submitter email. |
item.contextUrl | string | null | Submitted-from URL. |
item.contextUserAgent | string | null | Client user agent. |
item.contextAppVersion | string | null | App version. |
item.convertedTicketId | string | null | Linked ticket ID if converted. |
item.createdAt | string | ISO 8601 creation timestamp. |
item.updatedAt | string | ISO 8601 last update timestamp. |
item.images | object[] | Attached images with presigned view URLs. |
item.images[].url | string | Presigned GET URL (5-minute TTL). |
item.comments | object[] | Comments in chronological order. |
item.comments[].authorType | string | submitter or triage. |
item.convertedTicket | object | null | { id, title } when converted to a ticket. |
Example
curl "{your dashboard origin}/api/feedback/v1/submissions/clx9sub00000000000000001?externalUserId=user-123" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN"POST /submissions/:id/comments
Add a comment to a feedback item as the submitter. Only the item owner can comment via the ingest API.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Feedback item ID. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Must match the item owner. |
body | string | Yes | Comment text (1–10,000 characters). |
Response — 201 Created
| Field | Type | Description |
|---|---|---|
id | string | Comment ID. |
body | string | Comment text. |
createdAt | string | ISO 8601 creation timestamp. |
Example
curl -X POST "{your dashboard origin}/api/feedback/v1/submissions/clx9sub00000000000000001/comments" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user-123",
"body": "I can reproduce this on iOS 18 as well."
}'PATCH /submissions/:id/status
Close a feedback item as the submitter. Only allowed when the current status is new or acknowledged. Sets status to closed_by_submitter.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Feedback item ID. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Must match the item owner. |
status | string | Yes | Must be closed_by_submitter. |
Response — 200 OK
| Field | Type | Description |
|---|---|---|
id | string | Feedback item ID. |
status | string | Updated status (closed_by_submitter). |
Example
curl -X PATCH "{your dashboard origin}/api/feedback/v1/submissions/clx9sub00000000000000001/status" \
-H "Authorization: Bearer arx_fb_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user-123",
"status": "closed_by_submitter"
}'Do not expose ingest tokens in client-side JavaScript or public repositories. Rotate tokens if leaked.
Related
- Integrations overview
- Tickets — converting feedback to tickets
