---
title: "Applicant vs Application Data"
slug: applicant-vs-application
section: Applicant Journey
status: stable
description: "The first-class boundary between who the person is and a single attempt at a program — and what each partner type sees through it."
last-updated: 2026-10-07
---

# Applicant vs Application Data

The first-class boundary between who the person is and a single attempt at a program — and what each partner type sees through it.

> **Field-level enforcement**
>
> This page documents the boundary as it stands today. The mechanism that keeps it true tomorrow — required field-level metadata (scope, core) on every Workflow Studio field, the step-level data scope dropdown that replaces per-field redaction, plus a baseline audit of biographicalSync — lives on the Field Classification Contract page.

> **One Person, Many Attempts**
>
> Applicant data describes the person and follows them across every program attempt. Application data describes a single attempt at one program in one season. The two live in separate tables (applicants and applications) and are exposed to partners through scoped, role-gated endpoints. This page is the canonical reference for the boundary.

## Two Concepts, One Person

Applicant Data

The identity record. Stable across program attempts. Created once per person per brand, then reused.

- &bull; First name, last name, email, nationality
- &bull; Brand scope and external ID (CRM / partner key)
- &bull; Lives in the applicants table
Application Data

One attempt at one program in one season. A new attempt produces a new row, even for the same person.

- &bull; programId, seasonId, status, submittedAt
- &bull; Workflow state, placement link, application-scoped documents
- &bull; `firstApplicantWorkAt` timestamp, for application-specific Portal work, never person-wide profile edits or registration
- &bull; Lives in the applications table
## Field-Level Rule of Thumb

> **Ask one question of every field**
>
> “Would this still be true if the program didn’t exist?” If yes &rarr; it is applicant data. Put it on applicants. If no &rarr; it is application data. Put it on applications.

A nationality is true regardless of which program the person applies to &rarr; applicant. A workflow status is meaningless without the program &rarr; application. A document like “passport” describes the person but is uploaded in the context of a program attempt; in the current schema it is application-scoped (see Documents below) and is not deduped across attempts.

## One Applicant, Many Applications

applicants

Maria Lopez

id: 4821 · brand: Camp · nationality: MX

&rarr; 
application #9012

Camp Counselor · Summer 2024 · completed

application #11457

Camp Counselor · Summer 2026 · in_review

Same person, two attempts, two application rows. Identity is not duplicated; workflow state is not shared.

## Where Each Field Lives

| Field | Lives On | Why |
| --- | --- | --- |
| field | | rationale |

_(row above is a template, repeated for each data row)_

## Documents Follow the Same Split

Two document concepts mirror the two data concepts:

application_documents

Documents collected for a specific application attempt. Today these are stored in the documents table with applicationId as a required FK. Re-applying does not carry these forward automatically.

my_documents (applicant-scoped)

Identity-bound documents the person owns regardless of program (passport scan, profile photo, language certificates). Surfaced in the Applicant Portal as the “My Documents” surface and intended to be reused across attempts. Schema gap: there is currently no applicant-scoped documents table — see the audit below.

**Profile photo writes.** `PATCH /api/applicants/:id` rejects `profileImageUrl` and `profileImageSourceUrl` with 400 VALIDATION_ERROR (`details.field` `profileImageUrl`), the same pattern as `email` and `hubspotContactId`: the photo fields are owned by the photo pipeline (portal photo replace, applicant creation with a photo URL, the Personal Info sync and the reason-coded identity-photo override), which fetches, size-checks and re-uploads the image to storage.

> **Why this split matters for partners**
>
> Hosts and recruiters see different document slices because the documents themselves are scoped differently. Application-scoped documents are visible only inside the application row they belong to (and only to the partner that owns or is placed against that application). Applicant-scoped documents would be visible across every attempt the partner is authorized to see — if/when that table exists.

## Partner Visibility Matrix

Per partner type, the matrices below list the applicant fields and application fields visible, the endpoints that serve them, and any visibility rules driven by step scope and the platform PII registry. Globus is the source of truth; the matrix is the contract each partner portal must implement against.

> **Now restated as a surface contract**
>
> These per-partner field tables are a snapshot of what fills each surface today. The authoritative enumeration of surfaces — recruiter.summary, recruiter.profile, host.talent_card, host.profile, staff.profile, and the workflow_tab_only fallback — and the rendering contract each portal must honour live on the Dynamic Field Surface Spec . Every Workflow Studio field carries a surfaces slot that decides which of these tables it lands in; new fields appear in the appropriate partner's view without a code change. The per-field tables below should therefore be read as “what currently fills each surface,” not as the complete contract.

### Employer / Host Visibility

Hosts only see applicants who are placed against one of their own job orders. They never see the global applicant pool, never see other hosts’ placements, and never see pre-placement workflow state. Required host role: admin, recruiter (hiring manager), finance, or readonly (the HostRole values in server/middleware/partnerAuth.ts; the read-only role cannot mutate).

| Field | Level | Host Sees | Endpoint |
| --- | --- | --- | --- |
| field | | visible | endpoint |

_(row above is a template, repeated for each data row)_

### Recruiter / Partner Visibility

