Submissions API
Read and delete your form submissions from your own code: API tokens, endpoints, pagination, filters and errors, with curl and JavaScript examples.
The Submissions API lets your own code list your forms and read or delete their submissions: to sync them into a CRM, back them up, or build your own inbox. It is a small JSON API over HTTPS, authenticated with a personal token.
Submitting forms needs no token at all; that is the submit endpoint. The API is for reading what has arrived.
Create a token#
- Sign in and open API in the dashboard header.
- Name the token after what will use it, for example "CRM sync", and choose Create token.
- Copy the token. It starts with
sf_and is shown only once: SilentForms stores just a hash of it.
A token can read and delete every submission of every form in your account, so keep it on a server or in a secret store. Never put it in a web page or a mobile app. Revoke a token in the dashboard as soon as you no longer need it; requests with it then get a 401.
Authentication#
Send the token in the Authorization header of every request:
curl https://silentforms.com/api/v1/forms \
-H "Authorization: Bearer sf_your_token"The API does not answer browsers on other sites (it sends no CORS headers): call it from your server.
List your forms#
GET /api/v1/forms
{
"data": [
{ "id": 12, "name": "Contact", "createdAt": "2026-09-01T10:00:00.000Z", "active": true, "submissions": 87 }
]
}active is false when the form is paused or suspended. submissions counts the inbox, not spam.
List a form's submissions#
GET /api/v1/forms/{formId}/submissions
Newest first. Query parameters, all optional:
| Parameter | Meaning |
|---|---|
limit | 1 to 100 submissions per page. Default 50. |
cursor | The nextCursor from the previous page. |
since | Only submissions received at or after this time, for example 2026-10-01T00:00:00Z. |
spam | true for the spam view (honeypot hits, kept 7 days) instead of the inbox. |
{
"data": [
{
"id": 1234,
"formId": 12,
"receivedAt": "2026-10-25T09:30:00.000Z",
"email": "[email protected]",
"data": { "name": "Jane", "email": "[email protected]", "message": "Hello!" },
"origin": "www.example.com",
"spam": false,
"read": false
}
],
"nextCursor": 1180
}data is what the visitor submitted, with special fields such as the access key removed. Checkbox groups are arrays. email is the visitor's address if the form had an email field. origin is the site the form was posted from. Visitors' IP addresses are not part of the API. When nextCursor is null there are no more pages.
Read or delete one submission#
GET /api/v1/submissions/{id} returns { "data": { … } } in the same shape.
DELETE /api/v1/submissions/{id} deletes it, along with any of its deliveries still waiting, and answers 204 with no body. There is no undo.
Errors#
Errors have a status code and a JSON body:
{ "error": { "message": "Form not found." } }| Status | Meaning |
|---|---|
| 400 | A query parameter is out of range or malformed. The message says which. |
| 401 | The token is missing, wrong or revoked. |
| 404 | The form or submission does not exist, or belongs to someone else. |
| 429 | More than 120 requests a minute with this token. Wait for the Retry-After header's number of seconds. |
Example: export every new submission#
This Node.js script fetches everything received since a date, page by page:
const BASE = 'https://silentforms.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.SILENTFORMS_TOKEN}` };
async function submissionsSince(formId, since) {
const all = [];
let cursor = null;
while (true) {
const query = new URLSearchParams({ since, limit: '100', ...(cursor ? { cursor: String(cursor) } : {}) });
const response = await fetch(`${BASE}/forms/${formId}/submissions?${query}`, { headers });
if (response.status === 429) {
await new Promise((resolve) => setTimeout(resolve, Number(response.headers.get('Retry-After')) * 1000));
continue;
}
if (!response.ok) throw new Error((await response.json()).error.message);
const page = await response.json();
all.push(...page.data);
cursor = page.nextCursor;
if (!cursor) return all;
}
}
const forms = (await (await fetch(`${BASE}/forms`, { headers })).json()).data;
for (const form of forms) {
const submissions = await submissionsSince(form.id, '2026-10-01T00:00:00Z');
console.log(`${form.name}: ${submissions.length} submissions`);
}Next steps#
- Get submissions pushed to you instead of polling: Webhooks.
- No code at all: Zapier, Make or n8n.
- Every field and response of the submit endpoint: the overview.