API Reference
Instaform API
Read-only access to your Instaform forms and submissions over plain HTTP JSON. Five endpoints, one bearer token, no SDK required.
Overview
Base URL
https://api.instaform.co
Every path below is relative to this origin, so listing forms is a GET to https://api.instaform.co/public/v1/forms.
Availability
API access is included on the Pro and Enterprise plans only. On any other plan every endpoint returns 403 with code feature_unavailable.
v1 is read-only
Every endpoint is a GET. There is no way to create, update or delete anything through this API. Any other verb — POST, PUT, PATCH, DELETE — returns 405 with code read_only_api, on every path, whether or not you sent a valid key.
Authentication
Create a key under Settings → API keys, then send it as a bearer token on every request.
The full key is shown exactly once, at creation. Instaform stores only a hash of it, so a lost key cannot be recovered — create a new one and revoke the old.
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/forms"One generic 401, on purpose
A missing key, a malformed header, an unknown key, a revoked key and an expired key all return the same 401 body. This is deliberate: telling the two apart would confirm to whoever holds a leaked key that it was genuine and merely stale.
{
"error": "Invalid API key.",
"code": "invalid_api_key"
}Rate limits
Up to 1000 requests per clock-hour per key. The window is fixed (aligned to the top of the clock hour), not a rolling/sliding window, so a burst that spans the hour boundary may briefly exceed 1000 requests within any given 60-minute span (up to ~2000 in the worst case — ~1000 late in one hour plus ~1000 early in the next).
Exceeding the per-window limit returns 429 with a Retry-After header (seconds until the current window resets).
The limit is counted per key, not per account and not per IP — issuing a second key for a second integration gives that integration its own budget.
{
"error": "Rate limit exceeded. Please try again later.",
"retry_after": 1284
}Pagination
List endpoints take page and per_page query parameters. per_page defaults to 20 and is capped at 100 — a larger value is clamped rather than rejected. Every list response carries a meta object alongside data.
"meta": {
"current_page": 1,
"total_pages": 7,
"total_count": 128,
"per_page": 20
}total_count counts the readable set only — held submissions are excluded from it. See held submissions below.
Endpoints
/public/v1/formsList forms
Returns forms belonging to the authenticated account, most recently created first. submissions_count excludes held submissions.
Parameters
pagequery — Page number. Defaults to 1.per_pagequery — Items per page. Defaults to 20, capped at 100.
Request
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/forms?page=1&per_page=20"Response 200
{
"data": [
{
"id": 12,
"hash_id": "a1b2c3d4e5",
"title": "Contact us",
"description": "Website contact form",
"status": "published",
"submissions_count": 128,
"created_at": "2026-01-14T09:12:03Z",
"updated_at": "2026-02-02T17:40:55Z"
}
],
"meta": {
"current_page": 1,
"total_pages": 3,
"total_count": 47,
"per_page": 20
}
}/public/v1/forms/{id}Get one form, including its field schema
A form id that does not belong to the authenticated account returns 404, never 403 — the distinction would confirm the record exists on someone else’s account.
Parameters
idpath — The form id. Required.
Request
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/forms/12"Response 200
{
"data": {
"id": 12,
"hash_id": "a1b2c3d4e5",
"title": "Contact us",
"description": "Website contact form",
"status": "published",
"submissions_count": 128,
"created_at": "2026-01-14T09:12:03Z",
"updated_at": "2026-02-02T17:40:55Z",
"fields": [
{
"id": "field_0",
"type": "email",
"label": "Email",
"required": true
}
]
}
}/public/v1/forms/{form_id}/submissionsList submissions for one form
Held submissions (over your plan’s monthly limit) are omitted from both the results and meta.total_count. A form_id that does not belong to the authenticated account returns 404, never 403.
Parameters
form_idpath — The form id. Required.pagequery — Page number. Defaults to 1.per_pagequery — Items per page. Defaults to 20, capped at 100.
Request
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/forms/12/submissions?page=1&per_page=50"Response 200
{
"data": [
{
"id": 981,
"hash_id": "f7e6d5c4b3",
"form_id": 12,
"form_title": "Contact us",
"data": {
"field_0": "[email protected]",
"field_1": "Do you take weekend bookings?"
},
"created_at": "2026-02-02T17:40:55Z",
"updated_at": "2026-02-02T17:40:55Z"
}
],
"meta": {
"current_page": 1,
"total_pages": 7,
"total_count": 128,
"per_page": 50
}
}/public/v1/submissionsList submissions across all your forms
Held submissions are omitted from both the results and meta.total_count. Optionally scope to one form with form_id.
Parameters
form_idquery — Restrict to submissions for this form id.pagequery — Page number. Defaults to 1.per_pagequery — Items per page. Defaults to 20, capped at 100.
Request
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/submissions?form_id=12"Response 200
{
"data": [
{
"id": 981,
"hash_id": "f7e6d5c4b3",
"form_id": 12,
"form_title": "Contact us",
"data": {
"field_0": "[email protected]",
"field_1": "Do you take weekend bookings?"
},
"created_at": "2026-02-02T17:40:55Z",
"updated_at": "2026-02-02T17:40:55Z"
}
],
"meta": {
"current_page": 1,
"total_pages": 7,
"total_count": 128,
"per_page": 20
}
}/public/v1/submissions/{id}Get one submission
A held submission, or a submission id that does not belong to the authenticated account, both return a plain 404 — the API does not distinguish "does not exist" from "held over your plan limit".
Parameters
idpath — The submission id. Required.
Request
curl -H "Authorization: Bearer inst_live_xxxxxxxx" \
"https://api.instaform.co/public/v1/submissions/981"Response 200
{
"data": {
"id": 981,
"hash_id": "f7e6d5c4b3",
"form_id": 12,
"form_title": "Contact us",
"data": {
"field_0": "[email protected]",
"field_1": "Do you take weekend bookings?"
},
"created_at": "2026-02-02T17:40:55Z",
"updated_at": "2026-02-02T17:40:55Z"
}
}Held submissions
Submissions held over your plan’s monthly limit are omitted entirely from this API. They are absent from list results, excluded from total_count and submissions_count, and fetching one directly by id returns 404. Upgrade to unlock them.
The 404 is a plain one: the API does not distinguish “does not exist” from “held over your plan limit”. That is what stops the counts from leaking how many rows are being withheld, so do not treat a 404 as proof that a submission was never received.
Because the exclusion happens in the query rather than at serialization, pagination and totals are computed over the readable set — page counts stay consistent with what you can actually fetch.
Error responses
Errors are JSON with an error message and, except on 429, a stable code to branch on.
| Status | code | When |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, unknown, revoked, or expired API key. All five return this identical body. |
| 403 | feature_unavailable | The account’s current plan does not include API access. The body also carries feature: "api_access" and current_plan. |
| 404 | not_found | The record does not exist, belongs to another account, or — for a submission — is held over your plan’s monthly limit. |
| 405 | read_only_api | Any non-GET method on this read-only API. |
| 429 | — | Rate limit exceeded for the current clock-hour window. The body carries retry_after and the response carries a Retry-After header. |