Pages
Page endpoints allow you to create, read, update, and delete pages within projects. Pages are the content that QR codes link to. Pages are a shallow resource under projects: you list and create pages in the context of a project (/projects/{project}/pages), but get, update, and delete a single page by its ID (/pages/{id}).
Enums (integer-backed):
- visibility:
1= public,2= hidden,3= private (password-protected) - template:
0= default,1= link,2= vcard_plus,3= document,4= social
List Pages
Get a list of all pages in a project. Each item is a collection representation (no form_data, sections, or page_design_template). The response includes included_qr_codes: QR code resources for each page that has a linked QR code.
GET https://v1.freeqr.io/api/projects/{project}/pages
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string (ULID) | Yes | Project ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 10, max: 100) |
sort | string | No | Sort field: id, views, name (prefix with - for descending) |
filters[search] | string | No | Search by page name (partial match) |
Response
{
"data": [
{
"id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"name": "My Page",
"domain": "example.com",
"handle": "my-page",
"visibility": 1,
"password": null,
"password_hint": null,
"template": 0,
"page_design_template_id": null,
"views": 150,
"form_submissions_count": 5,
"unseen_form_submissions_count": 2,
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}
],
"meta": {
"total": 1
},
"included_qr_codes": []
}
List items do not include form_data, sections, or page_design_template; use Get Page for full detail.
Create Page
Create a new page in a project.
POST https://v1.freeqr.io/api/projects/{project}/pages
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string (ULID) | Yes | Project ID |
Request Body
{
"name": "My New Page",
"domain": "example.com",
"handle": "my-new-page",
"template": 0,
"visibility": 1,
"password": null,
"password_hint": "Hint for password",
"page_design_template_id": "01arz3ndektsv4rrffq69g5fav",
"form_data": {}
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Page name (max 100 characters) |
domain | string | No | Domain (max 100, must exist in domains). If omitted, the first available domain is used. |
handle | string | No | Page handle/slug (max 60 characters). Stored slugified; must be unique per domain. |
template | integer | No | Page template enum: 0–4 (see template enum above) |
visibility | integer | No | Visibility enum: 1 public, 2 hidden, 3 private |
password | string | No | Password for password-protected pages (min 1, max 100 when set) |
password_hint | string | No | Password hint (max 255 characters) |
page_design_template_id | string (ULID) | No | Page design template ID (must be type PAGE_DESIGN). When provided, the template's current version config is merged with form_data; form_data overrides template config. |
form_data | object | No | Form data (max 200 keys). Validated against page form sections; overrides template config when page_design_template_id is set. |
Possible errors:
422— Handle already taken: body message likeHandle "my-page" is already taken. Please choose another one.(handle is slugified and checked for uniqueness per domain).403— Max pages limit reached: body messageMax pages limit reached. Please upgrade your plan.
Response
Returns the created page as a full PageResource (same shape as Get Page): includes form_data, sections, page_design_template (when loaded and viewable). No included_files on create.
{
"id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"name": "My New Page",
"domain": "example.com",
"handle": "my-new-page",
"visibility": 1,
"password": null,
"password_hint": null,
"template": 0,
"page_design_template_id": null,
"views": 0,
"form_submissions_count": 0,
"unseen_form_submissions_count": 0,
"form_data": {},
"sections": [],
"page_design_template": null,
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}
When page_design_template_id is provided, the template's current version config is merged with form_data (with sanitization); form_data is then validated against the page's form sections.
Get Page
Get a single page by ID. Returns the full PageResource (including form_data, sections, page_design_template when loaded and viewable) plus included_files (files referenced by the page).
GET https://v1.freeqr.io/api/pages/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | Page ID |
Response
{
"id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"name": "My Page",
"domain": "example.com",
"handle": "my-page",
"visibility": 1,
"password": null,
"password_hint": null,
"template": 0,
"page_design_template_id": null,
"views": 150,
"form_submissions_count": 5,
"unseen_form_submissions_count": 2,
"form_data": {},
"sections": [],
"page_design_template": null,
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}
Top-level included_files is also returned (array of file resources referenced by this page). page_design_template is included only when the template relation is loaded and the authenticated user can view it.
Update Page
Update an existing page. Only the following fields are updatable; domain and handle cannot be changed.
PATCH https://v1.freeqr.io/api/pages/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | Page ID |
Request Body
{
"name": "Updated Page Name",
"template": 0,
"visibility": 3,
"password": "new-password",
"password_hint": "Hint text",
"page_design_template_id": "01arz3ndektsv4rrffq69g5fav",
"form_data": {}
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Page name (max 100 characters) |
template | integer | No | Page template enum: 0–4 |
visibility | integer | No | Visibility: 1 public, 2 hidden, 3 private |
password | string | No | Password (required when visibility is 3; min 1, max 100) |
password_hint | string | No | Password hint (max 255 characters) |
page_design_template_id | string (ULID) | No | Page design template ID (PAGE_DESIGN type). Template config is merged with form_data; form_data overrides. |
form_data | object | No | Form data (max 200 keys); validated and merged with template config when page_design_template_id is set. |
Response
Same structure as Get Page (full PageResource; included_files not included on update response).
Delete Page
Delete a page.
DELETE https://v1.freeqr.io/api/pages/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | Page ID |
Response
Returns the deleted page resource (same structure as Get Page).
Get Public Page
Get a public page by identifier. Used for unauthenticated access (e.g. when a user scans a QR code). The identifier is the page's handle and domain in the form handle.domain (e.g. my-page.example.com).
GET https://v1.freeqr.io/api/pages/public/{identifier}
Requires Authentication: No
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Page identifier: handle.domain (e.g. my-page.example.com) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | No | Access token for password-protected pages (obtained from Unlock Page) |
Response
Success (200): Returns the public page payload plus included_files and included_components. The main payload contains only id, domain, handle, and form_data. Components are filtered by public visibility.
{
"id": "01arz3ndektsv4rrffq69g5fav",
"domain": "example.com",
"handle": "my-page",
"form_data": {}
}
Top-level included_files (files for the page and its components) and included_components (public components) are also returned.
Password-protected without valid token (403): When the page is not public and no valid token is provided:
{
"meta": {
"page_id": "01arz3ndektsv4rrffq69g5fav",
"password_hint": "Hint for password"
}
}
Use Unlock Page with the page ID and password to obtain a token, then call this endpoint again with ?token=....
Unlock Page
Unlock a password-protected page and get a temporary access token. Use this when the user has entered the correct password; then call Get Public Page with ?token={token} to load the page.
POST https://v1.freeqr.io/api/pages/{page}:unlock
Requires Authentication: No
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | string (ULID) | Yes | Page ID |
Request Body
{
"password": "page-password"
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
password | string | Yes | Page password (max 255 characters) |
Response
Success: Returns a temporary token in meta.token. Use it as the token query parameter when calling Get Public Page.
{
"meta": {
"token": "access-token-string"
}
}
Incorrect password: Response body contains "message": "Incorrect password."