Developers
REST API
API keys, every endpoint, pagination and errors.
The REST API reads and writes your forms' responses from your own code. Every request is JSON over HTTPS, and every response is JSON.
Base address: https://datamold.crawlink.com/api/v1
API keys
Create a key under Settings → API keys (Admin or Owner). Give it a name, the scopes it needs, optionally the forms it may touch, and optionally an expiry (30, 90 or 365 days).
- A key looks like
fk_…and is shown once. DataMold keeps only a fingerprint of it, so store it somewhere safe; a lost key is replaced, not recovered. - Revoke a key at any time; it stops working at once.
- Each key counts the requests it makes, so you can see which integration is doing what.
Send the key with every request, in either header:
Authorization: Bearer fk_YOUR_KEY
X-Api-Key: fk_YOUR_KEYScopes
| Scope | Allows |
|---|---|
forms.read | Listing forms and reading their fields; usage. |
data.read | Reading responses. |
data.write | Adding and changing responses. |
data.delete | Deleting responses. |
Endpoints
{id} is the form's id (from GET /api/v1/forms), not its public slug.
| Method | Path | Scope | Does |
|---|---|---|---|
GET | /api/v1/forms | forms.read | Your forms, with response counts. |
GET | /api/v1/forms/{id} | forms.read | One form and its answer fields. |
GET | /api/v1/forms/{id}/submissions | data.read | A page of responses. |
POST | /api/v1/forms/{id}/submissions | data.write | Add a response. |
GET | /api/v1/forms/{id}/submissions/{sid} | data.read | One response, with its files. |
PATCH | /api/v1/forms/{id}/submissions/{sid} | data.write | Change some answers. |
DELETE | /api/v1/forms/{id}/submissions/{sid} | data.delete | Delete a response. |
GET | /api/v1/usage | forms.read | This month’s usage against the plan. |
GET | /api/v1/health | — | Is the service up. |
curl "https://datamold.crawlink.com/api/v1/forms" \
-H "Authorization: Bearer fk_YOUR_KEY"Listing responses
Query parameters for GET /api/v1/forms/{id}/submissions:
| Parameter | Meaning |
|---|---|
page, pageSize | Page number and size (default 50, at most 200). |
q | Free-text search across the answers and the reference code. |
stage, tag | Only responses in this stage, or with this tag. |
from, to | Arrived between these times (epoch milliseconds). |
sortBy, sortDir | Sort by a field key; asc or desc (the default). |
where | A condition, field:operator:value. Repeat it for more; between takes low,high. |
match | all (default) or any of the conditions. |
Operators: eq, neq, contains, notContains, startsWith, endsWith, gt, gte, lt, lte, between, in, isEmpty, isNotEmpty.
curl -G "https://datamold.crawlink.com/api/v1/forms/FORM_ID/submissions" \
-H "Authorization: Bearer fk_YOUR_KEY" \
--data-urlencode "pageSize=100" \
--data-urlencode "where=city:eq:Pune" \
--data-urlencode "where=seats:gte:2"{
"form": { "id": "01J…", "name": "Event registration", "slug": "k7q2m9xa3b" },
"submissions": [
{
"id": "01J…",
"code": "K7Q2M",
"status": "complete",
"source": "web",
"data": { "full_name": "Asha Rao", "city": "Pune", "seats": 2 },
"createdAt": 1791965028499,
"updatedAt": 1791965028499
}
],
"page": 1,
"pageSize": 100,
"total": 248,
"nextPage": "https://…/api/v1/forms/01J…/submissions?page=2&pageSize=100"
}Follow nextPage until it is null to read every page. Each response returned
counts as one row view against your plan; reading a single response by its id does not.
Adding and changing responses
Send the answers as a JSON object of field keys and values — flat, or wrapped as {"data": {…}}. They are checked against the form's rules exactly as a responder's
would be. A new response answers 201 with the response; PATCH changes only
the keys you send.
curl -X POST "https://datamold.crawlink.com/api/v1/forms/FORM_ID/submissions" \
-H "Authorization: Bearer fk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"full_name": "Asha Rao", "city": "Pune", "seats": 2}'Submitting without a key
Each form also has a public endpoint for your own forms and front ends. It needs no key and takes the form's slug. It obeys your allowed domains, and one address can send at most 60 responses a minute.
fetch('https://datamold.crawlink.com/api/public/forms/YOUR_FORM_SLUG/submissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ full_name: 'Asha Rao', email: 'asha@example.com' })
});201answers{ "id", "code", "createdAt" }— the code is the reference to show the person.- JSON and ordinary form posts (
application/x-www-form-urlencoded) both work. GET /api/public/forms/YOUR_FORM_SLUGreturns the form's public definition, if you want to build the form from it.- Files: post each file as
multipart/form-data(fieldfile) to/api/public/uploads?form=SLUG&field=KEY, then send theidit returns as that field's answer. 25 MB per file at most; uploads need a paid plan.
Errors
A failed request answers with a status and a message a person can read:
{
"message": "Some answers need fixing.",
"errors": [{ "key": "email", "message": "…what is wrong with this answer…" }]
}| Status | Means |
|---|---|
401 | No key, an unknown key, or a key that was revoked or has expired. |
403 | The key lacks the scope, or is not allowed this form. |
402 | A plan limit is used up for the month (responses or row views). |
404 | No such form or response. |
409 | The form is not accepting responses, or a “unique” answer was already given. |
422 | Some answers are invalid; `errors` lists them by field key. |
429 | Too many submissions from one address (public endpoint only). |
Limits
The API has no request-rate limit of its own; it shares your plan's monthly meters. Listing and
exporting count row views, and adding responses counts responses. Check where you stand with GET /api/v1/usage. See how usage is counted.