Download OpenAPI specification:Download
REST API for Zapa Client Portals management.
All API endpoints require OAuth 2.0 authentication. Include your access token in the Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN
Subscribe to real-time events by registering webhooks. All webhook payloads are signed with HMAC-SHA256.
API resources can be turned into web links that open the app directly:
https://app.zapaportal.com/org/{org_id}/vault/{portal_id}/folder/main?file={file_id} to a portal link to open that file's preview automaticallyThe org_id, portal id, and file id values all come from the API responses below.
The API intentionally provides file metadata only — there is no endpoint that returns file contents or a download URL. Portals typically hold sensitive client documents (tax records, contracts, signed agreements), so downloads are restricted to signed-in users in the web app, where access is permission-checked and recorded in the portal's audit history. Every OAuth consent screen makes this promise to the end user: "This app will not have access to download file contents."
To send someone to a file, use a file preview link (above) rather than trying to fetch the bytes.
If your integration needs a capability this API doesn't offer yet, contact support@zapaportal.com — we prioritize API additions based on customer requests.
Exchanges an authorization code for an access token, or refreshes an expired access token.
This endpoint lives on the API domain (https://api.zapaportal.com), unlike the authorize
endpoint which is on the web app domain.
Access tokens expire after 1 hour (expires_in: 3600); store the refresh_token and use the
refresh_token grant to get a new one without user interaction. The endpoint accepts both
application/x-www-form-urlencoded and application/json bodies.
| grant_type required | string Value: "authorization_code" |
| code required | string |
| client_id required | string |
| client_secret required | string |
| redirect_uri required | string |
{- "grant_type": "refresh_token",
- "refresh_token": "string",
- "client_id": "string",
- "client_secret": "string"
}{- "access_token": "string",
- "token_type": "Bearer",
- "expires_in": 3600,
- "refresh_token": "string",
- "scope": "string"
}Returns the user and organization associated with the access token — user and org IDs and names, the OAuth client ID, and the scopes that were actually granted.
Call this first after obtaining a token: it verifies authentication works, tells you which scopes
the user approved on the consent screen (they may have granted fewer than you requested), and gives
you the org_id needed to build web links to portals and files. No specific scope is required —
any valid token works.
{- "user_id": "string",
- "user_name": "string",
- "user_email": "string",
- "org_id": "string",
- "org_name": "string",
- "client_id": "string",
- "scopes": [
- "string"
]
}Returns the organization's configured workflow states as {id, name} pairs. Call this before
setting a portal's workflow state — the workflow endpoint requires both values, and they must match
an existing state. Also handy for populating dropdowns in your integration's UI.
[- {
- "id": "string",
- "name": "string"
}
]Returns all active (non-deleted) portals in your organization, including tags, workflow state, task counters, and recent-activity timestamps — useful for syncing portal lists into a CRM or dashboard.
Templates: pass templatesOnly=true to list your organization's portal templates instead of
regular portals. Use a template's id as the templateId when creating a new portal to copy its
folder structure and settings.
Linking: build a web link to any portal with
https://app.zapaportal.com/org/{org_id}/vault/{id}/folder/main.
| templatesOnly | boolean Default: false When true, returns portal templates instead of regular portals |
[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "tags": [
- "string"
], - "workflowStateId": "string",
- "workflowStateName": "string",
- "archived": true,
- "created_at": "2019-08-24T14:15:22Z",
- "created_by": "string"
}
]Creates a new client portal — typically one per client or engagement.
Creating from a template (Business & Enterprise): first call GET /api/v1/portals?templatesOnly=true
to find your template's id, then pass it as templateId. The new portal is created with the
template's folder structure and default settings:
{ "name": "ABC Corp - 2026 Tax Return", "templateId": "TEMPLATE_UUID", "tags": ["tax-2026"] }
Set isTemplate: true instead to create a new template rather than a regular portal.
Next steps: invite the client with POST /api/v1/portals/{portalId}/guests, then upload files
with POST /api/v1/portals/{portalId}/files. Subscribing a webhook to portal.created lets other
systems react to portals created here or in the web app.
| name required | string Display name, e.g. client name or project |
| tags | Array of strings Tags for filtering and grouping on the dashboard |
| isTemplate | boolean Create a reusable template instead of a regular portal |
| templateId | string <uuid> ID of a portal template to copy structure and settings from |
{- "name": "string",
- "tags": [
- "string"
], - "isTemplate": true,
- "templateId": "196100ac-4eec-4fb6-a7f7-86c8b584771d"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "tags": [
- "string"
], - "workflowStateId": "string",
- "workflowStateName": "string",
- "archived": true,
- "created_at": "2019-08-24T14:15:22Z",
- "created_by": "string"
}Returns a single portal's details, including tags, workflow state, and activity timestamps. Use this to refresh one portal's state instead of re-listing everything, or to resolve a portal ID received in a webhook payload.
| portalId required | string <uuid> |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "tags": [
- "string"
], - "workflowStateId": "string",
- "workflowStateName": "string",
- "archived": true,
- "created_at": "2019-08-24T14:15:22Z",
- "created_by": "string"
}Updates a portal's name, tags, workflow state, or archive status. Only include the fields you want to change.
Set archived: true to archive a portal when an engagement ends — nothing is deleted, and it can
be restored later with archived: false. There is no delete endpoint; portals can only be
permanently deleted from the web app.
| portalId required | string <uuid> |
| name | string |
| tags | Array of strings |
| workflowStateId | string |
| workflowStateName | string |
| archived | boolean |
{- "name": "string",
- "tags": [
- "string"
], - "workflowStateId": "string",
- "workflowStateName": "string",
- "archived": true
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "tags": [
- "string"
], - "workflowStateId": "string",
- "workflowStateName": "string",
- "archived": true,
- "created_at": "2019-08-24T14:15:22Z",
- "created_by": "string"
}Moves a portal to a different workflow state (e.g. "In Progress" → "Review"). Fetch valid
workflowStateId/workflowStateName pairs from GET /api/v1/workflow-states first — both values
are required and should come from that list.
State changes fire the portal.workflow_changed webhook event, making this a good integration
point for driving external processes (e.g. notify your practice-management system when a portal
reaches "Complete").
| portalId required | string <uuid> |
| workflowStateId required | string |
| workflowStateName required | string |
{- "workflowStateId": "string",
- "workflowStateName": "string"
}Returns metadata (name, size, extension, upload date/user) for every active file and folder in a
portal. Folders appear as entries too — a file's parent_folder_file_id links it to its folder.
Linking to a file: to send someone to a specific file, build a web preview link:
https://app.zapaportal.com/org/{org_id}/vault/{portalId}/folder/main?file={file_id} — opening it
signs the user in (if needed) and opens that file's preview automatically.
Why no download endpoint? The API deliberately never returns file contents. Portal documents are often sensitive (tax records, contracts, signed agreements), and the OAuth consent screen promises users that connected apps cannot download their file contents. Downloads happen in the web app, where they're permission-checked per user and recorded in the portal's audit history. If your integration has a use case that requires content access, contact support@zapaportal.com to discuss it.
| portalId required | string <uuid> |
[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "size": 0,
- "extension": "string",
- "parent_folder_file_id": "0a617e6d-dd07-4f15-a106-b7abf547121c",
- "uploaded_at": "2019-08-24T14:15:22Z",
- "uploaded_by": "string"
}
]Uploads a file to a portal. Two modes are supported:
One-step (recommended for automation): include a public HTTPS file_url and the server fetches
the file (up to 100 MB) and stores it immediately — the response is the completed file record.
This is how the Zapier integration uploads files.
Two-step (for uploading local bytes): omit file_url and the response contains a presigned
upload_url valid for 1 hour. Then:
PUT the raw file bytes to upload_url, sending the same Content-Type you specified here
(the response echoes the exact method and headers to use)POST /api/v1/portals/{portalId}/files/complete with the returned file_id to register
the file — until you do, the file will not appear in the portalSuccessful uploads fire the file.uploaded webhook event and notify portal users per their
notification settings. Pass parent_folder_file_id (a folder's file ID from the list endpoint) to
place the file inside a folder.
| portalId required | string <uuid> |
| filename required | string File name including extension, e.g. |
| contentType | string MIME type (defaults to |
| size | integer File size in bytes (used for the two-step flow's file record) |
| file_url | string <uri> Public HTTPS URL to fetch the file from (one-step mode, max 100 MB) |
| parent_folder_file_id | string <uuid> Folder to place the file in (omit for the portal root) |
{- "filename": "string",
- "contentType": "string",
- "size": 0,
- "parent_folder_file_id": "0a617e6d-dd07-4f15-a106-b7abf547121c"
}{- "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
- "expires_in": 3600,
- "method": "PUT",
- "headers": { }
}Registers a file after you've PUT its bytes to a presigned upload_url. This creates the file
record so it appears in the portal, notifies users, and fires the file.uploaded webhook event.
Only needed for the two-step upload flow — one-step (file_url) uploads are registered
automatically.
| portalId required | string <uuid> |
| file_id required | string <uuid> The |
| filename required | string |
| extension | string Defaults to the filename's extension |
| size | integer File size in bytes |
| parent_folder_file_id | string <uuid> |
{- "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
- "filename": "string",
- "extension": "string",
- "size": 0,
- "parent_folder_file_id": "0a617e6d-dd07-4f15-a106-b7abf547121c"
}{- "error": "string",
- "error_description": "string"
}Returns all active tasks in a portal with their status, due date, assignees, and type. Useful for syncing outstanding client action items into an external task tracker, or checking whether a client has completed their requested items before advancing the portal's workflow state.
| portalId required | string <uuid> |
[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "title": "string",
- "description": "string",
- "action_type": "General",
- "status": "open",
- "due_date": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z"
}
]Creates a task in a portal — a request for the client (or your team) to do something, like
"Upload your W-2" or "Review the engagement letter". Tasks are created with status open;
assignees are notified and receive reminders as the due date approaches.
action_type categorizes the request (Question?, File Request, Sign Document, Fill Form,
or General). Creating a task fires the task.created webhook event.
| portalId required | string <uuid> |
| title required | string |
| description | string |
| due_date | string <date-time> |
| action_type | string Default: "General" |
| assigned_guest_ids | Array of strings |
| assigned_guest_names | Array of strings |
| assigned_member_ids | Array of strings |
| assigned_member_names | Array of strings |
{- "title": "string",
- "description": "string",
- "due_date": "2019-08-24T14:15:22Z",
- "action_type": "General",
- "assigned_guest_ids": [
- "string"
], - "assigned_guest_names": [
- "string"
], - "assigned_member_ids": [
- "string"
], - "assigned_member_names": [
- "string"
]
}{- "task_id": "736fde4d-9029-4915-8189-01353d6982cb"
}Updates one or more fields on a task; omitted fields keep their current values. Task IDs come from the portal's task list endpoint.
You can change status here (e.g. reopen a task by setting a non-done status), but if you just want
to finish a task, prefer POST /api/v1/tasks/{taskId}/complete — it also fires the
task.completed webhook event.
| taskId required | string <uuid> |
| title | string |
| description | string |
| status | string Use |
| due_date | string or null <date-time> |
{- "title": "string",
- "description": "string",
- "status": "string",
- "due_date": "2019-08-24T14:15:22Z"
}{- "success": true,
- "task_id": "736fde4d-9029-4915-8189-01353d6982cb"
}Marks a task as completed and updates the portal's open/closed task counters. This is the
preferred way to finish a task from an integration — for example, auto-completing a "File Request"
task after your system uploads the requested document. Fires the task.completed webhook event.
| taskId required | string <uuid> |
{- "success": true,
- "task_id": "736fde4d-9029-4915-8189-01353d6982cb"
}Invites one or more guests (clients) to a portal by email. Each guest receives an invitation email;
new users are prompted to create an account, existing users just accept the invitation. Accepts
either singular (email, name) or plural (emails, names) forms.
This is the typical second step after creating a portal: create the portal, invite the client,
upload their documents. Invitations fire the guest.invited webhook event.
Guest permissions can't be set through the API yet — new guests get your organization's default portal permissions, adjustable in the web app's Members panel. Contact support@zapaportal.com if your integration needs permission control.
| portalId required | string <uuid> |
string <email> Single guest email (alternative to | |
| name | string Single guest name (alternative to |
| emails | Array of strings <email> [ items <email > ] |
| names | Array of strings |
{- "email": "user@example.com",
- "name": "string",
- "emails": [
- "user@example.com"
], - "names": [
- "string"
]
}{- "success": true,
- "invited_count": 0,
- "message": "string"
}Returns all webhook subscriptions for your organization, including each webhook's subscribed
events, enabled status, signing secret, and delivery success/failure counters. Check the
counters to spot endpoints that are failing deliveries.
[- {
- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "events": [
- "portal.created"
], - "secret": "string",
- "enabled": true,
- "success_count": 0,
- "failure_count": 0,
- "created_at": "2019-08-24T14:15:22Z"
}
]Subscribes an HTTPS endpoint to real-time events. Deliveries are signed with HMAC-SHA256 in the
X-Webhook-Signature header — verify it with the returned secret before trusting a payload
(see the Developer Quickstart for example code). Failed deliveries
are retried 3 times with exponential backoff.
After creating, use POST /api/v1/webhooks/{webhookId}/test to send a test event and confirm your
endpoint receives and verifies it correctly.
| url required | string <uri> |
| events required | Array of strings Items Enum: "portal.created" "portal.workflow_changed" "file.uploaded" "file.signed" "task.created" "task.completed" "guest.invited" |
{- "events": [
- "portal.created"
]
}{- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "events": [
- "portal.created"
], - "secret": "string",
- "enabled": true,
- "success_count": 0,
- "failure_count": 0,
- "created_at": "2019-08-24T14:15:22Z"
}Updates the URL, subscribed events, or enabled flag; at least one field is required. Setting
enabled: false pauses deliveries without losing the configuration or signing secret — useful
during maintenance windows on your receiving endpoint.
| webhookId required | string <uuid> |
| url | string <uri> |
| events | Array of strings Items Enum: "portal.created" "portal.workflow_changed" "file.uploaded" "file.signed" "task.created" "task.completed" "guest.invited" |
| enabled | boolean |
{- "events": [
- "portal.created"
], - "enabled": true
}{- "success": true,
- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675"
}Permanently removes a webhook subscription — deliveries stop immediately and the signing secret is invalidated. If you only need a temporary stop, disable it with the update endpoint instead.
| webhookId required | string <uuid> |
{- "success": true,
- "webhook_id": "a47606a1-5b39-4a81-9480-c2cb738ff675"
}Sends a signed webhook.test event to the webhook's URL and reports the delivery result — use it
to verify your endpoint is reachable and your signature verification works before relying on real
events. The response's status_code is what your endpoint returned; a success: false with an
error message usually means a network or TLS problem reaching your URL.
| webhookId required | string <uuid> |
{- "success": true,
- "status_code": 0,
- "delivery_id": "9892f438-d31c-4ff2-bc84-146525b292ff",
- "error": "string"
}