Forms API.
Build your own front end for a Revvly form. Read the published form, upload files and submit answers from the browser with no key, or submit from your server with an API key. Every submission runs the form’s steps exactly as one from the hosted page would.
#Which to use
#Read a published form
The live version of the form: its questions in order, and the settings for the button, thank-you message and redirect. Edits in Revvly appear here only once they are published, so build from this, not from the builder. A form that is not published, archived or unknown is a 404.
{
"publicId": "Xk2mQ8vT1nR4pLw7Za",
"name": "Book a demo",
"description": null,
"version": 3,
"settings": {
"submitLabel": "Request a demo",
"successMessage": "Thanks, we will be in touch within a day.",
"redirectUrl": null
},
"fields": [
{
"id": "fld_1", "key": "email", "type": "EMAIL", "label": "Work email",
"description": null, "placeholder": "[email protected]", "required": true,
"options": [], "maxLength": null, "urlParam": null,
"maxFiles": null, "maxFileSizeMb": null, "accept": null
},
{
"id": "fld_2", "key": "team_size", "type": "DROPDOWN", "label": "Team size",
"description": null, "placeholder": null, "required": false,
"options": [{ "id": "o1", "label": "1-10" }, { "id": "o2", "label": "11-50" }],
"maxLength": null, "urlParam": null,
"maxFiles": null, "maxFileSizeMb": null, "accept": null
},
{
"id": "fld_3", "key": "utm_source", "type": "HIDDEN", "label": "Source",
"description": null, "placeholder": null, "required": false,
"options": [], "maxLength": null, "urlParam": "utm_source",
"maxFiles": null, "maxFileSizeMb": null, "accept": null
}
]
}#Answer formats
answers is an object keyed by each field’s key. Leave out questions the person did not answer. Unknown keys are ignored.
#Submit answers
POST https://api.revvly.ai/api/v1/forms/Xk2mQ8vT1nR4pLw7Za/submissions
Content-Type: application/json
{
"answers": {
"email": "[email protected]",
"team_size": "11-50",
"utm_source": "newsletter"
},
"website": "",
"pageUrl": "https://example.com/demo?utm_source=newsletter",
"referrer": "https://www.google.com/"
}Success201 Created
{
"submissionId": "6705f2c1a4b3e2d1c0f9e8d7",
"successMessage": "Thanks, we will be in touch within a day.",
"redirectUrl": null
}Replace the form with successMessage, or go to redirectUrl when it is set.
Invalid answers400 Bad Request
{
"message": "Some answers need attention.",
"fieldErrors": {
"email": "Enter a valid email address.",
"team_size": "Pick one of the listed options."
}
}#Upload a file
For FILE_UPLOAD questions. Ask for an upload address per file, upload the bytes to it, then submit the returned fileIds as the answer. The type is decided by the file name’s extension; a type or size the question does not accept is a 400 here. When the form is submitted, each file is checked for its real type and scanned before it is kept, and a file that fails comes back as that question’s fieldErrors entry. Files never submitted are deleted after a day.
POST https://api.revvly.ai/api/v1/forms/Xk2mQ8vT1nR4pLw7Za/uploads
Content-Type: application/json
{ "fieldKey": "cv", "filename": "ada-lovelace.pdf", "size": 482113 }201 Created
{
"fileId": "6705f2c1a4b3e2d1c0f9e8a1",
"uploadUrl": "https://…signed upload address…",
"contentType": "application/pdf",
"maxBytes": 31457280,
"expiresAt": "2026-10-09T10:15:00.000Z"
}
// Then, from your server:
PUT <uploadUrl>
Content-Type: application/pdf <- exactly the contentType returned
<the file's bytes>
// And submit the id as the answer:
"answers": { "cv": ["6705f2c1a4b3e2d1c0f9e8a1"] }#submitForm (GraphQL, from your server)
Submit to one of your workspace’s published forms with an API key sent as Authorization: Bearer <key>. The key needs the “Can submit forms” permission (Settings, API keys); a key with only write access is refused, and a forms key can do nothing else. Never put this key in browser code.
mutation Submit($input: SubmitFormInput!) {
submitForm(input: $input) {
submissionId
duplicate
successMessage
redirectUrl
}
}Variables{
"input": {
"publicId": "Xk2mQ8vT1nR4pLw7Za",
"answers": { "email": "[email protected]", "team_size": "11-50" },
"idempotencyKey": "contact-form-7f3a9c",
"pageUrl": "https://example.com/demo"
}
}// server.js. The key lives here and nowhere else.
const res = await fetch('https://api.revvly.ai/api/v1/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REVVLY_FORMS_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: SUBMIT_FORM, variables }),
});
const { data, errors } = await res.json();
const error = errors?.[0];
if (error?.extensions?.code === 'BAD_REQUEST') {
const fieldErrors = error.extensions.fieldErrors;
// Invalid answers carry { "<field key>": "<message>" }: show each under its question.
// A BAD_REQUEST without it is a problem with the call itself (both formId and
// publicId sent, an idempotencyKey over 128 characters): the message says which.
return fieldErrors ? showErrors(fieldErrors) : showProblem(error.message);
}