# PATCH /portals/:portal_id

**Resource:** [Portals](./portals.md)  
**MCP tool:** `update_portal`  
**Scopes:** `portals:write`  
**Write operation:** yes

Update a portal. Every field is optional and independent: whatever you omit is left exactly as stored. Set status to disabled to suspend member access. Passing config REPLACES the whole object (get → merge → patch), but omitting name, type, or status does NOT reset them, so do not echo them back unless you mean to change them.

## Parameters

| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `portal_id` | path | string | yes |  |
| `name` | body | string | no | New portal name. Omit to leave it unchanged. |
| `type` | body | string | no | partner \| client \| doc_collection \| stakeholder. OMIT this field to leave the current type unchanged: only supply it when you deliberately want to change the type. client = a private login for a client to track their project. partner = a referrer/broker portal (adds Referrals & commissions). stakeholder = an external contractor/stakeholder portal (pairs with config.stakeholder.enabled). doc_collection = a document-collection portal. |
| `status` | body | string | no | active \| disabled. Omit to leave it unchanged. |
| `config` | body | object | no | Portal design config (jsonb). REPLACE-not-merge: get the current config, merge your change, PATCH the whole object back — a partial config wipes the omitted keys. Keys: `branding` {logoUrl,primaryColor,secondaryColor,tagline,icon}; `welcome` {headline,body}; `contact` {name,role,email,phone}; `description` (invite-email blurb); `menus` [{id,label,icon,content?,sidebar:[{type,ref?,slug?,label?,icon?,memberCanEdit?}]}] — the top-nav tabs + sidebar entries (entry types: notepad, notepad_folder, scheduler, form, work_orders, signing, documents, invoices, files, referrals, booking, notepads, transcripts, chat); `dataSharing` {<content_type>: {enabled, mode:"all"\|"selected", resourceIds?, statusIds?, memberCanEdit?, memberEditableFields?}} — Container 1, auto-share of the ANCHORED OPPORTUNITY's content (content types: work_orders, transcripts, invoices, signing, documents, files, bookings, images, notepads). enabled=false fully protects a type; mode "selected" limits to resourceIds; memberEditableFields (work_orders only) is the allowlist of field SLUGS a member may write — omit it for the original "every field editable" behaviour, or pass [] for status-only so a contractor can accept/decline a job offer without being able to retype the offer amount; memberSelectableStatuses (work_orders only) is the same idea for status — the allowlist of status IDS a member may move a job INTO, omitted meaning every workspace status is offered; requiredFieldsByStatus (work_orders only) {"<status id>":["slug"]} gates a transition until those fields are filled ("_attachments" = at least one photo), so a subcontractor cannot mark a job finished before the work is confirmed; `templateContent` {signing?:[document_template_id], documents?:[document_template_id], files?:[secure_file_id], images?:[company_file_id], notepads?:[notepad_id]} — Container 2, TEMPLATE content proliferated into EVERY instance (signing/documents are rendered per instance with the referrer/opportunity variables). Set this on a TEMPLATE; a create/edit trigger proliferates it. `referral` {enabled,commissions,referrerContactId} — PARTNER portals only (commission dashboard); referrerContactId is pinned at instantiation. `stakeholder` {enabled,contractorContactId}: STAKEHOLDER portals only (type "stakeholder"), the external contractor/subcontractor design. enabled=true adds the "My Jobs" (contractor_jobs) + member-editable work_orders tabs; contractorContactId is pinned at instantiation and scopes the jobs dashboard to that contractor across every opportunity they service. `defaultMembers` [{email,name?}] (TEMPLATE only): the internal team seeded as members onto EVERY portal instantiated from this template, so they get in-portal access without an invite email. `documentsOpportunityId` — PARTNER portals only: the referrer's own opportunity that scopes templateContent signing/document/file candidates + variables. `anchor` {type:"opportunity"\|"contact", id} — set by the instantiate endpoints, not by hand. `templateId` — the source template (set at instantiation). `keepInstancesAligned` (TEMPLATE only, boolean, default false) — when true, every instance of this template (existing AND future) keeps its DESIGN (menus, home, branding, welcome, contact, nav, description) matched to the template: any template design edit propagates to all instances automatically, and switching it on aligns the existing instances immediately. Each instance's own opportunity anchor, referrer, members, shared records, and data-sharing rules are always preserved. Set it with set_portal_template_alignment. `lastSyncedAt` (instance only) — stamped by the alignment job when an instance was last matched to its template. |
| `require_approval` | body | boolean | no | Optional. Set true to route this write into the approval queue for human review instead of executing it immediately (returns 202 + an approval_id). Works even when your key/token has permission to execute directly. |

## Request example

```bash
curl -X PATCH   "https://api.trustpager.com/functions/v1/api/v1/portals/:portal_id"   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{"name":"...","type":"...","status":"..."}'
```

---
Base URL: `https://api.trustpager.com/functions/v1/api/v1` — Auth: `Authorization: Bearer YOUR_API_KEY`