Forms
Forms v2 submissions review, CSV export and per-form analytics.
8 endpoints· 8 called live· verified against http://127.0.0.1:8741
8 endpoints
GET/api/v1/salesshift/forms/{form_id}/analyticsCalled liveForm analytics: tiles, per-day series, top sources, enriched fields (W15).
`{range, tiles:{views, unique_views, starts, submissions, conversion_rate, new_contacts, enriched, spam, held, blocked}, held_pending, series:[{day, views, submissions}], sources:[{source, views, submissions, conversion}], enriched_fields:[{field, label, count}]}` over UTC days. Conversion = submissions ÷ unique visitors, clamped at 100%.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| range | string | optional | Default "all". One of "all" | "week" | "month" | "30d" | "custom". |
| from | string(date) | optional | custom only |
| to | string(date) | optional | custom only; ≤ 366 days |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/analytics' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 422 | computed at runtime: {message, issues:[{code: invalid_range}]} |
GET/api/v1/salesshift/forms/{form_id}/submissionsCalled liveOne form's submissions, newest first (W16 table).
Returns `{items:[SubmissionRow], total, page, page_size, counts}`; `counts` is per status for the whole form. Name and company are what the visitor typed, via the version's field mapping.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| status | string | optional | Default "accepted". One of "accepted" | "spam" | "held" | "blocked" | "all". |
| q | string | optional | Matches the email address or any answer (case-insensitive). |
| from | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| to | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| source | string | optional | "utm_source / utm_medium", the embedding site's host, or "(direct)". |
| page | integer | optional | Default 1. min 1. |
| page_size | integer | optional | Default 25. min 1. max 100. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 422 | computed at runtime: {code: invalid_status | invalid_date | invalid_range, message} |
GET/api/v1/salesshift/forms/{form_id}/submissions.csvCalled liveEvery submission matching the filters as CSV, streamed.
UTF-8 with a BOM, RFC 4180 quoting, 1,000 rows per batch. Fixed columns, then one column per answer key across every published version (newest label). Cells starting with = + - @ tab or CR are prefixed with '. IP address and user agent columns are included for admins only. Over 100,000 rows answers 413.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| status | string | optional | Default "accepted". One of "accepted" | "spam" | "held" | "blocked" | "all". |
| q | string | optional | Matches the email address or any answer (case-insensitive). |
| from | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| to | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| source | string | optional | "utm_source / utm_medium", the embedding site's host, or "(direct)". |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions.csv' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
A CSV file (Content-Disposition: attachment; {form-slug}-submissions-{date}.csv).
Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 422 | computed at runtime: {code: invalid_status | invalid_date | invalid_range, message} |
| 413 | computed at runtime: {code: export_too_large, message, total} |
GET/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}Called liveOne submission for the response drawer.
`{submission, answers:[{key,label,type,value,maps_to}], version_schema, contact, prev_id, next_id}`. Pass the table's filters so prev (newer) / next (older) follow it. IP and user agent are present for admins only.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required | |
| submission_id | string(uuid) | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| status | string | optional | Default "all". One of "accepted" | "spam" | "held" | "blocked" | "all". |
| q | string | optional | Matches the email address or any answer (case-insensitive). |
| from | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| to | string(date) | optional | YYYY-MM-DD, UTC day, inclusive. |
| source | string | optional | "utm_source / utm_medium", the embedding site's host, or "(direct)". |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 404 | computed at runtime: {code: not_found, message: 'Submission not found'} |
| 422 | computed at runtime: {code: invalid_status | invalid_date | invalid_range, message} |
DELETE/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}Called liveDelete a submission's answers (admin).
The contact stays. An SMS consent ledger row that points at the submission is kept with its evidence pointer cleared. Writes an audit line with ids only.
Admin only (require_admin); a member gets 403.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required | |
| submission_id | string(uuid) | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 403 | Only an organization admin can delete a submission. |
POST/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}/statusCalled liveMark a submission as spam, or restore it.
`{status: accepted|spam, run_workflows?:false}`. spam/held → accepted runs the submit pipeline on the stored answers (contact upsert, consent from the visitor's tick, enrichment, routing; workflows only when `run_workflows` is true, with `replayed: true` in the payload). → spam relabels only; the contact stays.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required | |
| submission_id | string(uuid) | required |
Request body (required)
Untyped body — these are the field names the handler reads.
statusrun_workflows
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions/{submission_id}/status' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "status": "<value>", "run_workflows": "<value>" }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 402 | computed at runtime: {code: contact_cap, message} |
| 409 | computed at runtime: {code: already_accepted | already_spam | blocked | invalid_transition, message} |
| 422 | computed at runtime: {message, issues:[{code: invalid_status}]} |
POST/api/v1/salesshift/forms/{form_id}/submissions/bulkCalled liveSpam, not spam, delete or create contacts for up to 200 submissions.
`{ids, action: spam|not_spam|delete|create_contacts, run_workflows?:false}` → `{done, skipped:[{id, reason}]}`. Each row runs in its own savepoint. `delete` is admin only.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required |
Request body (required)
Untyped body — these are the field names the handler reads.
idsactionrun_workflows
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions/bulk' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "ids": "<value>", "action": "<value>", "run_workflows": "<value>" }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 403 | Only an organization admin can delete submissions. |
| 422 | computed at runtime: {message, issues:[{code: invalid_action | invalid_ids}]} |
POST/api/v1/salesshift/forms/{form_id}/submissions/release-heldCalled liveCreate contacts for held submissions, oldest first (admin).
`{limit?:500, run_workflows?:false}` → `{released, still_held}`; 402 when nothing could be released because the workspace is still at its contact limit.
Admin only (require_admin); a member gets 403.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| form_id | string(uuid) | required |
Request body (required)
The body is optional.
limitrun_workflows
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/forms/{form_id}/submissions/release-held' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "limit": "<value>", "run_workflows": "<value>" }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Form not found |
| 402 | computed at runtime: {code: contact_cap, message, still_held} |
| 403 | Only an organization admin can release held submissions. |