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/clients
List 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/clients
Create 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/templates
All 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/portals
List 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/portals
Send 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/:portalId
Full 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/:responseId
Download 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/review
Approve 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/webhooks
Last 100 outbound deliveries for this account — event, endpoint, HTTP status, and error text — so partners can debug their own integrations without contacting support.