---
title: "Globus Portal API Reference"
slug: globus-portal-api
section: Portals
status: stable
description: "The verified portal-facing surface of the Globus Hub (Alliance-Strategies/BackOfficePrototype), extracted from live route registrations on 2026-07-04."
last-updated: 2026-07-04
---

# Globus Portal API Reference

# Globus Hub — Portal-Facing API Reference

The verified portal-facing surface of the Globus Hub (Alliance-Strategies/BackOfficePrototype), extracted from live route registrations on 2026-07-04. A frozen machine-readable contract lives in the repo at server/versioning/portal-contract-snapshot.json (225 endpoints, generated 2026-07-01) — this page is the human/agent-readable companion.

Consumers: Applicant Portal endpoints , Host Portal endpoints , Integration Contract .

## Auth Mechanisms

| Mechanism | Credential | What it checks |
| --- | --- | --- |
| createIntegrationAuth(scopes) | x-api-key or Bearer | Tri-modal: n8n system key (scope check skipped) → portal session/magic-link token → scoped API key. The primary post-onboarding portal auth. |
| validateApiKey(scopes) | x-api-key | Hashed lookup in api_keys; rejects inactive/revoked/expired; requires * or all listed scopes. |
| portalAuth | Bearer / X-Portal-Token / ?token= | Signed portal-session JWT (stateless) or raw magic-link token (DB hash). Sets applicant context; ownership-scoped. |
| portalOrIntegrationAuth(scopes) | either | API key present → integration path + X-Applicant-User-Id; else portal session. Both normalise onto the same context. |
| validateApplicantUserId | X-Applicant-User-Id header | Identifies which applicant a list/read is for; paired with API-key auth. |
| partnerAuth / portalServiceAuth | x-api-key (+ org headers) | PartnerContext in direct, portal_proxy (X-Partner-Org-Id) or host_proxy (X-Host-Org-Id) mode; proxy scopes portal.partner_proxy / portal.host_proxy. |
| staff session + RBAC | session or scoped API key | isAuthenticatedOrApiKey + requirePermission — staff/admin gate; a few /api/portal/* paths use this and are NOT applicant-reachable. |

> **A portal client needs BOTH credentials**
>
> Most reads are on the API-key surface, but POST /api/portal/applications, workflow-snapshot, notifications, embassy-consulates, applicant-photos and recurring check-ins require a portal session token; documents accept either. A stateless BFF (PPPToo) holds the API key and covers session-gated needs via alternate API-key routes (e.g. /api/checkins/recurring/applicant/...) — see the flags below.

## Portal API (/api/portal)

### Session & Config

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| POST | /api/portal/session | Public | Redeem magic-link token for a short-lived signed session token |
| POST | /api/portal/session/refresh | Public (signature-only) | Exchange a still-valid session token for a fresh one |
| GET | /api/portal/runtime-config | Public | Non-secret runtime config (public PostHog key etc.) |
| GET | /api/portal/host-user/profile | X-Onboarding-Token | Host-user invite landing profile (non-consuming lookup) |

### Applicant, Applications, Programs

API-key surface (createIntegrationAuth("integrations.portal")); list/read routes for a specific applicant take X-Applicant-User-Id.

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | /api/portal/applicant/profile | api-key + X-Applicant-User-Id | Applicant's own profile + first application's program/season |
| GET | /api/portal/applicants | api-key | List portal applicants (limit/search) |
| GET | /api/portal/applicants/:userId | api-key | Applicant by portal user id (int or Keycloak UUID) |
| GET | /api/portal/applications | api-key + X-Applicant-User-Id | List the applicant's applications (enriched) |
| GET | /api/portal/applications/:appId | api-key | Single application detail |
| GET | /api/portal/applications/:appId/progress | api-key | Progress counts + current step/phase |
| GET | /api/portal/applications/:appId/step-progress | api-key | Per-step progress array (scope=program or library) |
| GET | /api/portal/applications/:appId/program-config | api-key | Phases + steps for the pinned workflow |
| GET | /api/portal/applications/:appId/available-flows | api-key | Renewal/extension availability |
| GET | /api/portal/applications/:appId/step-access | api-key | Per-step lock/access snapshot + applicant copy + doc-review badges |
| GET | /api/portal/applications/:appId/step-reviews | api-key | Step-review decisions (read mirror) |
| GET | /api/portal/applications/:appId/payments | api-key | Finance payment refs (read mirror) |
| GET | /api/portal/applications/:appId/journey-position | api-key | Journey-position snapshot (read mirror) |
| POST | /api/portal/applications | portalAuth (session) | Applicant self-service create renewal/extension |
| GET | /api/portal/programs | api-key | Program list (+ published workflowVersionId) |
| GET | /api/portal/programs/:programId/config | api-key | Program metadata + portal step ordering |
| GET | /api/portal/program-steps | api-key | Global step library (label/order only) |
| GET | /api/portal/step-library | api-key | Full step library WITH fieldsConfig |
| GET | /api/portal/step-library/:stepKey | api-key | Single step-library entry |
| GET | /api/portal/countries | api-key | ISO 3166 country reference list |
| GET | /api/portal/brands | api-key | Brand list (id/slug/name) |
| GET | /api/portal/branding | api-key | Branding/theming by ?slug |

### Steps, Documents, Workflow

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | /api/portal/steps/:stepKey/validation-schema | api-key | Server field-validation descriptor for a step |
| POST | /api/portal/steps/:stepKey/submit | api-key | Submit step form data |
| GET | /api/portal/workflow-snapshot | portalAuth (session) | Published workflow step graph (?applicationId or ?seasonId&applicationType) |
| GET | /api/portal/applications/:appId/documents | portal-or-integration | List documents for the application |
| GET | /api/portal/applications/:appId/documents-by-step | portal-or-integration | Documents grouped by step |
| GET | /api/portal/applications/:appId/required-documents | portal-or-integration | Required-document checklist |
| POST | /api/portal/applications/:appId/documents | portal-or-integration | Upload a document |
| GET | /api/portal/documents/:id/versions | portal-or-integration | Document version history |
| GET | /api/portal/documents/:id/download | portal-or-integration | Stream document bytes (ownership-scoped) |

### Notifications, Check-ins, References

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | /api/portal/notifications | portalAuth | List applicant notifications |
| GET | /api/portal/notifications/count | portalAuth | Unread count |
| PATCH | /api/portal/notifications/:id/read | portalAuth | Mark one notification read |
| POST | /api/portal/notifications/read-all | portalAuth | Mark all read |
| GET | /api/portal/checkins/recurring/applicant/:applicantId/season/:seasonId | portalAuth | Recurring check-ins for a season |
| GET | /api/portal/checkins/recurring/applicant/:applicantId/current-season | portalAuth | Recurring check-ins, current season |
| GET | /api/portal/embassy-consulates | portalAuth | Embassy/consulate reference list |
| GET | /api/portal/applicant-photos/:id | portalAuth | Serve applicant photo (ownership-scoped) |
| POST | /api/portal/travel-requests/:id/withdraw | portalServiceAuth (proxy scope) | Withdraw a pending travel request |
| GET | /api/portal/reference-submissions/lookup | api-key | Referee lookup of a reference submission |
| POST | /api/portal/reference-submissions/respond | api-key | Referee submits a reference response |
| GET | /api/portal/reference-templates | api-key (portal.read) | List active reference templates |
| GET | /api/portal/reference-templates/resolve | api-key (portal.read) | Resolve templates by programKey |
| GET | /api/portal/reference-templates/:idOrKey | api-key (portal.read) | Single reference template |

### Staff-Gated /api/portal Paths

These live under the /api/portal prefix but are staff/admin routes — not applicant-reachable. Don't wire portal clients to them.

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | /api/portal/check-in-templates/:programId | staff session/API key + dashboard.view | Active check-in template |
| POST | /api/portal/resolve-placeholder | staff + dashboard.view | Resolve template placeholder |
| POST/PATCH/DELETE | /api/portal/step-library(/:id) | staff + programs.manage | Step-library CRUD |
| POST | /api/portal/step-library/sync | staff + programs.manage | Sync step library |

## Integrations API (/api/integrations)

The n8n and portal-backend surface. Onboarding routes need no X-Applicant-User-Id (the applicant doesn't exist yet). Job-order/housing-lead ingestion (~13 routes) and NetSuite finance sync are registered here too but sit outside the applicant-portal scope.

| Method | Path | Scope | Purpose |
| --- | --- | --- | --- |
| POST | /api/integrations/onboarding/check-user | integrations.onboarding | Check whether an applicant already exists (by email) |
| POST | /api/integrations/onboarding/provision | integrations.onboarding | Provision applicant + application + invite token (idempotent on hubspot_contact_id + program) |
| POST | /api/integrations/hubspot/communication-reply | integrations.hubspot.inbound | Inbound HubSpot comms reply |
| GET/PUT | /api/integrations/applicant-portal/profile/by-id/:id (+/photo) | integrations.portal | Applicant profile read/update + photo by Hub id |
| GET | /api/integrations/applicant-portal/profile/by-email/:email | integrations.portal | Profile by email |
| GET | /api/integrations/applicant-portal/profile/by-portal-user/:portalUserId | integrations.portal | Profile by portal user id |
| POST | /api/integrations/applicant-portal/link-portal-user | integrations.portal | Link Keycloak/portal user to applicant |
| POST | /api/integrations/applicant-portal/events | integrations.portal | Inbound portal event |
| POST | /api/integrations/applicant-portal/files (+/base64) | integrations.portal | File upload (multipart or base64) |
| POST | /api/integrations/applicant-portal/ai/execute \| text-review \| text-enhance | integrations.portal | Portal AI surface |
| GET | /api/integrations/applicant-portal/applicants/:applicantId/available-flows | integrations.portal | Dashboard flows (renewal/extension/J2) |
| GET | /api/integrations/applicant-portal/applications/:applicationId/workflow-snapshot | integrations.portal (+rate limit) | Pinned workflow snapshot |
| POST | /api/integrations/portal/documents/receive | integrations.portal | Receive a portal-uploaded document |
| GET/POST | /api/integrations/events(/:id, /:id/ack) | integrations.events | n8n ops-event poll/claim/ack (Event Outbox consumer) |
| POST/GET | /api/integrations/comms/log, /api/integrations/notification-logs/:id(/deliveries) | integrations.comms | Comms/notification logging + delivery attempts |
| PUT | /api/integrations/applicants/:applicantId/finance-link | integrations.finance | Link applicant to finance system (NetSuite) |

## Partner / Host Surface (/api/partner)

Roughly 200 route registrations across 31 files, all gated by partnerAuth() (direct, partner-proxy or host-proxy mode; host routes additionally take X-Host-Org-Id / X-Host-User-Email). Domains: host org profile, contacts, addresses, arrivals, cultural activities, documents, housing (+leads), reservations, talent pool, job orders, offer details, operating agreements, users, notes, and partner applicants/references. The authoritative per-route list is the frozen snapshot server/versioning/portal-contract-snapshot.json (keys /api/partner/host/*).

## Documented But Not Registered

> **These routes appear in doc/registry files but have NO live handler**
>
> GET /api/portal/me/context (does not exist — BFFs must compose it), GET /api/portal/brands/:id (only the list route exists), /api/portal/applications/:appId/j2-dependents, /api/portal/users(/:id), /api/portal/file-proxy, /api/portal/monthly-check-ins/* and /api/portal/sevis-check-ins/* (real routes are /api/portal/checkins/recurring/*), /api/portal/notifications/logs|templates, POST /api/portal/applicant-photos, and /api/portal/reference-submissions/application/:applicationId. If an integration references one of these, it is coded against documentation, not the API.

## Implementation Flags

(1) Dead handler: registerPortalStepProgressRoutes defines a session-auth + ownership-check variant of GET .../step-progress but is never wired; the live handler is the API-key variant with UUID-as-integrity-check only. (2) AI routes: /api/openai/* is a staff surface (RBAC-gated) — the designated portal AI path is /api/integrations/applicant-portal/ai/*. (3) Auth split: see the callout above — session-gated vs API-key routes are inconsistent within the prefix; new portal routes should pick one surface deliberately.
