API Reference

REST API endpoints for clients, onboardings, templates, responses, and events.

Pro plan and above.

The Portico API lets you create clients, launch onboardings, and read responses programmatically. All endpoints use JSON and follow REST conventions.

Authentication

Every request must include your API key in the Authorization header:

Authorization: Bearer pk_your-api-key

Generate keys in Settings > API & Webhooks. Keys are shown once on creation — store them securely. You can have up to 5 active keys per team.

Scopes

Each key can be restricted to specific scopes. If no scopes are set, the key has full access.

ScopeGrants access to
clients:readList and get clients
clients:writeCreate clients
onboardings:readList and get onboardings
onboardings:writeCreate and update onboardings
templates:readList and get templates
templates:writeReserved for future use
responses:readList responses for an onboarding

Base URL

https://app.portico.com/api/v1

Account info

Get current team

GET /api/v1/me

Returns team details and plan limits for the authenticated API key. Useful for verifying credentials and checking usage caps.

Scope: Any valid API key.

Response (200):

{
  "team": {
    "id": "uuid",
    "name": "Smith & Co",
    "tier": "pro"
  },
  "limits": {
    "onboardings_per_month": 50,
    "api_rate_limit": "200 requests/minute"
  }
}

onboardings_per_month is a number for capped plans or "unlimited" for Business.

Rate limits

PlanPer keyPer key + IP
Pro200 requests/min100 requests/min
Business1,000 requests/min500 requests/min

When you exceed the limit, the API returns 429 with a Retry-After: 60 header.

Error format

All errors return a JSON object with an error field:

{
  "error": "Description of what went wrong"
}

Common status codes:

CodeMeaning
400Invalid or missing request parameters
401Missing or invalid API key
403Key lacks the required scope, or your plan doesn't include API access
404Resource not found or doesn't belong to your team
409Conflict (e.g., duplicate email, invalid status transition)
429Rate limit exceeded
500Server error — retry the request

Pagination

List endpoints return a total count and accept limit (1–100, default 50). Clients and onboardings use offset-based pagination (offset, default 0). Templates and responses use page-based pagination (page, starts at 1).

Offset-based example:

{
  "clients": [...],
  "total": 142
}

Page-based example:

{
  "templates": [...],
  "total": 5,
  "page": 1,
  "limit": 50
}

Clients

List clients

GET /api/v1/clients

Query parameters: limit, offset, email

Filter by email for an exact (case-insensitive) match.

Scope: clients:read

Response (200):

{
  "clients": [
    {
      "id": "uuid",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "company": "Smith & Co",
      "phone": null,
      "notes": null,
      "created_at": "2026-03-15T10:30:00Z"
    }
  ],
  "total": 42
}

Get a client

GET /api/v1/clients/:id

Scope: clients:read

Response (200):

{
  "client": {
    "id": "uuid",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "company": "Smith & Co",
    "phone": null,
    "notes": null,
    "created_at": "2026-03-15T10:30:00Z"
  }
}

Create a client

POST /api/v1/clients

Scope: clients:write

Request body:

{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "company": "Smith & Co",
  "phone": "+1-555-0100",
  "notes": "Referred by Alex",
  "locale": "en-US",
  "date_format": "MM/DD/YYYY",
  "timezone": "America/New_York"
}

name and email are required. All other fields are optional and default to your team's settings.

Field limits: name 200 chars, company 200 chars, phone 50 chars, notes 2,000 chars.

Response (201):

{
  "client": {
    "id": "uuid",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "company": "Smith & Co",
    "phone": "+1-555-0100",
    "created_at": "2026-03-15T10:30:00Z"
  }
}

Errors:

CodeError
400name and email are required
400Invalid email address
409Client with this email already exists

Onboardings

List onboardings

GET /api/v1/onboardings

Query parameters: limit, offset, status, client_id

status accepts: draft, sent, in_progress, completed, cancelled.

