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

# Publishing to Beeline

> How composerID publishes a decision into Beeline's Client APIs with the Intent ID in externalId, reads the site first and listens for events.

This page walks the Beeline publish chain as the reference adapter runs it: the token, the find-before-create check, the create, the client-defined values, the reads that precede any write, the scopes requested and the events that come back. It is for integration engineers and security reviewers. The coverage page, with the full field mapping and the vendor citations, is [Beeline](/destinations/beeline).

Beeline is the first destination composerID is integrating. Everything here derives from Beeline's published OpenAPI specifications, vendored in the engineering repository with their provenance (downloaded from developers.beeline.com on 2026-08-13), and is exercised offline against a specification-faithful fake tenant.

<Warning>
  Specification mapped is not the same as live. Tenant-specific configuration (mandatory fields, picklists, client-defined fields, webhook enablement) is proven against a customer's own site during onboarding. This page states what the adapter does; it does not claim a production customer integration.
</Warning>

## The publish

The publish is one documented operation against one stock object, the Business Intake Request. The Intent ID rides `externalId`, a client-supplied field that Beeline makes filterable on the list endpoint, so the record is re-findable from either side. The find, create and client-defined-values operations are in the `business-intake_2025-01-01` specification; the token call is documented in Beeline's authentication overview.

<Steps>
  <Step title="Get a token per API product">
    `POST https://integrations.auth.beeline.com/oauth/token` (EU: `integrations.eu-auth.beeline.com`) with client credentials and `audience` set to the target API's base URL. Tokens last 24 hours, one per audience. Client APIs are served from `https://client.beeline.com/api` and `https://client-eu.beeline.com/api` under `/sites/{clientSiteId}/...`.
  </Step>

  <Step title="Find before create">
    `GET /sites/{clientSiteId}/business-intake-requests?externalId=<Intent ID>`. Beeline documents no idempotency header, so this filter is the replay guard: if a record with the Intent ID already exists, nothing is created.
  </Step>

  <Step title="Create the request">
    `POST /sites/{clientSiteId}/business-intake-requests` with `externalId` carrying the Intent ID. The create schema requires `jobTitleId`, `hiringManagerId`, `workLocationId`, `quantity` and `startDate`; the tenant may require more, which is what the preflight reads below discover.
  </Step>

  <Step title="Write client-defined values">
    `PATCH /sites/{clientSiteId}/business-intake-requests/{businessIntakeRequestId}/client-defined-values`, writing only the custom fields the programme maps.
  </Step>
</Steps>

<Note>
  Every operation carries the `api-version` query parameter the specifications mark as required, pinned to each product's dated version (for example `2025-01-01` for Business Intake and `2023-07-01` for Webhook Management). A `403` from Beeline means a missing scope for the site, not a bad credential, and the transport reports it in those terms.
</Note>

## Reading the site first

Preflight resolves the decision against the site's own lists before anything is written, using the documented reference-data operations. The four `*Id` fields on the create schema are tenant UUIDs, so names from the decision are resolved to the site's identifiers here.

| Read                                                                                            | Resolves                                              | Notes                                                            |
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------- |
| `GET /sites/{clientSiteId}/job-titles` (Program Data)                                           | Job title to `jobTitleId`                             | Paged with `skip` and `top`; active records only                 |
| `GET /sites/{clientSiteId}/users` (Foundational Data)                                           | Hiring manager email or username to `hiringManagerId` | The only person identifier sent                                  |
| `GET /sites/{clientSiteId}/locations` (Foundational Data)                                       | Location to `workLocationId`                          | Active records only                                              |
| `GET /sites/{clientSiteId}/cost-centers?costCenterNumber=` (Foundational Data)                  | Cost centre number to `billToCostCenterId`            | Lookup by number: the specification has no list-all on this path |
| `GET /sites/{clientSiteId}/client-defined-fields` and `.../{fieldId}/items` (Foundational Data) | The site's required custom fields and their picklists | The fields vendor documentation cannot know                      |

Every gap is reported in one pass: required fields with no value, values with no active match on the site, and picklist violations. The result is an enrichment request that an upstream surface renders back to the requester.

## Access requested

Beeline grants scopes per API product, so the grant is a finite list a security reviewer can read line by line.

| API product                                          | Scopes                                                                                         | Why                                                     |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Business Intake (`business-intake_2025-01-01`)       | `read:business_intake`, `write:business_intake`, `read:client_defined`, `write:client_defined` | Publish the request and carry the Intent ID             |
| Worker Management (`worker-management_2023-07-01`)   | `read:request`, `read:intake-form`, `write:intake-form`, `read:candidate`, `read:assignment`   | The intake-form path, and read-back for reconciliation  |
| Webhook Management (`webhook-management_2023-07-01`) | `read:webhook_management`, `write:webhook_management`                                          | Subscribe, validate the endpoint, recover missed events |
| Project Agreement Management (`sow_2025-01-01`)      | `read:sow`, `read:project`                                                                     | SOW and milestone read-back; no writes                  |
| Financial Time and Expense (`bte_2023-07-01`)        | `read:bte`                                                                                     | Timesheet and spend read-back                           |
| Supplemental Data (RaaS)                             | `run:raas`                                                                                     | Programme-scale reconciliation reads                    |

