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

GET/public/v1/forms

List forms

Returns forms belonging to the authenticated account, most recently created first. submissions_count excludes held submissions.

Parameters

  • page query — Page number. Defaults to 1.
  • per_page query — 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
  }
}
GET/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

  • id path — 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
      }
    ]
  }
}
GET/public/v1/forms/{form_id}/submissions

List 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_id path — The form id. Required.
  • page query — Page number. Defaults to 1.
  • per_page query — 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
  }
}
GET/public/v1/submissions

List 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_id query — Restrict to submissions for this form id.
  • page query — Page number. Defaults to 1.
  • per_page query — 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
  }
}
GET/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

  • id path — 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.

StatuscodeWhen
401invalid_api_keyMissing, malformed, unknown, revoked, or expired API key. All five return this identical body.
403feature_unavailableThe account’s current plan does not include API access. The body also carries feature: "api_access" and current_plan.
404not_foundThe record does not exist, belongs to another account, or — for a submission — is held over your plan’s monthly limit.
405read_only_apiAny non-GET method on this read-only API.
429Rate limit exceeded for the current clock-hour window. The body carries retry_after and the response carries a Retry-After header.