Scope: onboardings:read

Response (200):

{
  "onboardings": [
    {
      "id": "uuid",
      "status": "in_progress",
      "progress_pct": 60,
      "created_at": "2026-03-15T10:30:00Z",
      "due_date": "2026-04-01T00:00:00Z",
      "assigned_to": "uuid",
      "clients": {
        "id": "uuid",
        "name": "Jane Smith",
        "email": "jane@example.com"
      },
      "templates": {
        "id": "uuid",
        "name": "Agency Onboarding"
      }
    }
  ],
  "total": 18
}

Get an onboarding

GET /api/v1/onboardings/:id

Scope: onboardings:read

Returns the full onboarding including the template snapshot (the form structure captured when the onboarding was created).

Response (200):

{
  "onboarding": {
    "id": "uuid",
    "status": "in_progress",
    "progress_pct": 60,
    "created_at": "2026-03-15T10:30:00Z",
    "due_date": "2026-04-01T00:00:00Z",
    "assigned_to": null,
    "template_snapshot": {
      "name": "Agency Onboarding",
      "steps": [
        {
          "id": "uuid",
          "title": "Business Details",
          "sort_order": 0,
          "is_optional": false,
          "fields": [
            {
              "id": "uuid",
              "type": "short_text",
              "label": "Company Name",
              "is_required": true,
              "sort_order": 0,
              "config": {}
            }
          ]
        }
      ]
    },
    "clients": {
      "id": "uuid",
      "name": "Jane Smith",
      "email": "jane@example.com"
    },
    "templates": {
      "id": "uuid",
      "name": "Agency Onboarding"
    }
  }
}

Create an onboarding

POST /api/v1/onboardings

Scope: onboardings:write

Request body:

{
  "template_id": "uuid",
  "client_id": "uuid",
  "due_date": "2026-04-01T00:00:00Z"
}

template_id and client_id are required. due_date is optional.

Creating an onboarding counts toward your monthly limit. If the limit is reached, the request returns 403.

Response (201):

{
  "onboarding": {
    "id": "uuid",
    "status": "draft",
    "progress_pct": 0,
    "created_at": "2026-03-15T10:30:00Z",
    "due_date": "2026-04-01T00:00:00Z",
    "token": "hex-string"
  }
}

Errors:

CodeError
400template_id and client_id are required
403Monthly onboarding limit reached
404Template not found
404Client not found

Update an onboarding

PATCH /api/v1/onboardings/:id

Scope: onboardings:write

Request body (all fields optional):

{
  "status": "cancelled",
  "due_date": "2026-04-15T00:00:00Z",
  "assigned_to": "uuid"
}

status only accepts cancelled or in_progress. Valid transitions:

FromTo
draftcancelled
sentcancelled
sentin_progress
in_progresscancelled

Setting status to cancelled deactivates the client link and cancels pending reminders.

Response (200):

{
  "onboarding": {
    "id": "uuid",
    "status": "cancelled",
    "progress_pct": 60,
    "due_date": "2026-04-15T00:00:00Z",
    "assigned_to": null
  }
}

Errors:

CodeError
400No valid fields to update
404Onboarding not found
409Could not transition from "completed" to "cancelled"

Send an onboarding

POST /api/v1/onboardings/:id/send

Scope: onboardings:write

Sends (or resends) the onboarding invite email to the client with a fresh magic-link token. The onboarding must be in draft, sent, or in_progress status.

Request body (optional):

{
  "force": false,
  "pin": "482901"
}
FieldTypeDescription
forcebooleanBypass the active-client safety check (default false). Required when the client has responded within the last 5 minutes.
pinstringOptional 6-digit PIN the client must enter before opening the form.

If the onboarding is in_progress and the client has submitted a response in the last 5 minutes, the request returns 409 unless force: true is set.

Sending a draft onboarding transitions its status to sent. Prior access tokens are deactivated so only the newest link works.

Response (200):

