REST API
Everything under /api/public/v1. JSON in, JSON out. Send your key as Authorization: Bearer ifl_live_… (or x-api-key).
Scope requirements
read— all GET endpoints.write— creating clients, sending portals, reviewing items.
Give read-only keys to dashboards/reporting tools and read+write keys to automations that act.
Clients
GET
/api/public/v1/clientsList all clients, newest first (max 1000).
Response
{
"data": [
{ "id": "b1e…", "name": "Acme Co", "email": "hello@acme.com", "notes": null, "created_at": "2026-09-30T10:00:00Z" }
]
}POST
/api/public/v1/clientsCreate a client, or update an existing one matched by email (or by
client_id). Adding a client triggers the account's welcome email and a client.created webhook.Request
{ "name": "Acme Co", "email": "hello@acme.com", "notes": "Referred by Dana" }Response · 201
{ "data": { "id": "b1e…", "name": "Acme Co", "email": "hello@acme.com" } }Templates
GET
/api/public/v1/templatesAll templates with their ordered items — use the ids to send portals.
Response
{
"data": [
{
"id": "tpl_…",
"name": "Web Design Kickoff",
"is_default_seed": true,
"items": [
{ "id": "itm_…", "label": "Final logo files (SVG/PNG)", "type": "file", "required": true, "order_index": 1 }
]
}
]
}Portals
GET
/api/public/v1/portalsList portals with completion counts. Optional
?status= filter (not_started | in_progress | awaiting_review | complete).Response
{
"data": [
{
"id": "por_…",
"status": "in_progress",
"client": { "id": "b1e…", "name": "Acme Co", "email": "hello@acme.com" },
"items_total": 8,
"items_submitted": 5,
"items_approved": 4,
"items_rejected": 1
}
]
}POST
/api/public/v1/portalsSend a portal: creates the tokenized link, emails the client, fires
portal.sent. Pass client_id, or client_name + client_email to create the client inline. Subject to plan limits (Starter: 3 active portals/month).Request
{ "template_id": "tpl_…", "client_name": "Acme Co", "client_email": "hello@acme.com" }Response · 201
{
"data": {
"id": "por_…",
"status": "not_started",
"client_id": "b1e…",
"portal_url": "https://your-domain.com/p/<signed-token>"
}
}GET
/api/public/v1/portals/:portalIdFull export: client, template, and every response. Credential answers are represented by a
has_credential flag — plaintext never leaves the vault.Response
{
"data": {
"id": "por_…",
"status": "awaiting_review",
"portal_url": "https://…/p/<signed-token>",
"client": { "id": "b1e…", "name": "Acme Co", "email": "hello@acme.com" },
"template": { "id": "tpl_…", "name": "Web Design Kickoff" },
"items": [
{
"response_id": "res_…",
"label": "Final logo files (SVG/PNG)",
"type": "file",
"status": "submitted",
"value_text": null,
"file_url": "/api/public/v1/portals/por_…/files/res_…",
"has_credential": false,
"rejection_note": null,
"submitted_at": "2026-09-30T12:34:56Z"
}
]
}
}GET
/api/public/v1/portals/:portalId/files/:responseIdDownload one submitted file. Streams bytes with a content-disposition filename. Requires a read key and works for files stored in the built-in bucket or any connected provider.
Reviews
POST
/api/public/v1/responses/:responseId/reviewApprove or reject a submitted item (scope: write). Rejecting emails the client with your note, exactly like the dashboard flow — only that one item reopens.
Request
{ "action": "reject", "note": "Logo is low-res — please re-upload at 300dpi+" }Response
{ "data": { "ok": true, "portal_id": "por_…", "portal_status": "in_progress" } }When all items are approved the portal flips to
complete and the portal.completed webhook fires — wire your downstream automations to that, not to a polling loop.Webhook deliveries
GET
/api/public/v1/webhooksLast 100 outbound deliveries for this account — event, endpoint, HTTP status, and error text — so partners can debug their own integrations without contacting support.