Careers spaces

Embed
Preview of the card
<a href="https://forumulate.com/guides/careers-spaces"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/ba969969-2a89-44e4-a39f-5a77ed146daf/social-card.svg?v=3e7ae33eec8b" alt="Careers spaces" width="600" height="315"></a>

The content model

A role is a page in a CAREERS space. Its sections (summary, responsibilities, requirements, nice-to-have, benefits, process) are what a candidate reads; its declared fields are what a candidate filters on and what Google Jobs indexes.

Roles and pages

Not every page in a careers space is a role. "How we hire" or "Working here" is a page. Set metadata.kind with PATCH …/pages/{id}/metadata ({"kind": "page"}), or use Kind of page in the editor's details rail.

kind

On the board

Apply form

Markup

Role sections

role (default)

yes

yes

JobPosting

yes

page

no

no

WebPage

no

A page with no kind is a role, so nothing you already have changes. Any other value is refused with a 422. The kinds are listed under pageKinds at GET /space-types/CAREERS.

Closing a role

closes (a date) closes the role from the following day, counted in UTC. A closed role stays readable at its address and says that it has closed. It also changes in five ways:

  • It is removed from the board and from every feed.

  • It no longer has an apply form, and the apply endpoint answers 410 gone for it.

  • It is marked noindex.

  • It is removed from the sitemap.

  • Its JobPosting markup is removed.

Country

country (a code like GB, or a country name) becomes addressCountry on an on-site or hybrid role's jobLocation. We never parse it out of the free-text location.

A role in several places lists them in location separated by semicolons (London; Berlin). The board prints "London · Berlin", the location filter offers each city, and the JobPosting gets one Place per city. Give country once for all of them (US), or once per place in the same order (GB; DE).

Pay transparency

payNote is a sentence shown under the salary on the role — what the band covers and how an offer is placed in it, which New York, Colorado, California and the EU pay-transparency rules ask for.

Employment type

The full set, as declared — Full time, Part time, Contract, Temporary, Internship, Volunteer, Per diem, Other. (An older version of this guide listed only the first four; the other four have always worked.) Each maps to a valid Google employmentType, so nothing is guessed from prose.

Where the work happens

remote is On-site / Hybrid / Remote, and it alone decides what the markup claims:

remote

jobLocation

jobLocationType

On-site

the location, when it names a place

—

Hybrid

the location, when it names a place

—

Remote

—

TELECOMMUTE

A hybrid role is a place you go some of the time, so it is not marked TELECOMMUTE — that value means 100% remote, and a role marked with it is indexed as a job anybody anywhere can do.

Put a remoteness word in location ("Remote (EU)", "Anywhere") and we claim no place at all rather than assert it as a city, which is what gets a listing rejected.

Who may apply from

Google wants an applicant-location requirement on a fully remote role, and only you know the answer, so it is a field: applicantLocations, countries comma-separated ("Germany, Austria"). Leave it blank and the property is omitted — we never claim "Worldwide", which is not a country.

Salary

salary is a structured band and its keys are exactly four:

{ "min": 80000, "max": 100000, "currency": "EUR", "period": "year" }

period is one of hour, day, week, month, year, and it becomes unitText in the markup. Any other key in the object is refused with 422 naming the shape — a band posted with unit instead of period used to be accepted and then published with no period on it at all.

compensation is the free-text band readers see in prose; it is the fallback for the markup when there is no structured band.

Import roles from your ATS

If your roles already live in Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Personio, Recruitee or Teamtailor, connect the board instead of retyping them. For every provider but Teamtailor we read the same public job board your candidates see, with no API key (Teamtailor needs one), and make one role page per posting. Apply on every imported role goes back to the posting in your ATS, so candidates land in the pipeline you already work.

# Connect (and say who the hourly sync publishes as: you)
PUT    /orgs/{org}/spaces/{space}/careers/ats
  { "provider": "lever", "board": "acme", "syncEnabled": true }

# Read the board now — 202 once the board answered; pages are written in the background
POST   /orgs/{org}/spaces/{space}/careers/ats/sync

# How the last pass went: lastRunAt, lastStatus, lastCounts, lastError
GET    /orgs/{org}/spaces/{space}/careers/ats

# Disconnect — imported roles stay as ordinary pages and stop updating
DELETE /orgs/{org}/spaces/{space}/careers/ats

board is the name in your board's address: the acme in boards.greenhouse.io/acme, jobs.lever.co/acme or jobs.ashbyhq.com/acme. In the studio it is Settings → Import from an ATS.

