Qwilr API
Qwilr provides access to API to both build Qwilr pages as well as subscribe to page events and integrate with other systems. This article will cover how to get started with our API features. For more information on Qwilr's API you can also review our API documentation pages here: API Docs and Reference Site
In this article:
Setting Up API Access
Note: API Access is available on Starter, Growth, and Scale Plans.
Page creation is charged on a per-page basis. Accounts that have access to Qwilr's API can buy bundles of API pages. More information on pricing and setup for page creation can be found here.
Step 1. Head over to your API settings by logging into your Qwilr account and going to Account settings on the left-hand sidebar of your Qwilr dashboard at the bottom.

Step 2. Select Qwilr API on the left, then create an API Access Token by adding a clear description of what the access token is for, then copy the token immediately and store it somewhere secure. This is the only time you'll be able to view it. If you lose it, you'll need to generate a new one.
Important info: Your access token grants full access to your Qwilr account. Don’t include it in your codebase, publish it to GitHub, or share it in public spaces.
Step 3. BearerAuth
All API requests must include your access token as a Bearer token in the Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN Content-Type: application/json
All requests must be sent over HTTPS to:
https://api.qwilr.com/v1
Rate Limits: The API allows up to 120 requests per minute from a single IP address. Requests exceeding this limit will receive a 429 Too Many Requests response.
Creating Pages
Pages are created by sending a POST request to /pages . There are two methods for building a page, each suited to different use cases:
From a Template
- Allows you to create pages based on Templates in Qwilr that you design
- Use tokens as dynamic placeholders
- All blocks are fixed; layout is pre-set
- Text, images, and maps can be personalized
From Saved Blocks
- Programmatically include, exclude, or reorder saved blocks
- Supports the defining and manipulating line items in the Quote block
- More flexibility, however, utilizing a more complex setup
- Requires block IDs (retrievable via
GET /blocks/saved)
Important: If your page needs Quote block pricing modified or dynamic, you must use the Saved Blocks approach. Dynamic Quote blocks are not supported via templates.
Creating Pages from a Template
Step 1. Create your template in Qwilr. For more guidance on this, go to our Templates help doc. After your template is created you can get the template ID from the URL in our editor. Example: app.qwilr.com/#/page/your-template-id
Step 2. Personalize your template with tokens in the editor. API reference keys for substitutions will use underscores for spaces and lower case letters. Example: a token named "Client Name" becomes client_name
You can find your API references keys for tokens in your account when editing a template and going to the 'tokens' button, selecting account, and clicking on the three dot menu beside the token to 'Copy API reference'.

