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

# Publishing

> What it takes for a job to go live, and the three gates in your way.

A job goes live either by creating it with `"publish": true` or by calling
`POST /jobs/{id}/publish` on a draft. Both run the same three gates the
Sorce dashboard runs.

## Gate 1: Completeness

A live listing must carry:

* `title` and `description`
* `remoteOption`, plus `remoteCountry` (REMOTE) **or** at least one
  location (HYBRID / IN\_PERSON). See [Locations](/guides/locations)
* `jobType` and `jobLevel`
* A salary range: `salaryMin` ≤ `salaryMax`, with `salaryPeriod`
* `bidPerQualifiedApplicantCents` > 0: what you pay per qualified
  applicant
* At least one `REQUIRED` qualification: what applicants are screened
  against

Anything missing comes back as `422 VALIDATION_FAILED` with a `fields` map
naming each gap.

## Gate 2: Terms

Your organization accepts the Sorce Employer Terms of Service **once, in
the dashboard**. The API cannot accept terms on your behalf. Until then,
publishing returns `409 AGREEMENT_OUTDATED`.

## Gate 3: Payment method

Publishing is what lets a balance accrue, so a card on file is the price
of going live: `402 PAYMENT_METHOD_REQUIRED` until one is added in the
dashboard's Billing tab.

## Create-and-publish is atomic

```bash theme={null}
curl -X POST https://www.sorce.jobs/api/external/v1/jobs \
  -H "Authorization: Bearer sk_ext_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Senior Product Designer",
    "description": "Own the design system end to end.",
    "remoteOption": "REMOTE",
    "remoteCountry": "United States",
    "jobType": "FULL_TIME",
    "jobLevel": "SENIOR",
    "salaryMin": 150000,
    "salaryMax": 195000,
    "salaryPeriod": "ANNUAL",
    "bidPerQualifiedApplicantCents": 1500,
    "qualifications": [
      { "text": "5+ years of product design experience", "kind": "REQUIRED", "order": 0 }
    ],
    "publish": true
  }'
```

If any gate fails, **nothing is created**. No orphan drafts to clean up.
Retry the same payload once the gate is satisfied.

## Post-application identifiers (optional)

When your jobs originate on an external ATS, you can attach routing metadata on
create or patch:

* `opportunityId` — your id for the opening on your source ATS
* `atsJobUrl` — the canonical job URL on that ATS

Both are optional and not required to publish. They are stored on the job and
returned on every job read so downstream post-application flows can route
submissions back to the correct opening.

## After publish

* The listing runs for **30 days** (`expiresAt`), then flips to `EXPIRED`.
* `pause` hides it temporarily; `resume` brings it back; `close` is
  terminal.
* Edits to a live job are re-validated against Gate 1 and rejected whole
  if they'd leave the listing incomplete.
* Once applications arrive, `qualifications` and `questions` freeze
  (`409 JOB_FROZEN`); the bid and everything else stay editable.