What a sync does, per posting:

  • New — a published role page. Team, location(s), employment type, where the work happens, the salary band and Apply URL are filled from the posting; the Reference ID too when the ATS publishes a requisition id.

  • Changed — the page's text and those fields are rewritten and republished. The ATS is the source of truth for what it publishes, so edit imported roles there.

  • Gone from the board — the role is closed (its closes date set to yesterday), not deleted: the address still answers, with "closed", and the role leaves the board, the sitemap and Google Jobs.

  • Back on the board — reopened.

  • Deleted here — left alone. Deleting an imported role is how you keep it off your site; the sync will not bring it back.

Pages you write yourself ("How we hire") are never touched. Images, video and embeds in a posting's description are dropped — a reader page loads nothing from another company's servers. With syncEnabled the board is re-read every hour; changing the provider or board starts over (the old roles stay, unsynced).

Applications

# Public: a candidate applies
POST /public/orgs/{org}/spaces/{space}/careers/apply
  { "pageId": "…", "name": "…", "email": "…", "coverLetter": "…", "links": "…" }

# Admin: the inbox
GET   /orgs/{org}/spaces/{space}/careers/applications?status=reviewing
PATCH /orgs/{org}/spaces/{space}/careers/applications/{id}  { "status": "interviewing" }

Who is told

Every application sends two emails, both on your own mail provider:

  • the candidate gets "We received your application for …" (the Application received email template). Turn it off with confirmation: false;

  • the hiring team gets New application — at notifyEmails, else your support address, else nobody (the inbox is still the record).

PATCH /orgs/{org}/spaces/{space}/careers/form
  { "notifyEmails": ["hiring@acme.com"], "confirmation": true }

Both are in the apply form's settings in the studio.

The pipeline stages

The stages are yours. A space that never sets them has these six:

Key

Name

Kind

new

New

open

reviewing

Reviewing

open

interviewing

Interviewing

open

offer

Offer

open

hired

Hired

hired

rejected

Rejected

rejected

Change them in the studio (Settings → Hiring pipeline) or over the API:

GET /orgs/{org}/spaces/{space}/careers/pipeline
PUT /orgs/{org}/spaces/{space}/careers/pipeline
  { "stages": [
      { "key": "new", "label": "Applied" },
      { "key": "screen", "label": "Phone screen" },
      { "key": "take-home", "label": "Take-home task" },
      { "key": "onsite", "label": "Onsite" },
      { "key": "hired", "label": "Offer accepted", "kind": "hired" },
      { "key": "rejected", "label": "Not this time", "kind": "rejected" } ],
    "ratingScale": "stars",
    "moveTo": { "reviewing": "screen", "interviewing": "onsite", "offer": "onsite" } }
  • The list is in order. A new application lands in the first stage, which must be open. At least one stage must be hired or rejected.

  • The key is what an application stores; the label is what people read. Renaming a stage keeps its key, so nothing moves. status on an application and ?status= on the inbox take a key; anything this space does not have is refused with a 422 that lists the keys it does.

  • The kind is what the platform acts on. The retention sweep keeps every application in a hired stage, whatever you call it.

  • Removing a stage that holds applications needs moveTo — where they go. They move in the same write, so no candidate is ever in a stage nobody can see. Without it the write is refused, naming the stage and how many.

Notes and ratings

Each application has a private notes thread and one rating per reviewer. Notes are never mailed and never in a webhook — a reply is what the candidate reads, a note is what the team thinks. Only a note's author can delete it.

GET    /orgs/{org}/spaces/{space}/careers/applications/{id}/notes
POST   /orgs/{org}/spaces/{space}/careers/applications/{id}/notes   { "body": "…" }
DELETE /orgs/{org}/spaces/{space}/careers/applications/{id}/notes/{noteId}
GET    /orgs/{org}/spaces/{space}/careers/applications/{id}/ratings
PUT    /orgs/{org}/spaces/{space}/careers/applications/{id}/rating  { "score": 4 }

A score is 1–5; null withdraws yours. With ratingScale: "thumbs" the studio shows thumbs up (stored as 5) and down (1), so switching scale loses nothing. Every inbox row carries rating: { average, count, mine } and notes (how many).

