Skip to content
Join our Slack

Getting started

This walkthrough takes you from an empty terminal to a form and its first response. You will create a Personal Access Token, confirm what it can do, post a Form Blueprint, publish it, submit a response, and read that submission. Every step uses curl; only steps 1 and 7 need a browser.

  • A Fomr account, and a role in your organization that can create and publish forms. The walkthrough tells you how to check.
  • curl, and jq to pull values out of responses.

The examples throughout this section of the help center use one form: the Docs Example Form, an event registration form with an intro page, a question page, and a final page. Save this document as event-registration.json — step 4 posts it as-is.

event-registration.json
{
"public_id": "Ev3ntReg01",
"name": "Event registration",
"is_template": false,
"hidden_values": [
{
"key": "utm_source",
"default_value": null
}
],
"logic_rules": [],
"calculated_values": [],
"pages": [
{
"public_id": "pIn1",
"name": "Welcome",
"type": "intro",
"is_hidden": false,
"is_entrypoint": true,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nWlc",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "text",
"meta": {
"content": "<p>Join us for the Acme Events community meetup.</p>"
}
},
{
"public_id": "nSt1",
"parent_node_id": null,
"column_index": null,
"sort_order": 2,
"is_hidden": false,
"type": "actions",
"meta": {
"label": "Start",
"is_page_actions_override": false,
"back_button_label": "Back"
}
}
]
},
{
"public_id": "pQ01",
"name": "Your details",
"type": "normal",
"is_hidden": false,
"is_entrypoint": false,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nNam",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "text_input_short",
"meta": {
"question": "<span>Full name</span>",
"placeholder": "Jane Doe",
"validation": {
"required": true
}
}
},
{
"public_id": "nEml",
"parent_node_id": null,
"column_index": null,
"sort_order": 2,
"is_hidden": false,
"type": "text_input_email",
"meta": {
"question": "<span>Email address</span>",
"placeholder": "[email protected]",
"validation": {
"required": true
}
}
},
{
"public_id": "nTck",
"parent_node_id": null,
"column_index": null,
"sort_order": 3,
"is_hidden": false,
"type": "multiple_choice",
"meta": {
"question": "<span>Ticket type</span>",
"select_options_data": [
{
"value": "general",
"label": "General",
"label_formatted": "<span>General</span>"
},
{
"value": "student",
"label": "Student",
"label_formatted": "<span>Student</span>"
},
{
"value": "speaker",
"label": "Speaker",
"label_formatted": "<span>Speaker</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"multiple_choice_variant": "alphabet",
"validation": {
"required": true
}
}
},
{
"public_id": "nDie",
"parent_node_id": null,
"column_index": null,
"sort_order": 4,
"is_hidden": false,
"type": "checkbox_choice",
"meta": {
"question": "<span>Dietary needs</span>",
"select_options_data": [
{
"value": "vegetarian",
"label": "Vegetarian",
"label_formatted": "<span>Vegetarian</span>"
},
{
"value": "vegan",
"label": "Vegan",
"label_formatted": "<span>Vegan</span>"
},
{
"value": "gluten_free",
"label": "Gluten-free",
"label_formatted": "<span>Gluten-free</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"select_options_style": "checkbox",
"validation": {
"required": false
}
}
},
{
"public_id": "nCmp",
"parent_node_id": null,
"column_index": null,
"sort_order": 5,
"is_hidden": true,
"type": "text_input_short",
"meta": {
"question": "<span>Company</span>",
"validation": {
"required": false
}
}
},
{
"public_id": "nSub",
"parent_node_id": null,
"column_index": null,
"sort_order": 6,
"is_hidden": false,
"type": "actions",
"meta": {
"label": "Register",
"is_page_actions_override": false,
"back_button_label": "Back"
}
}
]
},
{
"public_id": "pFn1",
"name": "Thank you",
"type": "final",
"is_hidden": false,
"is_entrypoint": false,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nThx",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "thank_you",
"meta": {
"thank_you_title": "<h1 style=\"text-align: center\">See you there!</h1>",
"thank_you_description": "<p style=\"text-align: center\">We sent a confirmation to your email address.</p>",
"thank_you_icon": "check_animated"
}
}
]
}
],
"settings": {
"email_notifications_enabled_owner": 1,
"email_notifications_enabled_respondent": 0,
"email_notifications_recipients_owner": [
],
"email_notifications_recipient_respondent": null
},
"properties": {
"show_branding": 1,
"auto_jump_next_page": 1,
"allow_back_navigation": 1,
"meta_title": null,
"meta_description": null,
"meta_index": 1,
"meta_favicon_url": null,
"meta_ogimage_url": null,
"partial_response_capture_enabled": 0
},
"access": {
"is_closed": 0,
"close_at": null,
"open_at": null,
"duplicate_response_rule": null
},
"styles": {
"theme_id": null,
"override_properties": null
}
}

