Client portals API
How to work a firm's client portals from a script, an automation or an assistant: list portals, send a client a request, add folders, move the work to a stage, read what happened, and hear when a client finishes a request or adds a file.
A client portal is a private space for one client: a Messages channel they see, a Firm only channel they don't, a file library, requests (files, answers, approvals, tasks) and stages. This guide is for the firm's side. A client's own view stays in the app: nothing here can be reached with a client's token.
Before you start
- The add-on. Client portals are a paid add-on (switch
clientPortalin plans), priced per portal licence. While it's off or has ended, reads still work so a firm can see what it made; writes answer403 add_on_required. An Owner who switched portals off gets403 feature_off. A self-hosted server has every feature on and needs no licences. - Portal licences. Only staff holding a portal licence work client portals, and a token acts as its owner: a token of someone without one sees no portals (the list is empty and one portal answers
404), and the app's own routes answer403 portal_licence_required. Owners and Admins hand licences out in Org Settings → Client portals. Clients never need one. - People only. Every portal route needs a personal token (or a person's OAuth connection) of someone in the workspace who isn't a Guest. An app token gets
403, and apps can't be added to a portal's channels. - Scopes.
| Scope | Lets a token |
|---|---|
portals:read |
list portals, read one, its files, requests and timeline |
portals:write |
set up a portal, change it, its stages and branding, add folders, send and change requests |
- What a token reaches is what its owner reaches in the app. You see the portals you're on the staff of; Owners, Admins and people with Manage client portals set them up. A portal you can't open answers
404. - Never an email address. Contacts and invitations come with names and dates only.
Portals
GET /v1/orgs/{orgId}/portals
POST /v1/orgs/{orgId}/portals { "name": "Smith & Co", "kind": "business", "lead_user_id": "…", "staff_ids": ["…"] }
GET /v1/portals/{portalId}
PATCH /v1/portals/{portalId} { "welcome": "…", "show_progress": true, "require_2fa": true }
PATCH /v1/orgs/{orgId}/portal-branding { "display_name": "Acme Accounting", "accent_light": "#1f6f43" }
POST takes an Idempotency-Key, so a retried request sets up one portal. category_id makes an existing category into a portal. PATCH can turn two-factor sign-in on, never off.
Stages and folders
PUT /v1/portals/{portalId}/stages { "names": ["Intake", "Documents", "Review", "Filed"] }
POST /v1/portals/{portalId}/stage { "position": 1 }
GET /v1/portals/{portalId}/files
POST /v1/portals/{portalId}/folders { "name": "2024 Tax", "visibility": "shared" }
A new folder is Firm only unless you ask for "visibility": "shared". Moving to a stage may start requests from the portal's template, as it does in the app.
Requests
GET /v1/portals/{portalId}/requests
GET /v1/portals/{portalId}/requests/{requestId}
POST /v1/portals/{portalId}/requests { "title": "Tax documents", "due_on": "2027-01-15",
"items": [{ "kind": "upload", "label": "W-2s" },
{ "kind": "question", "label": "Any new dependents?", "config": { "question": "yesno" } }] }
PATCH /v1/portals/{portalId}/requests/{requestId}
Item kinds are upload, question, checkbox, approve and task (a sign item links an envelope sent in the app). A new request tells the client the way the app does: an email naming the firm, how many items and when (titles only if the firm chose that), and a line in their portal. internal_note is for your staff and never reaches the client. POST takes an Idempotency-Key.
The timeline
GET /v1/portals/{portalId}/timeline?limit=100
What happened, newest first: requests sent and completed, files added, stages, approvals. Each line has id, kind, at, text and firm_only, which marks a line the client never sees.
Client channels in the app
In Missived a portal is a client: Create → Client makes one in a single step (name, kind, lead, the team, a template and the client's contacts, with "Send the invites now" ticked by default). The client gets a row under Clients in the sidebar and opens on tabs: Conversation (the portal's Messages channel, which the client sees), Internal notes (its Firm only channel, which they never see), Files, Requests, Billing and a firm-only Timeline. The channels keep their ids, so messages_channel_id and internal_channel_id here are the Conversation and Internal notes, and everything on this page works unchanged. Your client still sees "Messages" in their own portal.
Someone on a client's team who has no portal licence sees the client's name in the sidebar and a locked page with "Ask an Admin for a licence", nothing else.
Logging email with a client
When your server has a logging domain (CLIENT_LOG_EMAIL_DOMAIN, log.missived.com on the hosted service), everyone with a portal licence gets a private address in Settings → Calendar & connections → Client email logging, like [email protected]. Bcc it when you write to a client, or forward a client's email to it, and the email is filed on that client's Timeline under Email: only your team sees it, never the client. Attachments go to the client's Files in a Firm only Email attachments folder.
- Who it's from. We take mail only from your own account address when it passes DMARC, or from your own Gmail or Outlook forwarding rule (a trusted ARC seal from Google or Microsoft naming you). Anything else is refused, so a stranger can't write into a client's file.
- Which client. An email is filed on every client whose contacts' verified addresses, or the extra addresses you've added for that client, appear on it. One that matches no client waits in your Unfiled tray for 14 days: file it to a client (and remember the address), or drop it.
- Each client's own address. A client's Email view shows its own logging address, for mail from an address we don't know yet. Only that client's team can send to it.
- Setting up a rule. Gmail sends a confirmation code to your logging address when you add it as a forwarding address. The code appears on the Settings card; we never open Gmail's link for you. If your Microsoft 365 workspace blocks forwarding outside the company, the card says so (5.7.520).
- Hide and delete. The person who logged an email, or an Owner or Admin, can hide it from the rest of the team; the person who logged it can delete it.
- Kept. Logged email stays until the client is deleted (it's in both exports); the raw copy is kept 30 days, Unfiled email 14 days.
Logged and sent email isn't in /v1, MCP or events.
Emailing a client from Missived
A client's Timeline → Email has Write an email, and each email there has Reply. You pick who it goes to from the client's own contacts and addresses (nobody else), write a subject and a message, and can attach files from the client's library: up to 10 MB is attached, anything bigger goes as a link (only for files the client can already open in their portal). It waits 10 seconds for Undo, then goes; the card shows Sent, Delivered, Delayed, Bounced or "Not sent" and why, with Try again.
- From your own address. In Settings → Calendar & connections → Client email logging, Send as your own address asks for your work address and emails it a six-digit code. The address must be on a domain your workspace verified for sending (a ticket queue's Your own address, on the Tickets add-on). Once confirmed, email to clients comes from you, and we check the domain again every time one goes.
- Otherwise "via Missived". Without a confirmed address (or if the domain stops being verified) it goes as "You at Your firm via Missived" from the hosted service's client-mail domain, with a line saying who sent it.
- Replies come back here. Every email carries a reply address on the logging domain, so the client's answer is filed on the same client and you're told in Activity. Tick Replies go to my own mailbox to have them go to you instead (they aren't filed then). Reply addresses stop working 90 days after the client is closed.
- Limits. 10 people per email, 30 emails an hour per person and the add-on's daily number per workspace. The word filter applies. An address that reports our mail as spam can't be emailed again, and the workspace's client email pauses until Missived looks into it.
Email sent from Missived goes through our email provider (Resend), like every other email we send; it's listed on our privacy promise.
What stays in the app
Inviting or removing a client, reminding them now, cancelling a request, deleting a folder, closing or deleting a portal, who may invite and what reminder emails say all happen in Missived, where a person confirms them.
Events
portal.request_completed (every required item of a request is done), portal.file_uploaded (a file or a new version went into the library, by the client or your staff) and portal.stage_changed go to event subscriptions, and from there to Zapier, Make and n8n.
A portal is private, so its events go only to a subscription that lists the portal's Messages channel (messages_channel_id on the portal) in channel_ids; a subscription for every channel never receives them, and only someone who can open the portal can list its channel. An event carries ids, the stage position, a file's size, version and SHA-256, and who did it. Names — the portal's, the request's title, the file's name, the stage's name — come along only when the org sends content in events. Answers, notes and email addresses are never sent.
For assistants
The MCP server has tools on these routes: list_portals, get_portal, list_portal_files, list_portal_requests and get_portal_timeline to read, and create_portal, update_portal, update_portal_branding, set_portal_stages, move_portal_stage, create_portal_folder, create_portal_request and update_portal_request to work. Confirm a request's items and due date with the person before sending it: the client is told.