<Check>
  Excluded by design: Foundational Data writes (`write:iam`, `write:org`, `write:user`, `write:spend_authority`), supplier and invoice scopes, and approval-write scopes. Approving time or candidates from a chat surface needs `write:bte` or `write:candidate`, which stays a separate and explicit grant rather than something bundled into the publish integration.
</Check>

## Events back

Beeline's Webhook Management API delivers `{id, type, time, data}` envelopes. The receiver does five things, each backed by a documented fact in `webhook-management_2023-07-01`:

* **Handshake.** When a webhook is created, modified or activated, Beeline sends a `webhook.validation` event carrying a validation code, and the subscription stays `pending` until that code is echoed back through `POST /sites/{clientSiteId}/webhooks/{webhookId}/validate/{validationCode}`. Beeline marks the webhook inactive if the code is not returned within 10 minutes. The receiver captures the code so the subscription can be activated.
* **Verify.** With `authType: hmac`, Beeline signs deliveries with the base64 secret supplied at subscription. The receiver verifies HMAC-SHA256 over the raw body with a timing-safe comparison and rejects, never queues, an unverifiable delivery. The concrete signature header name is confirmed against the tenant at onboarding.
* **De-duplicate.** Beeline documents that duplicate event messages can occur, so every event is de-duplicated on its `id` before anything downstream sees it.
* **Correlate.** An event carries Beeline record ids (`assignmentId`, `businessIntakeRequestId` and so on), not the Intent ID, so the receiver maps each event to an Intent ID learned from publish receipts and reference reads.
* **Sweep.** After any outage the receiver replays `GET /sites/{clientSiteId}/webhooks/{webhookId}/events` through the same ingestion path, so recovery never depends on a push having arrived.

The default subscription covers the start of each lifecycle in scope: `bid.submitted`, `candidate.submitted`, `assignment.scheduled`, `assignment.started`, `sow.pendinginitialapproval`, `sow.pendingfinalapproval` and `timesheet.submittedforapproval`. Later stages ride the same hook.

<Warning>
  The vendored `webhook-management_2023-07-01` specification is authoritative for event names, and `timesheet.submittedforapproval` is the only timesheet event it defines. The reference receiver's `DEFAULT_EVENT_TYPES` still carries `timesheet.submitted`, a name the specification does not define; read the specification name as correct until the code is corrected to match.
</Warning>

## The statement-of-work route

Beeline's Project Agreement Management API (`sow_2025-01-01`) exposes read and approval operations and no create operation. Statements of work are created inside Beeline. composerID's role on that channel is read and approval visibility against the Intent ID, and this page does not pretend otherwise.

## Running it

One process runs the whole chain: read the completed assessment, mint the Intent ID, read the site's configuration, resolve the decision against it, and publish with the Intent ID in `externalId`.

<CodeGroup>
  ```bash Rehearse locally theme={"system"}
  python3 -m service.beeline_publish --rehearse --response resp-001
  ```

  ```bash Payload only theme={"system"}
  python3 -m service.beeline_publish --rehearse --response resp-001 --dry-run
  ```

  ```bash Against a real site theme={"system"}
  export BEELINE_SITE_ID=... BEELINE_CLIENT_ID=... BEELINE_CLIENT_SECRET=...
  export WORKAUTHOR_BASE=... WORKAUTHOR_TOKEN=...
  python3 -m service.beeline_publish --response <assessmentResponseId> --region us
  ```
</CodeGroup>

`--region` takes `us` or `eu`. The exit code tells you what happened:

* `0`: published; the command prints a receipt.
* `1`: a transport or trigger error; the message is on stderr.
* `2`: a preflight gap; the command prints an enrichment request for the requester and sends nothing.
* `64`: credentials are missing.

<Tip>
  `--rehearse` boots a fake WorkAuthor and a fake Beeline on localhost that behave the way the vendored specifications say the real services behave, including the required `api-version` parameter and the `externalId` list filter. It is how the chain is exercised before any credential exists.
</Tip>

## Next steps

<Columns cols={3}>
  <Card title="Beeline coverage" icon="table" href="/destinations/beeline">
    The field mapping, what crosses the boundary, and the vendor citations.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Run the sandbox and the Beeline chain from a clean checkout.
  </Card>

  <Card title="Events" icon="webhook" href="/concepts/events">
    How destination webhooks land on the intent timeline.
  </Card>
</Columns>