{
  "success": true
}

Errors:

CodeError
400No client associated with this onboarding
400PIN must be exactly 6 digits
404Onboarding not found
409Client is actively working on this onboarding. Set force: true to resend.
409Completed, cancelled, or expired onboardings must be reopened before sending
502Could not send the email. Please try again.

Templates

List templates

GET /api/v1/templates

Query parameters: page, limit

  • page — starts at 1, default 1.
  • limit — 1 to 100, default 50.

Scope: templates:read

Response (200):

{
  "templates": [
    {
      "id": "uuid",
      "name": "Agency Onboarding",
      "industry": "agency",
      "created_at": "2026-02-10T08:00:00Z",
      "updated_at": "2026-03-01T12:00:00Z"
    }
  ],
  "total": 5,
  "page": 1,
  "limit": 50
}

Get a template

GET /api/v1/templates/:id

Scope: templates:read

Returns the template with all steps and fields.

Response (200):

{
  "template": {
    "id": "uuid",
    "name": "Agency Onboarding",
    "industry": "agency",
    "created_at": "2026-02-10T08:00:00Z",
    "updated_at": "2026-03-01T12:00:00Z",
    "template_steps": [
      {
        "id": "uuid",
        "title": "Business Details",
        "sort_order": 0,
        "is_optional": false,
        "conditional_rules": null,
        "template_fields": [
          {
            "id": "uuid",
            "type": "short_text",
            "label": "Company Name",
            "is_required": true,
            "sort_order": 0,
            "config": {},
            "conditional_rules": null
          }
        ]
      }
    ]
  }
}

Responses

List responses

GET /api/v1/responses?onboarding_id=uuid

Query parameters: onboarding_id (required), page, limit

  • page — starts at 1, default 1.
  • limit — 1 to 100, default 50.

Scope: responses:read

Response (200):

{
  "responses": [
    {
      "id": "uuid",
      "field_id": "uuid",
      "value": "Acme Corp",
      "status": "approved",
      "rejection_reason": null,
      "created_at": "2026-03-20T14:00:00Z"
    }
  ],
  "total": 12,
  "page": 1,
  "limit": 50
}

Response status is one of: draft, submitted, approved, rejected, changes_requested.

Events

List events

GET /api/v1/events

Returns recent audit events for your team in reverse chronological order. Use this to poll for activity since your last sync.

Query parameters:

ParamDescription
limit1 to 100, default 25.
typeFilter by event type (see table below).

Scope: Any valid API key.

Event types:

TypeDescription
onboarding.completedClient finished all required steps
onboarding.sentInvite email delivered to client
onboarding.startedClient opened the onboarding link
onboarding.overdueOverdue workflow triggered
response.submittedClient submitted a field response
signature.signedClient signed a signature field
payment.succeededClient payment processed
file.uploadedClient uploaded a file
webhook.deliveredOutgoing webhook delivered

Response (200):

{
  "events": [
    {
      "id": "uuid",
      "type": "onboarding.completed",
      "timestamp": "2026-03-20T14:00:00Z",
      "onboarding_id": "uuid",
      "data": {}
    }
  ]
}

Errors:

CodeError
400Unknown event type. Allowed: ...

Zapier subscriptions

Manage webhook subscriptions for Zapier triggers. See Webhooks for event types and payload details.

Subscribe

POST /api/v1/zapier/subscribe

Request body:

{
  "event": "onboarding.completed",
  "target_url": "https://hooks.zapier.com/hooks/catch/123/abc"
}

target_url must be a public HTTPS URL.

Allowed events: onboarding.completed, onboarding.sent, onboarding.started, onboarding.overdue, response.submitted, signature.signed, payment.succeeded, file.uploaded

Response (201):

{
  "id": "uuid"
}

Unsubscribe

DELETE /api/v1/zapier/subscribe

Request body:

{
  "id": "uuid"
}

Response (200):

{
  "success": true
}