Qwenvo

Developer documentation

Authenticate with an API key from Settings → API keys. Keys have full access to every workspace you belong to. Rate limit: 600 requests per minute per key.

Authentication

curl https://qwenvo.com/api/v1/forms \
  -H "Authorization: Bearer fl-xxxxxxxx"

List forms

GET /api/v1/forms
→ { "forms": [{ "id", "name", "status", "publicId", "submissionCount", "updatedAt" }] }

Get a form

GET /api/v1/forms/{formId}
→ { "id", "name", "status", "url", "fields": [{ "key", "label", "type" }], "document": { blocks, logic, settings, theme } }

List submissions

GET /api/v1/forms/{formId}/submissions?filter=all|completed|partial&page=1&limit=50&after=2026-01-01T00:00:00Z
limit: 1-100 (default 50), newest first. "partial" = unfinished submissions (plans with partial submissions).
→ {
  "page", "limit", "hasMore", "total",
  "totalNumberOfSubmissionsPerFilter": { "all", "completed", "partial" },
  "questions": [{ "id", "type", "title", "isTitleModifiedByUser", "isDeleted" }],
  "submissions": [{ "id", "formId", "respondentId", "isCompleted", "submittedAt", "createdAt",
                   "responses": [{ "questionId", "answer" }],
                   "isComplete", "answers": { "<fieldKey>": value }, "hiddenFields", "calculations", "files" }]
}
questions[].id is the field key (hidden fields "hidden:<name>", calculated fields "calc:<name>", country "meta:respondentCountry").

GET /api/v1/forms/{formId}/submissions/{submissionId} → one submission (same fields) + "questions" + "meta"

Delete a submission

DELETE /api/v1/forms/{formId}/submissions/{submissionId}

Create a webhook

POST /api/v1/forms/{formId}/webhooks
{ "url": "https://example.com/hook", "events": ["submission.created"] }
→ { "id", "secret" }

Export submissions

GET /api/forms/{formId}/export?format=csv|json|xlsx&status=all|complete|partial&from=YYYY-MM-DD&to=YYYY-MM-DD&ids=a,b
CSV: UTF-8 with BOM, every field quoted; columns "Submission ID","Respondent ID","Submitted at" (UTC), then one per question.
GET /api/forms/{formId}/submissions/{submissionId}/pdf → application/pdf
GET /api/forms/{formId}/files → application/zip of every uploaded file

Current user

GET /api/v1/users/me
→ { "user": { "id", "email", "name", "avatarUrl", "emailVerified", "createdAt", "referredBy" }, "workspaces": [{ "id", "name", "slug", "plan", "planUntil", "role" }], "referrals": [{ "id", "name", "email" (masked), "signedUpAt", "status": "signed_up"|"paying", "plan", "discount", "rewardUsd" }], "referralLink" }

GET /api/v1/users/me/invites
→ { "invites": [{ "id", "role", "createdAt", "workspace": { "id", "name", "slug" }, "acceptUrl" }] }

Workspaces and trash

GET /api/v1/workspaces
→ { "workspaces": [{ "id", "name", "slug", "plan", "planUntil", "createdAt", "updatedAt", "role", "memberCount", "formCount", "folderCount", "domainCount" }] }

GET /api/v1/trash
→ { "forms": [{ "id", "name", "status", "publicId", "workspaceId", "workspaceName", "submissionCount", "deletedAt", "createdAt", "updatedAt" }], "workspaces": [{ "id", "name", "slug", "deletedAt", "createdAt", "formCount" }] }

Templates

GET /api/v1/templates
→ { "templates": [{ "slug", "name", "description", "category", "authorName", "useCount", "privacy", "mine" }] }   // gallery + your team's private templates

GET /api/v1/templates/{slug}
→ { "slug", "name", "description", "category", "authorName", "useCount", "schema", "previewPublicId" }   // preview, does not count a use

GET /api/v1/forms/{formId}/templates
→ { "templates": [{ "id", "slug", "name", "description", "category", "privacy", "authorName", "useCount", "featured", "previewUrl", "url", "createdAt" }] }

Import a document

GET /api/v1/import → { "documents": boolean, "image": boolean, "imageReason": string|null }
POST /api/v1/import   (multipart, field "file": .docx | .pdf | .png/.jpg/.gif/.webp, max 10 MB)
→ { "document", "summary", "provider", "extractedChars" }
Images need ANTHROPIC_API_KEY (501 otherwise). Shares the 30/hour AI rate limit.

Webhook payload & signature

POST <your url>
x-qwenvo-signature: t=1730000000,v1=<hex hmac>
x-qwenvo-event: submission.created
x-qwenvo-delivery: <eventId>

{
  "eventId": "<uuid, also the delivery id>",
  "eventType": "submission.created",
  "test": false,                       // true for "Send test event"
  "createdAt": "2026-10-24T19:28:30.602Z",
  "data": {
    "responseId", "submissionId", "respondentId", "formId", "formName", "createdAt",
    "fields": [{ "key", "label", "type", "value", "options"?: [{ "id", "text" }] }],
    "hiddenFields": { "<name>": "value" },
    "calculatedFields": { "<name>": value },
    "submissionPdfUrl", "submissionPreviewUrl"
  }
}
Every question of the form is listed (unanswered: value null); files and signatures are
[{ "id", "name", "url", "mimeType", "size" }]; choice questions add their options.
The test event uses the same builder with sample answers, so it has exactly the same keys.
submission.deleted sends data { "submissionId", "reason"?: "retention" }; form.published sends { "formId", "publicId", "version" }.

Verify: hmac_sha256(secret, t + "." + rawBody) === v1, and |now - t| <= 300 s.
Respond 2XX within 10 s. Retries: 5 min, 30 min, 1 h, 6 h, 1 day (6 attempts over ~31 h);
after the last one fails the form owner gets one email per failure streak.

Prefill a form

https://qwenvo.com/r/{publicId}?ref=abc&source=newsletter
Only hidden fields are read from the URL (case-sensitive). Map visible fields through "Default answer → hidden field".

Embed

<iframe src="https://qwenvo.com/embed/{publicId}" width="100%" height="600" frameborder="0"></iframe>
<!-- auto-resize --> <script src="https://qwenvo.com/embed.js" async></script>