On this page

REST API v1

GRTforms API

Programmatic access to your forms, submissions, and webhooks. This page is server-rendered documentation for the live /api/v1 surface. An interactive try-it playground is not part of v1.

Prefer a machine-readable spec? Download the OpenAPI document.

Overview

Authenticate with an API key, call HTTPS endpoints under /api/v1, and receive JSON. Form ids and entry ids are cuids. List endpoints use cursor pagination.

Base URL
https://grtforms.com
Versioning
Current version is v1, expressed in the path as /api/v1. There are no other versions.
Authentication
Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Rate limits
100 requests per minute per API key. See Rate limits.

Authentication

The v1 API uses API keys only. Session cookies from the dashboard are not accepted on /api/v1.

  1. Sign in and open Account → API Keys at /account/api-keys.
  2. Create a key, choose scopes, and copy it immediately. The full secret is shown once.
  3. Send it on every request as a Bearer token. Keys look like grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
http
Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API key scopes
ScopeDescription
forms:readRead form metadata and schema
forms:writeCreate unpublished draft forms via the API or MCP
entries:readRead submissions (sensitive fields redacted)
entries:writeCreate submissions via the API
webhooks:writeCreate, list, and delete webhooks
entries:read:sensitiveRead decrypted values of sensitive fields

Revoked or expired keys return 401. A valid key missing a required scope returns 403.

Errors

Errors use a consistent JSON body. Optional details may include validation issues or the missing scope.

json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  }
}
json
{
  "error": {
    "code": "forbidden",
    "message": "Missing required scope: forms:read",
    "details": {
      "requiredScope": "forms:read",
      "providedScopes": ["entries:read"]
    }
  }
}
HTTP error statuses
StatusCodeWhen
400validation_errorThe request body or query parameters failed validation.
401unauthorizedMissing, malformed, revoked, expired, or unknown API key.
403forbiddenThe API key is valid but missing a required scope.
404not_foundThe form, entry, webhook, or user was not found.
429rate_limit_exceededToo many requests in the current minute window.
500internal_errorAn unexpected server error occurred.

Rate limits

Authenticated v1 requests are limited to 100 requests per minute per API key. Successful responses include remaining-quota headers. A 429 also includes Retry-After.

  • X-RateLimit-Remaining — requests left in the current window
  • X-RateLimit-Reset — Unix timestamp when the window resets
  • Retry-After — seconds to wait (429 only)
json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded"
  }
}

Examples

Worked curls against production. Replace the placeholder key and ids with values from your account. Submissions are called entries in the API.

List forms

Requires forms:read.

bash
curl "https://grtforms.com/api/v1/forms?limit=20" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{
  "data": [
    {
      "id": "cm2form8k0001abcdefghijk",
      "name": "Event registration",
      "description": "RSVP for the October meetup",
      "slug": "event-registration-y2gt02",
      "isPublished": true,
      "submissionCount": 128,
      "createdAt": "2026-09-12T09:14:00.000Z",
      "updatedAt": "2026-10-01T16:22:00.000Z"
    }
  ],
  "pagination": {
    "hasMore": false,
    "nextCursor": null
  }
}

Get a form

Includes schema and settings so you can map field ids before reading or creating entries.

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Create a form

Requires forms:write. The form is created as an unpublished draft.

bash
curl -X POST "https://grtforms.com/api/v1/forms" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name":"Event registration","description":"RSVP for the October meetup"}'
json
{
  "data": {
    "id": "cm2form8k0001abcdefghijk",
    "name": "Event registration",
    "description": "RSVP for the October meetup",
    "slug": "event-registration-y2gt02",
    "schema": [
      {
        "id": "full_name",
        "type": "short_text",
        "label": "Full name",
        "validation": { "required": true }
      }
    ],
    "settings": {
      "submitButtonText": "Submit",
      "successMessage": "Thank you for your submission!"
    },
    "isPublished": false,
    "submissionCount": 0,
    "createdAt": "2026-10-09T12:00:00.000Z",
    "updatedAt": "2026-10-09T12:00:00.000Z"
  }
}

