> ## Documentation Index
> Fetch the complete documentation index at: https://f4c7a9e2d8b1-docs.tenzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Job editor bootstrap

> Aggregator for org-scoped job-editor template and settings data

<Warning>
  **Restricted Access**: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.
</Warning>

<Info>
  **Status: implemented (2026-09-01).** The aggregator endpoint, its generated client, and the job editor's `useOrgTemplateData` hook are wired end to end. Editor mount makes one aggregator request for Tier A; the Settings step makes one for Tier B.
</Info>

## Goal

Give the job editor one authenticated read for the org-scoped lists that `useOrgTemplateData` hydrates on open, instead of a fan-out of per-resource GETs:

```
GET /org/{org_id}/editor-bootstrap?tier=a|b
```

No Cosmos schema change. Pure read aggregation over the existing per-resource list handlers. Per-template CRUD stays on the existing feature routers, so mutations and client cache updates keep today's shapes.

## Gate

This endpoint shipped on the editor-composition structural brief, not on measured RUM data. The earlier version of this page gated implementation on a Datadog RUM or Chrome performance trace showing the Tier A / Tier B fan-out was the editor-open bottleneck; that measurement was not taken. Treat the aggregator as a composition simplification whose latency win is expected but unverified until editor-open timings are compared before and after.

The Configure-tab **document** snapshot (`jobDocumentStore.lastSaved`) is a separate concern. See [Job editor document](/internal/job-editor-document).

## Contract

### Route

`GET /org/{org_id}/editor-bootstrap`, served by `server/org_templates/editor_bootstrap_api.py` and registered under the `org-templates` tag, so the generated client function lives in the org-templates client module.

### Auth

* Stytch auth is required; the org is resolved from `AuthContext.org_id`.
* The path `org_id` must equal `auth.org_id`. Any mismatch is a 403 before any read is started. Some of the underlying per-list handlers take only a path `org_id` without that check; the aggregator does not inherit that gap.

### Query

| Param  | Values             | Behavior                                  |
| ------ | ------------------ | ----------------------------------------- |
| `tier` | `a` (default), `b` | Which sections of the payload to populate |

Intended use: `tier=a` on editor mount; `tier=b` when the Settings step activates and Tier A is already cached client-side.

### Response

`EditorBootstrapResponse` is flat. It carries the requested `tier` plus one field per section, and every section reuses the item model of the per-resource list endpoint it mirrors, so the frontend maps the same fields it maps today. There is no second template schema.

| Section                                   | Tier | Mirrors                                             |
| ----------------------------------------- | ---- | --------------------------------------------------- |
| `background_info_templates`               | A    | `GET /org/{org_id}/background_info_templates`       |
| `thank_you_email_templates`               | A    | `GET /org/{org_id}/thank_you_email_templates`       |
| `custom_booking_outro_templates`          | A    | `GET /org/{org_id}/custom_booking_outro_templates`  |
| `candidate_question_extraction_templates` | B    | `GET /candidate_question_extraction_templates`      |
| `suggested_follow_up_templates`           | B    | `GET /org/{org_id}/suggested_follow_up_templates`   |
| `redaction_templates`                     | B    | `GET /redaction_templates`                          |
| `summary_templates`                       | B    | `GET /summary_templates`                            |
| `flagged_event_templates`                 | B    | `GET /org/{org_id}/flagged_event_templates`         |
| `rejection_email_templates`               | B    | `GET /org/{org_id}/rejection_email_templates`       |
| `resume_feedback_email_templates`         | B    | `GET /org/{org_id}/resume_feedback_email_templates` |
| `work_accommodation_review_templates`     | B    | `GET /work_accommodation_review_templates`          |

The recruiting email and the org users map are not sections and stay on their own requests: the recruiting email is a scalar whose legitimate `null` was indistinguishable from a failed section, and the users map is an app-wide SWR key shared with non-editor pages.

A section is `null` in exactly two cases: the requested tier does not include it, or its read failed. A section that was read and found empty is an empty list, so the client can tell "not fetched" from "fetched empty". A client that requested a section and received `null` should treat that section as unavailable and may fall back to the per-list endpoint for it.

### Section isolation

Sections are read concurrently and each read is isolated: one section's failure is logged and returned as `null` while the other sections still populate. Each section also runs under its own timeout (`SECTION_TIMEOUT_SECONDS`, 10 seconds), so a section that is still running at that point is returned as `null` instead of holding the others. The response is 200 whenever the caller is allowed to read the organization, and one info log line records the org, tier, elapsed time and how many of the requested sections populated.

### Domain users are deliberately left out

`useOrgTemplateData` also loads domain users (`GET /org/{org_id}/get-domain-users`) when a calendar is connected. That handler makes a live Google or Microsoft directory call, and its frontend gate (`calendarConnected`) resolves after the editor mounts, so folding it into a mount-time read would either block Tier A on an external directory round trip or fire it for orgs that will never use it. It stays a separate SWR key.

## Composition

* The handler is orchestration only. Each section awaits the existing per-resource route handler with an explicit auth context and org id, so default-template seeding, sort order and org re-filters are inherited from those handlers rather than re-implemented.
* Which sections a tier populates is a pure function of the tier, kept separate from the I/O handler.
* No new SQL tables, no new DAO methods.

## Frontend

`useOrgTemplateData` keeps every per-list SWR key as the read path and `makeListSetter` as the write path, so template create, edit, and delete stay local-first and the admin and create-page consumers of this module see no change. The aggregator only changes how each reader inside the hook obtains its list:

* One aggregator request per tier: the Tier A readers share one `tier=a` request on editor mount, and the Tier B readers share one `tier=b` request when the Settings step activates. The frontend never asks for both tiers in one request.
* The per-list SWR keys stay the read path; each reader takes its own section from the shared response.
* Per-reader fallback: when a reader's section is `null`, or the aggregator request itself failed, that reader fetches its own per-list endpoint. An error toast now means both the aggregator section and the per-list endpoint failed for that list.
* `enableSettingsTier()` remains the Tier B gate, so enabling the Settings step does not refetch Tier A.
* Domain users keep their own request, as described above.

The request sharing and fallback mechanism lives in `ui/src/pages/jobs/editor/services/editorBootstrapLoader.ts`.

## Adjacent Settings calls (not folded in)

These fire from `CallerSettingsStep` around the same time as Tier B but are not in `useOrgTemplateData`. They remain separate SWR keys unless a metrics case appears:

* `POST /organization` (org job-board email / timezone)
* `POST /organization/brands`
* `GET /org/{orgId}/whatsapp-senders/has-approved`

Out of scope for this aggregator: job-scoped script fetch, ATS job schema, organization variable names.

## Non-goals

* Renaming backend "campaign" to "job"
* Changing Cosmos document shapes
* Aggregating job or script payloads
* Auto-merge of adjacent Settings calls without a metrics case
