Standard call shape (every curl below). The <AUTH> placeholder is shorthand for Authorization: Bearer <TOKEN> only. Body-bearing requests also need Content-Type: application/json. Send wix-site-id: <SITE_ID> when the token is account-scoped.
These calls need the Wix Forms app on the site (appDefId 225dd912-7dea-4738-8688-4b8c6955ffc2); a 428, a 400 naming the app, or an empty result on a site that should have content means it is not there. List Installed Apps shows what the site has; Install Wix Apps adds it by that id.
List a site's forms, find one, and read the submissions sent to it. The usual order is find the form first, then query its submissions by formId. Every call here is a read — nothing on the site changes, so no confirmation is needed.
Every form and every submission belongs to one namespace: the app that owns the form. Every read except Get Form and Get Submission needs it.
| App that owns the form | Namespace |
|---|---|
| Wix Forms (the forms on the site's Forms dashboard) | wix.form_app.form |
| Wix Bookings | wix.bookings.v2.bookings |
| Wix Pricing Plans | wix.pricing_plans.v1.custom_form |
| Wix eCom checkout | wix.ecom.v1.checkout |
| Wix Quizzes | wix.achievements.quizzes.v1.quiz |
| Wix Contacts | wix.contacts.v4.contact |
The source is Namespaces of apps made by Wix.
wix.form_app.form.namespace field.Permissions for given namespace not found: <namespace> → that app is not installed on the site. Stop. Check the site's apps with List Installed Apps and tell the user the app is missing. Do not try other namespaces (wix.old_form_app.form, wix.forms, …): none of them exist, and each one only costs another failed call."forms": [] for a namespace → it means no forms there, or the wrong namespace. It does not error on a namespace the site lacks.| Task | Request | Where the namespace goes |
|---|---|---|
| List forms | GET https://www.wixapis.com/form-schema-service/v4/forms?namespace=wix.form_app.form | query parameter namespace (required) |
| Query forms (by name, date) | POST https://www.wixapis.com/form-schema-service/v4/forms/query | inside the filter: "namespace": {"$eq": "wix.form_app.form"} |
| Get one form | GET https://www.wixapis.com/form-schema-service/v4/forms/{formId} | not needed |
| Query submissions | POST https://www.wixapis.com/form-submission-service/v4/submissions/namespace/query | inside the filter: "namespace": "wix.form_app.form" |
| Get one submission | GET https://www.wixapis.com/form-submission-service/v4/submissions/{submissionId} | not needed |
| Count submissions per form | POST https://www.wixapis.com/form-submission-service/v4/submissions/count | body field namespace |
⚠️ These look-alike URLs do not exist and return a bare 404:
/forms/v4/forms, /forms/v4/forms/query, /forms/v4/forms/{formId} — forms live under /form-schema-service/v4/./form-schema-service/v4/submissions/... and /form-submission-service/v4/forms — submissions live under /form-submission-service/v4/submissions/, forms don't.Use .../submissions/namespace/query for every submissions query. It is the only submissions query the docs list. Don't drop namespace from the path.
https://www.wixapis.com/forms/v4/submissions/... (the publicUrl the API spec gives for the submission methods) is the same API under another path, and works too. The /forms/v4/ alias exists for submissions only: /forms/v4/forms still 404s.
enabled defaults to true, and no value returns both. One call hides every disabled form. With List Forms, make both calls and report both lists, marking the disabled ones. Or use Query Forms with only the namespace filter (below): it returns enabled and disabled forms in one call.fieldsets=METADATA returns only the top-level properties (id, name, enabled, dates, namespace) and leaves out fields, steps and rules. Use it whenever you only need names and IDs. Omit it when you need the fields (to read submission answers, see STEP 5).forms[] and pagingMetadata. When pagingMetadata.hasNext is true, call again with paging.cursor=<pagingMetadata.cursors.next>.formIds=<id> (repeat the parameter for each ID).id, namespace ($eq only), name ($eq, $ne, $in, $nin, $startsWith), enabled ($eq, $ne), createdDate and updatedDate ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin). Date operators are $gte / $lte. $ge / $le are rejected. A range needs one condition per operator under $and, exactly as for submissions in STEP 4.enabled.forms[] and metadata (not pagingMetadata). For the next page, see Paging.Returns form, with formFields (each input field's inputOptions.target is the key its answers are stored under) and namespace.
namespace goes inside query.filter. A top-level "namespace" beside query doesn't count: the call fails with namespace filter is required.formId to read submissions across every form in the namespace.submissions[] and metadata (count, hasNext, cursors.next). Each submission has id, formId, status, seen, contactId (when linked to a contact), submitter, createdDate, and submissions (the answers, see STEP 5).When you already hold a submission ID, read it directly. No namespace is needed:
Returns submission, with the same fields as a query result, including formId (the form it belongs to) and the answers in submissions.
createdDateThe submission time is createdDate. Filter and sort on it. These fields do not exist, and each one fails with Filter contains unsupported condition or Sort contains unsupported condition: submittedDate, submittedAt, submissionTime, submissionDate, dateCreated, _createdDate.
$andA field takes one operator. Two operators in one object, such as "createdDate": {"$gte": …, "$lt": …}, fail with Field '$lt' is not supported for filter with operator '$eq'. Put each bound in its own condition under $and:
metadata.count. There is no total in the response, and Count Submissions takes no date filter.Returns formsSubmissionsCount[], with formId, totalCount and unseenCount for each form.
| Field | Filter operators | Sortable |
|---|---|---|
namespace | $eq — required in every filter | no |
id, formId, contactId, status | $eq, $ne, $in, $nin | yes |
seen | $eq, $ne | yes |
createdDate, updatedDate | $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin | yes |
submitter.memberId, submitter.visitorId, submitter.applicationId, submitter.userId | $eq, $ne, $in, $nin | yes |
orderDetails.checkoutId | $eq, $ne, $in, $nin | yes |
Any other field is rejected. The common wrong ones:
| You want | Use | Not |
|---|---|---|
| Submissions from one contact | "contactId": "<contactId>" (top level) | submitter.contactId, submitterContactId |
| Submissions to one form | "formId": "<formId>" | submission.formId |
| Unread submissions | "seen": false | — |
| Recorded submissions only | "status": "CONFIRMED" | — |
| Submissions with a given answer (an email typed in, a choice picked) | Not filterable. Filter by formId, then match the answer in code (STEP 5). | submissions.email, submissions.<target>, contact.email |
To find a person's submissions by their email, look up the contact first, then filter submissions by "contactId": "<contacts[0].id>":
No contact found means no submissions linked to that email. See Query Contacts.
Each page holds at most 100 submissions or forms. When metadata.hasNext is true, send the same URL with only the cursor. The cursor carries the filter and sort, including the namespace:
A submission's answers are in its submissions object, keyed by each field's target:
The targets are not labels. To show the user readable answers, read the form once (Get Form) and match each formFields[].inputOptions.target to the label inside that field's inputOptions, for example stringOptions.textInputOptions.label.
Return only what the user asked for. A submission page can be large. Before you answer or pass results on, keep only the fields you need, such as id, createdDate, contactId and the few answers that matter. Use fieldsets=METADATA on List Forms for the same reason.
To give the user a link to a form's submissions table, see Forms Dashboard Navigation.
| Error / symptom | Cause | Fix |
|---|---|---|
namespace must not be empty / namespace has size 0, expected 10 or more on List Forms | The namespace query parameter is missing | Add ?namespace=wix.form_app.form |
namespace filter is required on a submissions query | namespace is missing from query.filter, or sent at the top level | Put "namespace": "wix.form_app.form" inside query.filter |
Permissions for given namespace not found: … | The site doesn't have that app | Stop. Check installed apps and tell the user. Don't try other namespaces |
A bare 404 with no message | An invented URL such as /forms/v4/forms or /form-schema-service/v4/submissions/... | Use the URLs in STEP 2 exactly |
Sort contains unsupported condition / Filter contains unsupported condition | A field outside the closed list, such as submittedDate, _createdDate, submitter.contactId or an answer key | Use createdDate, contactId, or the table in STEP 4 |
Field '$lt' is not supported for filter with operator '$eq' | Two operators in one field object | One condition per operator under $and |
Filter contains unsupported operator on Query Forms | Two operators in one field object | One condition per operator under $and |
Field 'createdDate' is using non allowed operator '$ge' | $ge / $le | Use $gte / $lte |
| The user says a form is missing from the list | List Forms defaults to enabled=true | Call again with enabled=false |
"forms": [] | Wrong namespace, no forms there, or (on List Forms) every form is disabled | Check the namespace (STEP 1) and call again with enabled=false before reporting "no forms" |
$and, $or, pagingLast updated: 15 October 2026