List submissions

GET /api/v1/forms/{formId}/entries requires entries:read.

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries?limit=20&sort=desc&field=email&[email protected]" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Get a submission

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries/cm2entry9m0002abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Webhooks

Create an HTTPS endpoint, then register it. Deliveries include X-GRTforms-Signature, X-GRTforms-Event, and X-GRTforms-Delivery. Payload version is 2026-10.

bash
curl -X POST "https://grtforms.com/api/v1/webhooks" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "formId": "cm2form8k0001abcdefghijk",
    "url": "https://example.com/hooks/grtforms",
    "events": ["entry.created"]
  }'
json
{
  "version": "2026-10",
  "event": "entry.created",
  "timestamp": "2026-10-02T14:03:11.000Z",
  "data": {
    "form": {
      "id": "cm2form8k0001abcdefghijk",
      "name": "Event registration",
      "slug": "event-registration-y2gt02"
    },
    "entry": {
      "id": "cm2entry9m0002abcdefghijk",
      "data": {
        "full_name": "Ada Lovelace",
        "email": "[email protected]"
      },
      "metadata": { "source": "api" },
      "createdAt": "2026-10-02T14:03:11.000Z",
      "updatedAt": "2026-10-02T14:03:11.000Z"
    }
  }
}
javascript
const crypto = require("crypto");

function hmacKey(secret) {
  if (String(secret).startsWith("whsec_")) {
    return crypto.createHash("sha256").update(secret).digest("hex");
  }
  return secret;
}