The inbox has a List and a Board view — a column per stage; drag a card or use its stage menu. Export CSV (GET …/careers/applications.csv) adds stage_name, rating, ratings and notes (a count — the notes' text stays in the product).

What readers see

  • The open roles at /jobs/{org}/{space} (or your own address), with location and employment type on each. Four arrangements (the space's preset): list, departments (grouped by team, with a count beside each), table (a column each for team, location and type) and cards. "12 open roles" sits over the list (frontDoor.showCounts).

  • Once the board has 8 open roles, a search box and a filter bar (team, location, where the work happens, employment type, level). Every search and filter is an address you can share: ?q=design&f_remote=Remote. Set theme.careers.list.filtersFrom to change the threshold (0 = always). The filters sit over the list; theme.careers.list.filterPlacement moves them to a column on the left (rail-left, the hosted-board look) or right (rail-right), or into one line of menus (dropdowns). On a phone a rail becomes the bar over the list.

  • Your own words for the board's fixed phrases: frontDoor.emptyText (no open roles), frontDoor.rowCta (the row's "Learn more", with theme.careers.list.rowAction: "text") and frontDoor.allTeamsLabel.

  • The rest of the board's look — layout, columns, density, dividers, hover, facts as chips, page size — is theme.careers.list; see Theming.

  • An ItemList of the open roles on the board itself, for search engines.

  • A role at its own address, with an apply form — or a link to applyUrl. Under its title: its facts as chips, an Apply now button and copy link / share (frontDoor.showShare); an Apply bar stays at the bottom of the screen until the reader reaches the form. Three layouts, the space's piece layout (PUT …/preset {"piecePreset": "…"}): single (the form under the role), split (a sticky column with the facts and Apply) and tabs (Overview / Application). Under the form: your equal-opportunity statement and accommodations contact (frontDoor.eeoStatement, frontDoor.accommodations — written once for the space) and "More roles on <team>". Every piece is a theme key (theme.careers.role, facts, item); see Theming.

  • A social card per role: "We're hiring", the title, team, place, type and pay.

  • JobPosting structured data built from the fields above, on open roles only. datePosted is the day the role was first published, so editing a role does not make it look new again. directApply is true when candidates apply through the form on the page. identifier is the page id. The hiring organization includes your logo and social profiles when you have set them.

Job alerts and feeds

Beside the board's title, a "Get job alerts" control offers every way to follow the board (frontDoor.subscribeStyle, unset button):

  • The talent community — a candidate leaves their email (double opt-in) and gets one mail each time a role opens. On a team's own address (?team=Design) the sign-up is for that team's roles only. A role is announced once, the first time it is published as an open role: edits, republishing, and closing and reopening send nothing more, and pages beside the roles, closed roles and roles hidden from the board are never announced. Roles that were already live before job alerts existed are not announced. The mail is the careers.role-opened email template, counted against your plan's email quota. Turn it off with frontDoor.showSubscribe: false (the subscribe route then answers 404).

  • The roles as RSS, Atom and JSON Feed — rss.xml, atom.xml and feed.json beside the board (/careers/rss.xml on your own address), newest role first, dated from each role's first publish. ?tag=<team> narrows any of them to one team. Turn them off with frontDoor.showRss: false.

A front page can also carry the sign-up as a band of its own: a spaceBand with band: "subscribe".

Google for Jobs: instant updates

Google lists a new role when it next crawls your board, which can take days, and keeps listing a closed one until it notices. With your own Google service account connected, a role is announced the moment it is published or edited (URL_UPDATED) and withdrawn the moment it closes, becomes a plain page, is unpublished, made private or deleted (URL_DELETED). A role that closes by its closes date is withdrawn by a daily sweep, once. A renamed role retires its old address.

  1. In Google Cloud, create a service account, enable the Web Search Indexing API, and download a JSON key.

  2. In Search Console, add the service account's address as an Owner of the site your board is served on (your own domain, or ours).

  3. In the studio, open Settings → Google indexing, paste the key, and press Test connection. Send all roles now announces every published role (up to 200, Google's default daily quota).

PUT  /orgs/{orgId}/google-indexing                         {"serviceAccount": "<key JSON>", "enabled": true}
GET  /orgs/{orgId}/spaces/{spaceId}/careers/indexing       the key's account + what Google was told per role
POST /orgs/{orgId}/spaces/{spaceId}/careers/indexing/test
POST /orgs/{orgId}/spaces/{spaceId}/careers/indexing/submit

The key belongs to the organization (org.manage_integrations) and is stored encrypted; nothing ever returns it. Without one, nothing is sent.

Front page

A careers space's front page is a composed document. Until you edit it, it is generated: the intro band, then the roles band. A careers feed sorts by latest and filters by section (a department); its cards can show title, location, employment and department — "We're hiring" on another space's front page is one spaceFeed pointed here.

More fields on a role

Field

Type

What it does

featured

boolean

Pinned to the top of the board, with a "Featured" chip, when theme.careers.list.featuredFirst is on.

education

text

educationRequirements in the JobPosting.

workHours

text

workHours in the JobPosting.

startDate

date

jobStartDate in the JobPosting.

hiringManager

text

Who the candidate will work for: the role rail's people slot.

internal

boolean

An internal-only role (below).

internalGroups

text

Group slugs, comma separated, that may see an internal role. Empty: every member.

A role with no date of its own is dated from its first publish, and that date is used in the "Posted" line, the feeds and the markup.

Internal-only roles

A role with internal: true is for the people already in your organization. Only your organization's members can read it, apply to it and see it on the board. With internalGroups, only members of those groups can, including readers who sign in through the space's reader sign-in with one of those groups in their token. Everyone else gets the same 404 an absent page gets, on the role page and on its apply form and apply call alike, so nobody can confirm that the role exists. An internal role is never in a feed, the job-board XML, the widget, the sitemap, search or the JobPosting markup. Pages that show it are never cached for other readers.

How the board and roles look

All of this is theme.careers, set with the space theme (PATCH /orgs/{orgId}/spaces/{spaceId}/theme). A misspelt key is a 422 that names its path. The layering is: the look's patch, then your space theme, then a role's own customization.

Looks are patches

theme.careers.look picks a starting point. A look is a patch of independent keys over the default layer, so every key works under every look and your own keys always win.

Look

What it sets

default (unset)

list.rowAction: arrow, list.hover: tint, list.filtersFrom: 8; role.topApply, role.stickyApply, role.moreRoles: 4; facts.look: chips (card when the role layout is split)

paper

Nothing: the plain list and the form at the foot.

board

The default's keys, plus a compact list grouped under each team as an eyebrow with counts, filters in a rail-left from 4 roles, outlined chips, ruled rows, relative posted dates; role.layout: split; facts.look: card

cards

The default's keys, plus cards two to a row with pill chips, a lift hover, a raised card (radius 12, pad 20), lg semibold titles, relative posted dates; a raised apply card with a full-width submit

minimal

The default's keys, plus ruled rows with an underline hover, "Learn more" text, regular-weight titles; no sticky Apply and no more roles; facts.look: list; a flat, narrow apply form

bold

The default's keys, plus tiles three to a row with xl bold display titles, outlined cards (radius 16, pad 24), upper-case eyebrow chips, featured roles first; role.layout: split with a brand Apply bar on phones only and pill tabs; labels above, a full-width submit at the start

Because each look is just keys, you can combine parts of different looks. Some examples:

{ "careers": { "look": "board", "list": { "layout": "cards", "columns": 2 } } }

Board's team grouping and filter rail, with the roles drawn as cards.

{ "careers": { "look": "minimal", "list": { "featuredFirst": true, "posted": "date" },
               "role": { "layout": "tabs", "tabs": { "style": "segmented" } } } }

Minimal's hairline list, plus featured roles first and posted dates, with the role split into Overview and Application tabs.

{ "careers": { "look": "bold", "role": { "layout": "single", "stickyApplyOn": "all" } } }

Bold's tiles on the board, with the role in one column and the Apply bar on every screen.

The board: theme.careers.list

On top of the keys described above (layout, columns, density, divider, hover, chips, rowAction, filtersFrom, filterPlacement, pageSize):

Key

Default

What it does

layout

the preset's

rows, compact, cards, table, grid, tiles, timeline or accordion. The board draws every one, and every list key applies under each.

sort

manual

manual (the space's tree), newest, title or team.

featuredFirst

off

Roles with featured go first, with a "Featured" chip.

posted

none

The day a role went up, on its row: relative ("3 days ago") or date.

title

md, medium, body face

A row title's size (sm–xl), weight (regular–bold) and font (body, display, mono).

card

—

The surface of a card, tile or accordion row: surface, radius, pad, shadow.

groupGap

the heading's margin

Space between team groups, in px (0–96).

phoneLayout

rows for a table

Under 640px: keep the layout (same) or fall back to rows.

phoneFilters

bar

Under 1024px the filters stay a bar, or fold into one "Filters" drawer.

teamPages

board

A team's address (?team=Design): board is a view of the board, canonical to it; own makes it a department page in its own right, titled with the team and its own canonical.

The board publishes an ItemList of its open roles.

Team pages

Every named collection in the space is a team, with its own page at /jobs/{org}/{space}/c/{team} (/careers/c/{team} on your own address). The page shows the collection's name and description over its open roles, drawn by the same board with every theme.careers.list key.

A role: theme.careers.role, item, facts

Key

Default

What it does

role.layout

the space's piece layout, else single

single, split or tabs.

role.topApply

off (default look: on)

An Apply button beside the facts.

role.stickyApply

off (default look: on)

The Apply bar at the bottom of the screen until the reader reaches the form.

role.stickyApplyOn

all

Show that bar on all screens or only on a phone.

role.stickyBar

card

The bar's surface: plain, card or brand.

role.tabs

underline

The tabs layout's style: underline, pill or segmented.

role.moreRoles

0 (default look: 4)

"More roles on team" under the form (0–12).

role.closed

facts

A closed role's notice: a banner naming the day it closed, with more roles under it; only the facts; or none. redirect: true sends readers to the board instead.

role.print

the role, its facts and the statement

Whether a printed role keeps the apply form and the equal-opportunity statement. The sticky bar and tabs never print.

item.rail

the split column

A side column on any layout: layout (side or stacked), items, sticky (on), align (start / end).

item.actions

copy link and share

The role page's actions and their style.

facts.placement

head (rail under split)

Where the facts stand.

item.rail.items is limited to what a role page has a slot for: facts, cta, actions, date, back, share, subscribe, toc, people (the hiringManager field) and tags. The space's benefits (frontDoor.benefits, one per line) are shown under every role, beside the equal-opportunity statement.

The apply form's look: theme.careers.apply

Key

Default

What it does

card

the outlined card

The box around the form.

width

full

narrow (a readable 36rem) or full.

labelPosition

—

above, beside or inside.

columns

2

Short fields two to a row from 640px, or 1.

submitAlign

start

start, center or end.

fullWidthSubmit

off

The submit button as wide as the form.

heading

show

The "Apply for role" heading.

One role's own look

A role can override theme.careers (and theme.docs) for itself:

PATCH /orgs/{org}/spaces/{space}/pages/{page}/metadata
  { "theme": { "careers": { "role": { "layout": "split" }, "apply": { "width": "narrow" } } } }

Another type's namespace is refused with a 422 that names it (customization.theme.forum) and the namespaces a role can take. null clears a key. In the studio this is Page look & sharing in the role's details.

The front page's careers bands

Besides intro and roles, a careers front page can place teams (team cards with their open counts), locations, featured and benefits. Each draws nothing without data. With general applications on (below), a "Don't see the right role?" band sits under the board.

New frontDoor settings: showJobFeed (on: the job-board XML feeds) and benefits (what every role comes with, one per line).

The apply form, in depth

All of these are in PATCH /orgs/{org}/spaces/{space}/careers/form, and in the studio's Apply form section.

Setting

Default

What it does

submitLabel

"Submit application"

The button's words.

successTitle

the reader's own words

The thank-you's heading; successMessage is its body.

nextSteps

none

Up to four { label, url } links under the thank-you.

translations

none

The form's words per reader locale: each question's label, help and placeholder by its name, the button and the thank-you.

questionBank

none

Questions written once; roleBank[pageId] names the ones a role asks, after the space's and before the role's own.

screening

none

Rules by question name (below).

duplicateDays

90

A second application from the same address to the same role within this many days is marked "possible duplicate". 0 = never.

autofill

on

After a CV is attached, fill the name, email, phone and link boxes the candidate left empty from what it says (PDF or plain text).

general

off

{ enabled, title, intro }: take general applications at /jobs/{org}/{space}/apply, with a band under the board.

roleNotify

none

Per role (by page id), up to 10 addresses also told about that role's applications, beside notifyEmails.

indeedApply

none

{ apiToken, apiSecret } for Apply with Indeed (below).

The built-in labels speak in one voice and follow the reader's language. A candidate's answers are kept as a draft in their own browser, per role, so an accidental reload loses nothing. The self-identification answers, the consent tick and attached files are never kept in that draft.

Screening questions

{ "screening": {
    "visa_country": { "showIf": { "field": "needs_visa", "equals": "yes" } },
    "right_to_work": { "knockout": { "answers": ["no"], "action": "reject" } },
    "years": { "knockout": { "answers": ["0-1"], "action": "flag" } } } }
  • showIf asks a question only when an earlier one was answered equals (a choice's value, or yes for a ticked checkbox).

  • knockout acts on an answer in answers: reject files the application straight into the pipeline's rejected stage, and flag marks it for a reviewer. It is judged on the server, and the answers that knock out are never sent to the candidate's browser.

Up to 40 rules.

Hiring tools

These are in PUT /orgs/{org}/spaces/{space}/careers/pipeline beside the stages, and in the studio's Hiring pipeline section.

Setting

What it does

templates

Up to 30 reusable emails { key, name, body } for the reply box and bulk actions. {{name}}, {{role}} and {{booking}} are filled when sent.

blindReview

Hides a candidate's name, address, links and CV in the inbox, the board view and the CSV until the application reaches revealAt (a stage key; unset = the first stage after the entry stage).

scorecard

Up to 10 attributes { key, label }. Each reviewer rates them 1–5 beside the overall score, and the inbox shows the mean of each.

bookingUrls

A self-scheduling link (Calendly, Cal.com) per stage key, offered in the reply box as {{booking}}.

# Move many at once, and/or send each a template
POST /orgs/{org}/spaces/{space}/careers/applications/bulk
  { "ids": ["…", "…"], "status": "rejected", "template": "not-this-time" }

# The hiring report: funnel per stage, sources (referral, campaign, referring site), time to hire, per role
GET  /orgs/{org}/spaces/{space}/careers/report

The report is also the studio's Hiring report panel. A link to a role or the board with ?ref=<code> records <code> as the candidate's referral source.

Offers and the candidate's status page

PUT  /orgs/{org}/spaces/{space}/careers/applications/{id}/offer
  { "title": "Senior Designer", "compensation": "€90,000", "startDate": "2027-01-04", "letter": "…" }
POST /orgs/{org}/spaces/{space}/careers/applications/{id}/offer/approve
POST /orgs/{org}/spaces/{space}/careers/applications/{id}/offer/send
POST /orgs/{org}/spaces/{space}/careers/applications/{id}/offer/withdraw

An offer is drafted, then approved by a member other than its author (the author's own approval is refused with a 403), then sent. Sending it mails the candidate a link to their status page, /jobs/{org}/{space}/applications/{token}, where they read the letter and accept or decline. Before any offer, the status page tells them where their application stands in plain words (received, in-review, closed or hired). It never shows your internal stage names. POST …/applications/{id}/status-link issues a fresh link and retires the old one.

Two webhooks follow the pipeline: application.stage_changed and application.offer_answered.

Channels

The job-board feeds

  • /jobs/{org}/{space}/jobs.xml (/careers/jobs.xml on your own address): the roles in Indeed's job-feed XML, also read by Glassdoor and ZipRecruiter.

  • /jobs/{org}/{space}/linkedin.xml: the same roles in LinkedIn's Job Wrapping XML.

Both list only the board's open, public roles, and both are on by default (frontDoor.showJobFeed: false turns them off). Give the address to the job site. Nothing is pushed.

The job widget

<script src="https://acme.com/careers/widget.js" data-team="Design"></script>

The script writes one frame after its own tag: the board with ?embed=1, narrowed by data-team and any data-f-<field> filters, and sized to its content. Nothing third-party loads. theme.careers.embed controls what the smaller box shows:

Key

Default

What it does

layout

the board's layout

rows, compact, cards or table.

max

10

At most this many roles, then "All N open roles".

showFilters

off

The search box and filters inside the widget.

showTitle

off

The board's name over the list.

Apply with Indeed

With an Indeed Apply token and secret saved, every role draws Indeed's button, so a candidate can apply with their Indeed profile. Indeed posts the application back to us, signed with your secret. The application goes through the same door as the form's, so screening, duplicates, the pipeline, mail and webhooks all apply, and an attached CV (up to 5 MB) is kept. A post-back without a valid signature is refused.

More ATS boards

Beyond Greenhouse, Lever and Ashby, the import reads Workable, SmartRecruiters, Personio and Recruitee without a key. For those four, board is your subdomain: the acme in acme.recruitee.com. Teamtailor has no public feed, so it needs your Teamtailor API key:

PUT /orgs/{org}/spaces/{space}/careers/ats
  { "provider": "teamtailor", "board": "acme", "apiKey": "<key>", "syncEnabled": true }

The key is stored encrypted and never returned (hasApiKey says one is held). Leaving out apiKey keeps the stored key, and "" clears it. Sync passes fire careers.ats.* webhooks. Moving from another tool is covered in Importers.

Outlets

Theme components and extensions can add markup in these slots on the careers surface:

Outlet

Pages

Context

Where

careers-above-list

board

—

Above the open roles.

careers-below-list

board, team page

—

Below the open roles.

careers-row-after

board, team page

role.title role.slug

Under every role row (repeats).

careers-role-above-body

role

the same

Under the title and facts, above the description.

careers-role-aside

role

the same

In the side column beside the description.

careers-role-after-body

role

the same

After the description, above the form.

careers-apply-before

role

the same

Directly above the form.

careers-apply-after

role

the same

Directly below the form.

careers-apply-sent

role

the same

Wraps the "application sent" message; replace draws your own.

In the studio

A careers space's studio has these sections: Apply form (questions, the question bank, screening, the form's words and translations, the hiring team per role, CV autofill, general applications, Apply with Indeed), Import from an ATS, Google indexing, Hiring pipeline (stages, email templates, blind review, scorecard, scheduling links), Hiring report, Applications (list and board, bulk move and reply, offers) and Design (every theme.careers key).

What you need to set up

Feature

What it needs

Until then

Apply with Indeed

An Indeed Apply partner account. Paste its API token and secret in Apply form → Apply with Indeed. The secret is stored encrypted and never shown again.

No button. Post-backs answer 404.

Teamtailor import

Your Teamtailor API key, entered in Import from an ATS.

The sync refuses with a sentence naming the missing key.

LinkedIn

LinkedIn has to enable Job Wrapping for your company and be given the linkedin.xml address. Apply with LinkedIn needs LinkedIn partner approval and is not built in.

The feed is served, and nothing reads it.

Google for Jobs instant updates

A Google service account key (above).

Google finds roles when it next crawls.

Application and offer emails

Your organization's mail settings.

Applications and offers are still recorded in the inbox.

Paid multiposting

Not built in. The free XML feeds above are what job sites read.

—


Published. https://forumulate.com/guides/careers-spaces, in the guides space's "Building your site" collection. Re-import after editing — the page is not generated from this file.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.

Careers

Operation

Request

What it does

Shortcuts

Careers_getPipeline

GET /orgs/{orgId}/spaces/{spaceId}/careers/pipeline

Get the pipeline in a space

forumulate careers pipeline get<br>client.careers.getPipeline()

Careers_setPipeline

PUT /orgs/{orgId}/spaces/{spaceId}/careers/pipeline

Replace the pipeline in a space

forumulate careers pipeline set<br>client.careers.setPipeline()

Careers_form

GET /orgs/{orgId}/spaces/{spaceId}/careers/form

Get the form in a space (careers)

forumulate careers form get<br>client.careers.form()

Careers_setForm

PATCH /orgs/{orgId}/spaces/{spaceId}/careers/form

Update the form in a space (careers)

forumulate careers form set<br>client.careers.setForm()

Careers_exportCsv

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications.csv

File of applications

forumulate careers export<br>client.careers.exportCsv()

Careers_eeoSummary

GET /orgs/{orgId}/spaces/{spaceId}/careers/eeo-summary

Get the eeo summary in a space

forumulate careers eeo<br>client.careers.eeoSummary()

Careers_list

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications

List applications in a space

forumulate careers list<br>client.careers.list()

Careers_setStatus

PATCH /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}

Update the application

forumulate careers status<br>client.careers.setStatus()

Careers_replies

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/replies

List replies in a space (careers applications)

forumulate careers replies<br>client.careers.replies()

Careers_reply

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/reply

Create the reply in a space (careers applications)

forumulate careers reply<br>client.careers.reply()

Careers_assign

PATCH /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/assign

Assign the application

forumulate careers assign<br>client.careers.assign()

Careers_notes

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/notes

List notes in a space (careers applications)

forumulate careers notes list<br>client.careers.notes()

Careers_addNote

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/notes

Create a note in a space (careers applications)

forumulate careers notes add<br>client.careers.addNote()

Careers_deleteNote

DELETE /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/notes/{noteId}

Delete the note (spaces careers applications)

forumulate careers notes delete<br>client.careers.deleteNote()

Careers_ratings

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/ratings

List ratings in a space (careers applications)

forumulate careers ratings<br>client.careers.ratings()

Careers_rate

PUT /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/rating

Replace the rating in a space (careers applications)

forumulate careers rate<br>client.careers.rate()

Careers Ats

Operation

Request

What it does

Shortcuts

CareersAts_get

GET /orgs/{orgId}/spaces/{spaceId}/careers/ats

List ats in a space

client.careers.ats.get()

CareersAts_set

PUT /orgs/{orgId}/spaces/{spaceId}/careers/ats

Replace ats in a space

client.careers.ats.set()

CareersAts_remove

DELETE /orgs/{orgId}/spaces/{spaceId}/careers/ats

Delete ats in a space

client.careers.ats.remove()

CareersAts_sync

POST /orgs/{orgId}/spaces/{spaceId}/careers/ats/sync

Sync roles from the connected job board

client.careers.ats.sync()

Careers Hiring

Operation

Request

What it does

Shortcuts

CareersHiring_bulk

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/bulk

Move many applications to a stage and/or send each a template

client.careers.hiring.bulk()

CareersHiring_report

GET /orgs/{orgId}/spaces/{spaceId}/careers/report

The hiring report: funnel by stage, sources, time to hire, per role

client.careers.hiring.report()

CareersHiring_statusLink

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/status-link

Issue a fresh status link for a candidate (replaces the old one)

client.careers.hiring.statusLink()

CareersHiring_getOffer

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/offer

An application's live offer, or null

client.careers.hiring.getOffer()

CareersHiring_putOffer

PUT /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/offer

Write the draft offer; changing an approved one needs approval again

client.careers.hiring.putOffer()

CareersHiring_offerAction

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/offer/{action}

Approve (another member), send, or withdraw an offer

client.careers.hiring.offerAction()

Careers Indexing

Operation

Request

What it does

Shortcuts

CareersIndexing_view

GET /orgs/{orgId}/spaces/{spaceId}/careers/indexing

Get the indexing in a space

client.careers.indexing.view()

CareersIndexing_test

POST /orgs/{orgId}/spaces/{spaceId}/careers/indexing/test

Test the indexing

client.careers.indexing.test()

CareersIndexing_submit

POST /orgs/{orgId}/spaces/{spaceId}/careers/indexing/submit

Submit the indexing

client.careers.indexing.submit()

Esign

Operation

Request

What it does

Shortcuts

Esign_offerEnvelopes

GET /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/signature

The signature envelopes sent for an application’s offers

client.esign.offerEnvelopes()

Esign_sendOffer

POST /orgs/{orgId}/spaces/{spaceId}/careers/applications/{id}/signature

Send the approved offer letter for e-signature

client.esign.sendOffer()

Esign_legalEnvelopes

GET /orgs/{orgId}/spaces/{spaceId}/legal/signatures

The signature envelopes sent for a legal document

client.esign.legalEnvelopes()

Esign_sendLegal

POST /orgs/{orgId}/spaces/{spaceId}/legal/signatures

Send a legal document for e-signature

client.esign.sendLegal()

Google Indexing

Operation

Request

What it does

Shortcuts

GoogleIndexing_status

GET /orgs/{orgId}/google-indexing

Get the google indexing in an organization

client.careers.googleIndexing.status()

GoogleIndexing_configure

PUT /orgs/{orgId}/google-indexing

Replace the google indexing in an organization

client.careers.googleIndexing.configure()

GoogleIndexing_clear

DELETE /orgs/{orgId}/google-indexing

Delete the google indexing in an organization

client.careers.googleIndexing.clear()

Public Careers

Operation

Request

What it does

Shortcuts

PublicCareers_form

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/form

Get the form in a space (public reader careers)

client.careers.public.form()

PublicCareers_answer

POST /public/orgs/{orgId}/spaces/{spaceId}/careers/answers

Create an answer in a space (careers)

client.careers.public.answer()

PublicCareers_attach

POST /public/orgs/{orgId}/spaces/{spaceId}/careers/attachments

Create an attachment in a space (careers)

client.careers.public.attach()

PublicCareers_apply

POST /public/orgs/{orgId}/spaces/{spaceId}/careers/apply

Apply the career

client.careers.public.apply()

Public Careers Channels

Operation

Request

What it does

Shortcuts

PublicCareersChannels_rss

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/rss.xml

A careers board's open roles as RSS (?tag= narrows to one team)

client.careers.publicChannels.rss()

PublicCareersChannels_atom

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/atom.xml

A careers board's open roles as Atom (?tag= narrows to one team)

client.careers.publicChannels.atom()

PublicCareersChannels_jsonFeed

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/feed.json

A careers board's open roles as JSON Feed (?tag= narrows to one team)

client.careers.publicChannels.jsonFeed()

PublicCareersChannels_subscribe

POST /public/orgs/{orgId}/spaces/{spaceId}/careers/subscribe

Join a careers board’s talent community (email when a role opens)

client.careers.publicChannels.subscribe()

PublicCareersChannels_indeed

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/jobs.xml

A careers board's open roles as an Indeed XML job feed

client.careers.publicChannels.indeed()

PublicCareersChannels_linkedin

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/linkedin.xml

A careers board's open roles as a LinkedIn XML job feed

client.careers.publicChannels.linkedin()

Public Careers Status

Operation

Request

What it does

Shortcuts

PublicCareersStatus_status

GET /public/orgs/{orgId}/spaces/{spaceId}/careers/status/{token}

A candidate's application state and offer, read through their private link

client.careers.publicStatus.status()

PublicCareersStatus_answer

POST /public/orgs/{orgId}/spaces/{spaceId}/careers/status/{token}/offer

Accept or decline an offer from the status link

client.careers.publicStatus.answer()

Public Contact

Operation

Request

What it does

Shortcuts

PublicContact_exists

GET /public/orgs/{orgId}/spaces/{spaceId}/contact

Get the contact in a space (public reader)

client.forms.publicContact.exists()

PublicContact_submit

POST /public/orgs/{orgId}/spaces/{spaceId}/contact

Create the contact in a space

client.forms.publicContact.submit()

PublicContact_attach

POST /public/orgs/{orgId}/spaces/{spaceId}/contact/attachments

Create an attachment in a space (contact)

client.forms.publicContact.attach()

PublicContact_answer

POST /public/orgs/{orgId}/spaces/{spaceId}/contact/answers

Create an answer in a space (contact)

client.forms.publicContact.answer()

The Forumulate teamUpdated