The document is a Form Blueprint, the single JSON shape that describes a whole form. Form Blueprint explains every part of it; for now it is enough to know that you send one and get a form back.

  1. Create a Personal Access Token.

    Open Settings > API in the app, create a token, and give it a name that says where it will run. The full value is shown once and never again — after that only a Token Hint like fomrt_…Ab9z remains, so copy it now.

  2. Configure authentication.

    Typing a token into a command leaves it in your shell history, so read it into your environment instead. Run this, paste the token, and press Enter:

    Terminal window
    read -rs FOMR_TOKEN
    export FOMR_TOKEN
  3. Confirm the token works.

    GET /v1/me is the call to make first. It answers with your organization, the member the token acts as, and that member’s current permissions.

    Terminal window
    curl -s https://platform.fomr.io/v1/me \
    -H "Authorization: Bearer $FOMR_TOKEN" | jq '.member.permissions'

    Two values in there decide whether the rest of this page will work: forms must be write, and form_publish must be true. If either is missing, ask an organization Admin to create the token: only Admins can create and use Personal Access Tokens. A 401 instead means the token is wrong — check you copied the whole value.

  4. Create the form.

    POST /v1/forms takes the blueprint and answers 201 with the new form. Keep the public_id it assigns; every later call needs it.

    Terminal window
    FORM_ID=$(curl -s -X POST https://platform.fomr.io/v1/forms \
    -H "Authorization: Bearer $FOMR_TOKEN" \
    -H "Content-Type: application/json" \
    --data-binary @event-registration.json | jq -r '.public_id')
    echo "$FORM_ID"
    Ev3ntReg01

    Your form gets its own ID; Ev3ntReg01 is the one the documentation examples use. The full response also carries the stored document under draft, with every ID the server minted, so a real client can persist those without a second call.

  5. Check that the form is ready to publish.

    POST …/validate runs Publish Validation against the draft and answers 200 either way. It writes nothing and publishes nothing.

    Terminal window
    curl -s -X POST -H "Authorization: Bearer $FOMR_TOKEN" \
    "https://platform.fomr.io/v1/forms/$FORM_ID/validate"
    POST /v1/forms/Ev3ntReg01/validate
    {
    "valid": true,
    "issues": [],
    "would_publish_version": 1,
    "share_url": "https://fomr.io/s/Ev3ntReg01"
    }

    valid: true and an empty issues array mean the next step will succeed. If you edited the document and something is missing, each entry in issues names the page and block to fix.

  6. Publish it.

    POST …/publish publishes the stored draft as the next version and answers the form. The share link is in urls.share.

    Terminal window
    curl -s -X POST -H "Authorization: Bearer $FOMR_TOKEN" \
    "https://platform.fomr.io/v1/forms/$FORM_ID/publish" | jq -r '.urls.share'
    https://fomr.io/s/Ev3ntReg01
  7. Open and submit the form.

    Paste that URL into a browser, fill in the form, and submit it. The API lists completed submissions by default, so finish the form before moving on.

  8. Read the submission.

    List the newest completed submission, keep its public ID, then read the full resource:

    Terminal window
    SUBMISSION_ID=$(curl -s -H "Authorization: Bearer $FOMR_TOKEN" \
    "https://platform.fomr.io/v1/forms/$FORM_ID/submissions?limit=1" \
    | jq -r '.data[0].public_id')
    curl -s -H "Authorization: Bearer $FOMR_TOKEN" \
    "https://platform.fomr.io/v1/forms/$FORM_ID/submissions/$SUBMISSION_ID"
    GET /v1/forms/Ev3ntReg01/submissions/Sb01EventReg2026Ab3cDe
    {
    "public_id": "Sb01EventReg2026Ab3cDe",
    "form_public_id": "Ev3ntReg01",
    "status": "completed",
    "published_version": 3,
    "created_at": "2026-10-01T09:00:00.000Z",
    "updated_at": "2026-10-01T09:00:00.000Z",
    "answers": {
    "nNam": "Ada Lovelace",
    "nEml": "[email protected]",
    "nTck": {
    "value": "speaker",
    "label": "Speaker"
    }
    },
    "hidden_values": {
    "utm_source": "conference-newsletter"
    }
    }

    answers is keyed by each question’s stable node public ID: nNam is the name question in the Docs Example Form. hidden_values carries any Hidden Values captured when the response began. Submissions covers filters, Partial Responses, file downloads, and exports.

Only two of those calls changed anything: the create and the publish.

CallWhat it changed
GET /v1/meNothing. It reads the identity behind the token.
POST /v1/formsCreated the form and stored the document as its draft. Nothing was live yet.
POST …/validateNothing. It reports whether a publish would succeed.
POST …/publishPublished version 1. From here the share link serves.
GET …/submissionsNothing. It lists submissions for the form.
GET …/submissions/{submission_public_id}Nothing. It reads one submission.

Creating a form and writing its draft never change what respondents see. Only publish does that. Forms covers the rest of the lifecycle: taking a form offline again, closing it, scheduling when it opens and closes, and moving it to Trash.

  • Change the form. Pull the document with GET …/draft, edit it, and send the whole thing back with PUT …/draft. Pages and blocks are matched by ID, so IDs and answer history survive the write. See Replacing the draft.
  • Learn the document. Form Blueprint covers pages, nodes, settings, and every node type.
  • Learn the shared rules. Conventions covers content types, dates, identifiers, and how lists page.
  • Work with answers. Submissions covers completed and partial responses, signed file downloads, and streamed CSV or JSONL exports.
  • Handle failure properly. Errors has the code catalog, the extra fields each code carries, and the retry rules.

Your next form could be the best-looking thing on your site.

Free forever. Unlimited responses. No signup to start.