Post Request Example
// POST https://api.qwilr.com/v1/pages
{
"templateId": "your-template-id-here",
"name": "Proposal for Acme Corp",
"published": true,
"substitutions": {
"client_name": "Acme Corp",
"proposal_value": "$12,000",
"expiry_date": "30 April 2026"
},
"metadata": {
"crm_deal_id": "deal_9834",
"sales_rep": "jane.smith"
},
"tags": ["enterprise", "q2-2026"]
}
For reference in the above:
-
name: string
Title of the page, visible as the browser page title.
-
published: boolean
Whether the page is publically available;
falsemeans the page will be in Draft status. Default isfalse. -
substitutions: object
Mapping of token API reference keys to substitution values used throughout the page. The values can be overwritten if the same keys are defined in the block-level substitutions.
-
metadata: object
Data you provide, that will be returned as part of all Webhooks.
-
tags: array of strings
The tags for your page. Tags are case-sensitive.
-
ownerId: string(Id)^[a-z0-9]{24}$
ID of the user that should own the page. If not specified, the owner of the access token will be the owner of the page.
Create Pages from Saved Blocks
Step 1. Build your saved blocks by creating a Template in Qwilr, adding your tokens while in the template editor, and then saving those blocks individually with the included tokens.
Step 2. Get block IDs by either copying each block ID from the Blocks library (three-dot menu → "Copy Block ID"), or call GET /blocks/saved to retrieve all IDs programmatically.
Post Request Example
// POST https://api.qwilr.com/v1/pages
{
"blocks": [
{
"id": "your-intro-block-id",
"substitutions": {
"client_name": "Acme Corp"
}
},
{
"id": "your-quote-block-id",
"quoteSettings": {
"currency": "USD",
"selectionType": "single"
},
"quoteSections": [
{
"displayMode": "table",
"lineItems": [
{
"type": "fixedCost",
"description": "Platform licence (annual)",
"unitPrice": 9600,
"quantity": 1,
"billingSchedule": "annual"
}
]
}
]
}
],
"name": "Acme Corp — Annual Proposal",
"published": true,
"substitutions": {
"client_name": "Acme Corp"
},
"metadata": { "crm_deal_id": "deal_9834" },
"tags": ["enterprise"],
"ownerId": "6ee0f841f3cc8900090d82dc"
}
Token formatting tip: Tokens must be formatted in the Qwilr editor before page creation — you cannot pass HTML or markdown via the API. Line breaks can be passed using \n in your substitution values.
Successful Response
A successful creation returns an HTTP 201 with a JSON object containing the new page's ID, public URL, collaborate link, and PDF download URL. Store the id field as you'll need it for webhooks and future API calls.
Updating a Page
After creation, the only property you can update via the API is whether a page is published (live) or unpublished (draft)
Assigning Page Ownership
By default, the user associated with the access token owns new pages. To assign ownership to another team member, include their ownerId in the request. You can retrieve user IDs by calling GET /users
Webhooks
Webhooks let Qwilr notify your systems in real time when something happens to a page. Instead of polling the API, you register a URL and Qwilr will send an HTTP POST to it whenever a subscribed event fires.
Subscribing to an Event
// POST https://api.qwilr.com/v1/webhooks
{
"event": "pageAccepted",
"targetUrl": "https://your-server.com/webhooks/qwilr"
}
The response returns a subscription id which you will want to save if you need to cancel the subscription later.
Available Events
| Event | When it fires |
|---|---|
pageAccepted |
All required signers have accepted the page |
pagePartiallyAccepted |
At least one signer has accepted, but not all (e.g. 1 of 3 signatures collected) |
pagePreviewAccepted |
A preview acceptance was completed (useful for testing) |
pageFirstViewed |
The page was opened for the first time |
pageViewed |
The page was opened (fires on every view) |
pageSetLive |
The page was published / set to Live status |
pageRevivedLive |
A previously declined, expired, or draft page was re-published |
Webhook Payload
Each webhook callback includes the triggering event type, the pageId , and any metadata you attached when the page was created. To get the full page details, call GET /pages/{pageId} .
// Example pageAccepted payload received at your targetUrl
{
"event": "pageAccepted",
"pageId": "abc123def456",
"metadata": {
"crm_deal_id": "deal_9834",
"sales_rep": "jane.smith"
},
"acceptedAt": "2026-04-16T10:22:00Z"
}
Responding to Callbacks
Your endpoint must return an HTTP 200 response. If it doesn't (due to a timeout after 30 seconds, or an error response), Qwilr will retry the callback once more. If both attempts fail, the callback is dropped.
Security Recommendations
Qwilr webhooks don't include a cryptographic signature by default. To secure your endpoint:
- Obfuscate your URL: use a UUID or random string in your callback URL so it can't be guessed
- Use metadata secrets: include a shared secret in the
metadatafield when creating pages, and verify it when the webhook arrives - Allowlist Qwilr's IP: Qwilr sends callbacks from
13.237.241.97
Managing Subscriptions
To list all your active webhook subscriptions: GET /webhooks
To cancel a subscription: DELETE /webhooks/{subscriptionId}
Troubleshooting Page Creation
HTTP Error Reference
| Status | Meaning | Common cause |
|---|---|---|
| 400 | Bad Request | Malformed JSON, missing required fields, or invalid values in the request body |
| 401 | Unauthorised | Missing or invalid access token in the Authorization header |
| 403 | Forbidden | Token is valid but lacks permission (e.g. API not enabled for your account) |
| 404 | Not Found | The template ID or block ID doesn't exist or belongs to a different account |
| 422 | Unprocessable | Request structure is valid JSON, but fails Qwilr's business logic validation |
| 429 | Rate Limited | More than 120 requests per minute from one IP address |
Common Issues: Templates
Tokens appear blank on the created page
- Check the following:
- Use the API reference key, not the display name. In the editor, a token might be called "Client Company" but its API key could be
client_company. Go to the Token manager in your editor to confirm the exact key. - Values must be strings. Pass all substitution values as strings (e.g.
"12000"not12000), unless you're using repeating tokens which require an array. - Substitutions can be page-level or block-level. Values in the top-level
substitutionsobject apply to the entire page; values inblocks[n].substitutionsapply to that specific block and take precedence.
- Use the API reference key, not the display name. In the editor, a token might be called "Client Company" but its API key could be
Special characters appear as & , ' etc.
Characters are being HTML-encoded somewhere in your pipeline. Check that you're not double-encoding values before sending. Also ensure you're using the modern "blue bubble" token style in the editor, not the legacy {{ }} double-brace style, which has a known encoding issue.
Template ID returns 404
Confirm the ID is copied from the URL when in template-edit mode. Also verify the access token used belongs to the same account that owns the template.
Common Issues: Saved Blocks
Block ID returns 404
Use GET /blocks/saved to retrieve a fresh list of valid IDs. IDs can change if a block is deleted and recreated. Confirm you're not mixing template IDs and block IDs in the same request.
Quote block doesn't appear or shows incorrect data
The Quote block can only have tokens in the text area at the top of the Quote block. When it comes to the entire contents of the quote tables/line items/etc., these are replaced by the quoteSections data in your API request. Any formatting applied in the editor to the quote block will be overwritten. Line item types must be either fixedCost or text ; the variable type is not currently supported.
Request too large error
The API has a maximum request size of 200KB. This is most commonly hit when passing large substitution values (e.g. long blocks of text). Trim content where possible, and avoid sending base64-encoded image data. Use public image URLs instead.
Common Issues: Webhooks
Not receiving callbacks at all
- Confirm the subscription was successfully created (
GET /webhooksto list active subscriptions) - Check that your
targetUrlis publicly accessible. Localhost URLs won't work in production - Ensure you're not testing page views while logged into Qwilr. Internal views are filtered out by default
Receiving callbacks multiple times
Your endpoint may not be returning an HTTP 200 response, causing Qwilr to retry. Check that your handler always returns 200 even if you process the event asynchronously. Ensure you haven't accidentally registered the same subscription multiple times.
Frequently Asked Questions
Are there any limits on API Usage?
There’s a rate limit on API calls that amounts to 120 requests/minute from a single IP address. Calls above this will fail, with a 429 response
Can I update an existing Qwilr page via the API?
PUT /pages/{pageId} . Updating token values or page content after creation is not yet supported but is on the roadmap.
Can I accept a Qwilr page through the API?
Can I download a PDF of the page via the API?
pdfUrl is returned in the create page response. However, the PDF may not be immediately ready. Wait at least 5 minutes after page creation before retrieving it. For accepted pages, the auditTrailPdf URL is available immediately through the get page endpoint with the acceptance expand option.
Can I use the Quote block with templates?
Can I pass HTML or rich text through the API?
\n for line breaks, and Unicode characters for symbols like bullets (•, ★, ✦).
What are the page statuses returned by the API?
| API status | Meaning |
|---|---|
draft |
Page is unpublished |
live |
Published and accessible (includes "Pending" state) |
accepting |
Partially accepted — at least one, but not all required signatures collected |
accepted |
Fully accepted by all required signers |
declined |
Declined by the recipient |
Can I search or list all pages in my account?
Yes. GET /pages returns a paginated, newest-first list of your pages, filterable by status, tags, folder, or owner. See the API Reference for parameters and response details.
What is the maximum API request size?
Can I set link expiry or access controls via the API?
Can I use repeating tokens inside 2-column or accordion widgets?
Does the API support right-to-left languages?
Unfortunately, as with the main Qwilr app and pages, the API doesn’t enable right-to-left language content
What limits do we have for the metadata sent to the API?
Metadata from 3rd party systems is sent as a JSON object within the create page request. This means that it needs to be valid JSON that fits in the 200kb size limit.
Can I choose which Creators can edit/view the page via the API?
The API doesn’t have the ability to change the page permissions, which users or teams can edit or view.
Can the payment options be specified through the API?
We don’t have the page’s payment options directly available through the API. However multiple accept blocks can be used, set with the different payment options and then selected which to use when creating the page through the API.