function verifySignature(payload, signature, secret) {
  const key = hmacKey(secret);
  const [tPart, vPart] = signature.split(",");
  const timestamp = tPart.substring(2);
  const providedSig = vPart.substring(3);

  const expectedSig = crypto
    .createHmac("sha256", key)
    .update(`${timestamp}.${payload}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(providedSig, "hex"),
    Buffer.from(expectedSig, "hex")
  );
}

REST reference

Every live public or authenticated /api/v1 route. Dashboard-only routes (session login, builder, Excel export) are not part of this API.

GET

/api/v1/me

Get current user

Returns the authenticated account and the API key used for the request. Any valid key works; no extra scope is required.

Auth
Bearer API key
Scope
Any valid key

Request

bash
curl "https://grtforms.com/api/v1/me" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": {
    "user": {
      "id": "cm2user7q0000abcdefghijk",
      "email": "[email protected]",
      "name": "Ada Lovelace",
      "createdAt": "2026-09-01T12:00:00.000Z"
    },
    "apiKey": {
      "id": "cm2key1r0004abcdefghijk",
      "name": "CI pipeline",
      "scopes": ["forms:read", "entries:read"],
      "expiresAt": null,
      "usageCount": 42
    }
  }
}
  • 200 User and API key info
  • 401 Unauthorized
GET

/api/v1/forms

List forms

Returns a cursor-paginated list of forms owned by the authenticated user, newest first.

Auth
Bearer API key
Scope
forms:read
Parameters
NameInRequiredDescription
limitqueryNoPage size. Default 50, maximum 100.
cursorqueryNoForm id from pagination.nextCursor to fetch the next page.

Request

bash
curl "https://grtforms.com/api/v1/forms?limit=20" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": [
    {
      "id": "cm2form8k0001abcdefghijk",
      "name": "Event registration",
      "description": "RSVP for the October meetup",
      "slug": "event-registration-y2gt02",
      "isPublished": true,
      "submissionCount": 128,
      "createdAt": "2026-09-12T09:14:00.000Z",
      "updatedAt": "2026-10-01T16:22:00.000Z"
    }
  ],
  "pagination": {
    "hasMore": false,
    "nextCursor": null
  }
}
  • 200 List of forms
  • 401 Unauthorized
  • 403 Missing forms:read
POST

/api/v1/forms

Create a form

Creates an unpublished draft form. Requires forms:write. The schema is validated with the same form schema validators as the builder. Plan form-count limits are enforced. The form is never published by this endpoint.

Auth
Bearer API key
Scope
forms:write

Request

bash
curl -X POST "https://grtforms.com/api/v1/forms" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name":"Event registration","description":"RSVP for the October meetup"}'
json
{
  "name": "Event registration",
  "description": "RSVP for the October meetup",
  "schema": [
    {
      "id": "full_name",
      "type": "short_text",
      "label": "Full name",
      "validation": { "required": true }
    }
  ]
}

Response

json
{
  "data": {
    "id": "cm2form8k0001abcdefghijk",
    "name": "Event registration",
    "description": "RSVP for the October meetup",
    "slug": "event-registration-y2gt02",
    "schema": [
      {
        "id": "full_name",
        "type": "short_text",
        "label": "Full name",
        "validation": { "required": true }
      }
    ],
    "settings": {
      "submitButtonText": "Submit",
      "successMessage": "Thank you for your submission!"
    },
    "isPublished": false,
    "submissionCount": 0,
    "createdAt": "2026-10-09T12:00:00.000Z",
    "updatedAt": "2026-10-09T12:00:00.000Z"
  }
}
  • 201 Draft form created
  • 400 Validation error
  • 401 Unauthorized
  • 403 Missing forms:write or plan form limit
GET

/api/v1/forms/{formId}

Get form

Returns one form, including its field schema and settings. Settings omit secrets such as the form password hash. The public fill URL is /f/{slug}.

Auth
Bearer API key
Scope
forms:read
Parameters
NameInRequiredDescription
formIdpathYesForm id (cuid).

Request

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": {
    "id": "cm2form8k0001abcdefghijk",
    "name": "Event registration",
    "description": "RSVP for the October meetup",
    "slug": "event-registration-y2gt02",
    "schema": [
      {
        "id": "full_name",
        "type": "short_text",
        "label": "Full name",
        "validation": { "required": true }
      },
      {
        "id": "email",
        "type": "email",
        "label": "Email",
        "validation": { "required": true }
      }
    ],
    "settings": {
      "submitButtonText": "Register",
      "successMessage": "You're on the list.",
      "hideHeader": false,
      "hideTitle": false,
      "hideDescription": false
    },
    "isPublished": true,
    "submissionCount": 128,
    "createdAt": "2026-09-12T09:14:00.000Z",
    "updatedAt": "2026-10-01T16:22:00.000Z"
  }
}
  • 200 Form details
  • 401 Unauthorized
  • 403 Missing forms:read
  • 404 Form not found
GET

/api/v1/forms/{formId}/entries

List entries

Returns a cursor-paginated list of submissions for a form. Fields marked sensitive are returned as "[REDACTED]" unless the key has entries:read:sensitive. Search by field value with the field and value query parameters together.

Auth
Bearer API key
Scope
entries:read
Parameters
NameInRequiredDescription
formIdpathYesForm id (cuid).
limitqueryNoPage size. Default 50, maximum 100.
cursorqueryNoEntry id from pagination.nextCursor.
fromqueryNoInclusive start date (ISO 8601).
toqueryNoInclusive end date (ISO 8601). The full end day is included.
sortqueryNocreatedAt order: desc (default) or asc.
fieldsqueryNoComma-separated field ids to include in data.
fieldqueryNoField id to search. Must be sent together with value.
valuequeryNoField value to match. Must be sent together with field.

Request

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries?limit=20&sort=desc&field=email&[email protected]" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": [
    {
      "id": "cm2entry9m0002abcdefghijk",
      "data": {
        "full_name": "Ada Lovelace",
        "email": "[email protected]"
      },
      "metadata": { "source": "web" },
      "createdAt": "2026-10-02T14:03:11.000Z",
      "updatedAt": "2026-10-02T14:03:11.000Z"
    }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "cm2entry9m0002abcdefghijk"
  }
}
  • 200 List of entries
  • 400 field and value must be sent together
  • 401 Unauthorized
  • 403 Missing entries:read
  • 404 Form not found
POST

/api/v1/forms/{formId}/entries

Create entry

Creates a submission for a form. Keys in data should match field ids from the form schema. The server applies the same validation and conditional logic as a public submit: hidden and skipped fields are dropped, then remaining fields are validated. The server stores source: "api" on the entry metadata after merging any caller metadata, or source: "make" or "n8n" when the caller sets those values. A caller cannot mark the entry as a public-form submission. Entries marked make or n8n are treated as API-created for consent. Sending true (or { "agreed": true }) for a Consent field stores the form's current agreement text, hash, version, timestamp, and source: "api". That means the API caller is vouching that the person consented to that wording. Mailchimp skips consent on API-created entries — both Consent fields and a single-option checkbox used for opt-in — unless the form owner turns on Allow API-created consent. A successful create queues an entry.created webhook. Rate limits and the entries:write scope apply.

Auth
Bearer API key
Scope
entries:write
Parameters
NameInRequiredDescription
formIdpathYesForm id (cuid).

Request

bash
curl -X POST "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "full_name": "Ada Lovelace",
      "email": "[email protected]"
    }
  }'
json
{
  "data": {
    "full_name": "Ada Lovelace",
    "email": "[email protected]"
  },
  "metadata": {
    "source": "crm"
  }
}

Response

json
{
  "data": {
    "id": "cm2entry9m0002abcdefghijk",
    "data": {
      "full_name": "Ada Lovelace",
      "email": "[email protected]"
    },
    "metadata": { "source": "crm" },
    "createdAt": "2026-10-02T14:03:11.000Z",
    "updatedAt": "2026-10-02T14:03:11.000Z"
  }
}
  • 201 Entry created
  • 400 Invalid request body or form validation failed
  • 401 Unauthorized
  • 403 Missing entries:write
  • 404 Form not found
GET

/api/v1/forms/{formId}/entries/count

Count entries

Returns the number of submissions for a form, optionally filtered by createdAt range.

Auth
Bearer API key
Scope
entries:read
Parameters
NameInRequiredDescription
formIdpathYesForm id (cuid).
fromqueryNoInclusive start date (ISO 8601).
toqueryNoInclusive end date (ISO 8601).

Request

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries/count" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": {
    "count": 128
  }
}
  • 200 Entry count
  • 401 Unauthorized
  • 403 Missing entries:read
  • 404 Form not found
GET

/api/v1/forms/{formId}/entries/{entryId}

Get entry

Returns a single submission. Sensitive fields follow the same redaction rules as list entries.

Auth
Bearer API key
Scope
entries:read
Parameters
NameInRequiredDescription
formIdpathYesForm id (cuid).
entryIdpathYesEntry (submission) id (cuid).

Request

bash
curl "https://grtforms.com/api/v1/forms/cm2form8k0001abcdefghijk/entries/cm2entry9m0002abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": {
    "id": "cm2entry9m0002abcdefghijk",
    "data": {
      "full_name": "Ada Lovelace",
      "email": "[email protected]"
    },
    "metadata": { "source": "web" },
    "createdAt": "2026-10-02T14:03:11.000Z",
    "updatedAt": "2026-10-02T14:03:11.000Z"
  }
}
  • 200 Entry details
  • 401 Unauthorized
  • 403 Missing entries:read
  • 404 Form or entry not found
GET

/api/v1/webhooks

List webhooks

Returns webhooks configured for a form. The formId query parameter is required. Listing uses the webhooks:write scope (there is no separate read-only webhook scope).

Auth
Bearer API key
Scope
webhooks:write
Parameters
NameInRequiredDescription
formIdqueryYesForm id whose webhooks to list.

Request

bash
curl "https://grtforms.com/api/v1/webhooks?formId=cm2form8k0001abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": [
    {
      "id": "cm2hook0p0003abcdefghijk",
      "url": "https://example.com/hooks/grtforms",
      "events": ["entry.created"],
      "isEnabled": true,
      "failureCount": 0,
      "lastFailureAt": null,
      "createdAt": "2026-10-01T10:00:00.000Z",
      "updatedAt": "2026-10-01T10:00:00.000Z"
    }
  ]
}
  • 200 List of webhooks
  • 400 Missing formId
  • 401 Unauthorized
  • 403 Missing webhooks:write
  • 404 Form not found
POST

/api/v1/webhooks

Create webhook

Creates a webhook for a form. The signing secret is returned once and is never shown again. In production the URL must use HTTPS. Events: entry.created, entry.updated, entry.deleted.

Auth
Bearer API key
Scope
webhooks:write

Request

bash
curl -X POST "https://grtforms.com/api/v1/webhooks" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "formId": "cm2form8k0001abcdefghijk",
    "url": "https://example.com/hooks/grtforms",
    "events": ["entry.created"]
  }'
json
{
  "formId": "cm2form8k0001abcdefghijk",
  "url": "https://example.com/hooks/grtforms",
  "events": ["entry.created"]
}

Response

json
{
  "data": {
    "id": "cm2hook0p0003abcdefghijk",
    "formId": "cm2form8k0001abcdefghijk",
    "url": "https://example.com/hooks/grtforms",
    "events": ["entry.created"],
    "secret": "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "isEnabled": true,
    "createdAt": "2026-10-01T10:00:00.000Z"
  }
}
  • 201 Webhook created (secret included once)
  • 400 Invalid body or non-HTTPS URL in production
  • 401 Unauthorized
  • 403 Missing webhooks:write
  • 404 Form not found
DELETE

/api/v1/webhooks/{webhookId}

Delete webhook

Permanently deletes a webhook you own.

Auth
Bearer API key
Scope
webhooks:write
Parameters
NameInRequiredDescription
webhookIdpathYesWebhook id (cuid).

Request

bash
curl -X DELETE "https://grtforms.com/api/v1/webhooks/cm2hook0p0003abcdefghijk" \
  -H "Authorization: Bearer grt_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

json
{
  "data": {
    "success": true
  }
}
  • 200 Webhook deleted
  • 401 Unauthorized
  • 403 Missing webhooks:write
  • 404 Webhook not found
GET

/api/v1/openapi

OpenAPI document

Public OpenAPI 3.1 JSON for the v1 API. No API key is required. Useful for code generation and machine-readable clients.

Auth
Public — no API key
Scope
—

Request

bash
curl "https://grtforms.com/api/v1/openapi"

Response

json
{
  "openapi": "3.1.0",
  "info": {
    "title": "GRTforms API",
    "version": "1.0.0"
  },
  "paths": {
    "/api/v1/forms": {}
  }
}
  • 200 OpenAPI 3.1 document

Coming soon

These endpoints are coming soon and are not available yet. Payments, DocuSign, and other third-party product APIs are not part of GRTforms v1.

PATCH

/api/v1/forms/{formId}

Coming soon

Update a form

Updating schema or settings via the API is not available. Use the form builder.

DELETE

/api/v1/forms/{formId}

Coming soon

Delete a form

Deleting forms via the API is not available.

Zapier

The GRTforms Zapier app is not available yet. Contact Support to hear when it is.

What the app can do

  • New Entry — REST hook on entry.created with a polling fallback. Form dropdown, schema-generated output fields, page breaks skipped.
  • New Form — polling trigger.
  • Create Entry — same validation and hidden/skipped-field rules as a public submit.
  • Find Entry — search by field value.

MCP server

A remote Model Context Protocol server at /api/mcp wraps this same REST API. Authenticate with a Bearer API key — there is no separate OAuth path. MCP setup for Claude Desktop and Cursor.

OpenAPI spec

The same contract is published as OpenAPI 3.1 JSON. If this page ever fails to render in a restricted browser, the spec is still available at the URL below.

bash
curl "https://grtforms.com/api/v1/openapi"

/api/v1/openapi