Skip to content

Portal API

Portal routes are called by the portal worker via service token. The portal worker authenticates the client and forwards context via headers:

  • X-Portal-Company-Id — the authenticated client’s company ID (required)
  • X-Portal-User-Roleviewer, collaborator, or admin
  • X-Portal-User-Id — the portal user’s ID
  • X-Portal-Group-Company-Ids — comma-separated company IDs for group-aware queries (parent + children)

All routes enforce company scoping on every query. Endpoints that write data require collaborator or admin role.

GET /api/portal/dashboard

Returns an overview for the client including active projects with task progress, financial summary, recent activity, upcoming milestones, and latest announcements.

Response: { projects, taskCounts, financial, recentActivity, milestones, announcements }

  • financial includes total_invoiced, total_outstanding, total_overdue
  • Group-aware: uses all companies in the group
GET /api/portal/projects

Query parameters:

ParamTypeDescription
statusstringFilter by project status

Response: { projects, taskCounts }

GET /api/portal/projects/:id

Response: { project, goals, members, aiSummary, decisions }

Includes the project’s goals, team members, latest AI summary, and recent decisions.

GET /api/portal/projects/:id/tasks

Returns non-private tasks for the project with status, priority, type, and assignee details.

Response: { tasks }

GET /api/portal/projects/:id/roadmap

Returns the roadmap linked to the project, if any, with its items.

Response: { roadmap, items }

GET /api/portal/projects/:id/decisions

Returns all decisions for the project with the decider’s name.

Response: { decisions }

GET /api/portal/budgets

Returns budgets linked to the client’s projects or deals. Group-aware.

Response: { budgets }

GET /api/portal/budgets/:id

Response: { budget, lineItems }

POST /api/portal/budgets/:id/approve

Role required: collaborator or admin

Approves a budget that has pending_approval status. Logs approval in history.

Response: { ok: true }

GET /api/portal/invoices

Returns all invoices for the client’s companies. Group-aware.

Response: { invoices }

GET /api/portal/invoices/:id

Response: { invoice, lineItems, payments }

GET /api/portal/contracts

Returns contracts where any signer’s email belongs to the client’s company contacts. Group-aware.

Response: { contracts }

GET /api/portal/pages

Returns published pages linked to the client’s company or projects. Group-aware.

Response: { pages }

GET /api/portal/pages/:id

Returns a single published page with its rich content.

Response: { page }

GET /api/portal/notes

Returns client-visible notes across the client’s projects, deals, budgets, and invoices. Group-aware. Limited to 100 results.

Response: { notes }

POST /api/portal/notes

Role required: collaborator or admin

Request body:

FieldTypeDescription
resourceTypestringproject or invoice
resourceIdstringResource ID
contentstringNote content
parentIdstringOptional parent note ID (for replies)
portalUserIdstringPortal user ID

Response: { id } (201)

GET /api/portal/history

Returns recent history entries for the client’s projects, invoices, and budgets. Limited to 50 results.

Response: { history }

GET /api/portal/team

Returns team members and leads across the client’s active projects. Group-aware.

Response: { team, leads }

GET /api/portal/announcements

Returns the latest 20 company-category announcements.

Response: { announcements }

POST /api/portal/requests

Role required: collaborator or admin

Creates a task from a client request. Auto-assigns SLA if applicable. Notifies the delivery manager.

Request body:

FieldTypeDescription
titlestringRequest title (required)
descriptionstringRequest description
prioritystringPriority value (e.g. high, medium)
projectIdstringOptional project to attach the request to
portalUserIdstringPortal user ID

Response: { taskId, ok: true } (201)

GET /api/portal/notifications

Query parameters:

ParamTypeDescription
filterstringall (default), unread, or archived
limitnumberMax results (default: 50, max: 100)
offsetnumberPagination offset (default: 0)

Response: { notifications, limit, offset }

GET /api/portal/notifications/unread-count

Response: { unread_count }

POST /api/portal/notifications/:id/read

Response: { ok: true }

POST /api/portal/notifications/read-all

Response: { ok: true }

GET /api/portal/notifications/preferences

Returns notification types that support portal delivery, merged with the user’s saved preferences.

Response: { preferences }

PUT /api/portal/notifications/preferences

Request body:

FieldTypeDescription
preferencesarrayArray of { notification_type, channel_in_app, channel_email }

Response: { ok: true }

GET /api/portal/connections

Returns platform connections for the client’s company. Only available when dashboard_enabled is true on the client profile.

Response: { connections, dashboard_enabled }

POST /api/portal/connections

Connects a new platform (e.g. Shopify, Google Ads). Tests the connection, encrypts credentials, and stores. Notifies the assigned DM.

Request body:

FieldTypeDescription
platformstringPlatform key
credentialsobjectPlatform-specific credentials
display_namestringOptional display name

Response: { connection } (201)

DELETE /api/portal/connections/:id

Disconnects a client-managed connection. Cannot disconnect agency-managed connections.

Response: { ok: true }

POST /api/portal/connections/sync

Triggers an async sync of all connected platforms for the client.

Response: { message: "Sync started" } (202)

GET /api/portal/results

Returns aggregated metrics for the client with period-over-period comparison and goal tracking. Only available when dashboard_enabled is true.

Query parameters:

ParamTypeDescription
period_typestringweek, month (default), quarter, or year
period_endstringEnd date (YYYY-MM-DD, default: today)

Response: { period, metrics, insight, connected_platforms, dashboard_enabled }

Each metric includes value, prior_value, delta_pct, target, and tracking_status.

GET /api/portal/results/campaigns

Returns campaign-level performance data aggregated over the selected period.

Query parameters:

ParamTypeDescription
period_typestringweek, month (default), quarter, or year
period_endstringEnd date (YYYY-MM-DD)
platformstringFilter by ad platform
sort_bystringSort field (default: spend). Options: spend, impressions, clicks, conversions, roas, cpa, etc.
sort_dirstringasc or desc (default)

Response: { period, campaigns, totals }

POST /api/portal/results/ask

Ask a natural-language question about the client’s performance data.

Request body:

FieldTypeDescription
questionstringThe question to ask

Response: { answer }

GET /api/portal/questionnaire

Returns the client’s success profile questionnaire responses.

Response: { profile }

POST /api/portal/questionnaire

Role required: collaborator or admin

Submits or updates the client success profile. Can also create client goals. Notifies the assigned DM on first completion.

Request body:

FieldTypeDescription
financial_year_startstringFinancial year start
financial_year_endstringFinancial year end
key_trading_periodsstringKey trading periods
channel_online_pctnumberOnline channel percentage
channel_instore_pctnumberIn-store channel percentage
channel_wholesale_pctnumberWholesale channel percentage
celebration_visionstringCelebration vision
growth_priorities_textstringGrowth priorities
company_direction_textstringCompany direction
goalsarrayArray of goal objects with metric, target, unit, period, metric_key, metric_label

Response: { ok: true }

GET /api/portal/qbrs

Returns presented QBRs for the client. Internal sections are filtered based on portal_visibility settings.

Response: { qbrs }

GET /api/portal/onboarding

Returns the client’s onboarding template, stages, milestones, and progress.

Response: { template, stages, total_milestones, completed_milestones, completion_percentage, current_stage_type, started_at, completed_at }