Recruiters see applicants they created or are the source-of-record for, scoped by their active partner agreement (brand, programs, seasons). They never see applicants belonging to another partner, even within the same brand. Required role: partner_admin, partner_recruiter, or partner_readonly.

| Field | Level | Partner Sees | Endpoint |
| --- | --- | --- | --- |
| field | | visible | endpoint |

_(row above is a template, repeated for each data row)_

### Step Scope & Platform PII Registry

Visibility is decided in two places, neither of them per field. Together they replace what the previous design called “redaction.” See Field Classification Contract → Step Scope and Platform PII Registry for the canonical definitions.

s data on each portal request. There is no per-field redaction toggle.,
Platform PII registry (always-mask, hard-coded) / Passport, date of birth, SSN, background-check detail, and medical disclosures are masked on every non-staff surface regardless of step scope. The registry is owned by the platform team and not configurable per program.,
Host \u2192 placement.applicant.email / The step that captures the applicant
## Globus Boundary Audit

> **Audit posture**
>
> This audit walks the canonical Globus contract (schema &rarr; storage interface &rarr; partner-facing routes &rarr; RBAC) to confirm where the boundary holds and to flag concrete gaps. Gaps are reported here, not fixed — remediation is tracked separately.

### Schema

PASS — identity vs attempt are split at the table level

applicants holds identity fields such as firstName, lastName, email, nationality and shortCode. applications holds applicantId (FK), programId, seasonId and status. The split matches the rule of thumb above. (shared/models/applicant-lifecycle.ts.)

PASS — placements key off application, not applicant

placements.applicationId correctly references the per-attempt row, so re-applying produces a fresh placement chain rather than overloading the previous one.

GAP — no applicant-scoped documents table

documents.applicationId is .notNull(), so every document is tied to a single attempt. There is no applicant_documents / my_documents table backing the “My Documents” concept described above. Identity-bound documents (passport, profile photo) cannot be reused across re-applications without manual recopy. Reported, not fixed.

### Storage Interface

PASS — insert schemas honour the split

insertApplicantSchema and insertApplicationSchema are derived from the two tables independently (defined in `shared/models/applicant-lifecycle.ts`). Storage CRUD operations cannot conflate the two without bypassing the typed interface.

GAP — no `getApplicantWithApplications` helper

Partner-facing flows that need “person + every attempt I’m authorized to see” have to compose two queries by hand at the route layer, which is where boundary mistakes typically creep in (over-fetching applications outside the caller’s scope). A scoped storage helper that takes the caller’s partner-org-id / host-org-id and returns only authorized applications would centralize the filter. Reported, not fixed.

### Partner-Facing Routes & Naming

PASS — partner-facing reads are scoped

Every documented partner endpoint (partner, host) requires either X-Partner-Org-Id or X-Host-Org-Id + an active agreement. Bootstrap context resolves the caller’s scope before any applicant or application read is served.

RESOLVED — host document endpoints unified under /api/partner/host/documents

All host document endpoints are now documented under the canonical /api/partner/host/* prefix on the Host Document Center, matching host overview / activity-log endpoints. The legacy /api/host/documents/* mount is retained as a thin alias that 308-redirects to the canonical path with Sunset / Deprecation headers (RFC 8594) and will be removed once portal traffic on it reaches zero. New integrations must code against the canonical prefix.

GAP — no documented endpoint for application-scoped documents on the host side

Hosts can read their own org-scoped compliance documents but the contract does not yet expose application-scoped documents on placed participants (e.g. arrival paperwork). This is “planned” in the visibility matrix above and should be added to Host Portal Endpoints. Reported, not fixed.

### RBAC & Scope Filters

PASS — partner and host RBAC are separate from staff RBAC

Partner roles (partner_admin / recruiter / partner_readonly) and host roles (host_admin / host_hiring_manager / host_finance / host_readonly) are governed by dedicated middleware layers and cannot grant access to applicants outside the caller’s org-scoped slice. See RBAC & Governance.

GAP — visibility rules are policy, not yet enforced by the shared projector

The visibility rules listed above (host email visibility gated on the step's data scope of staff_and_host_after_placement; partner-visible documents driven by the step's audience) describe the target behaviour. They are enforced ad hoc per-route today rather than by the shared projectForAudience(record, audience) projector that reads step scope plus the platform PII registry. The projector is being built under Asana 1214212456520951; the registry under the new Platform PII Registry ticket in P1: Foundation Hardening. Reported, not fixed on this page.

### Reported Gaps (No Fixes)

| # | Gap | Layer | Severity |
| --- | --- | --- | --- |
| G1 | No applicant-scoped documents table; identity-bound docs cannot be reused across attempts. | | medium |
| G2 | No scoped storage helper for `applicant + authorized applications`; route-layer filtering can drift. | | medium |
| G3 | RESOLVED — host document endpoints unified under canonical `/api/partner/host/documents`; legacy `/api/host/documents` retained as a 308-redirect alias with Sunset/Deprecation headers. | | resolved |
| G4 | No documented endpoint for application-scoped documents on placed participants (host side). | | medium |
| G5 | Redaction is policy-by-route, not enforced by a shared projector keyed on caller + applicant + application. | | medium |

## Common Mistakes to Avoid
