Back to Job Posting & Applicant Tracking

Integration and API Spec

The reference during the build: every tool connection, auth scope, field mapping, and error-handling rule.

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Job Posting and Applicant Tracking

[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]

This document is the authoritative technical reference for every integration in the Job Posting and Applicant Tracking automation. It covers authentication methods, required OAuth scopes, webhook configuration, field mappings between tools, credential store layout, and error handling behaviour for every integration point. The FullSpec team uses this spec to build, configure, and test all connections. No external tool decisions or credential choices should be made that contradict this document.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agents
Greenhouse
ATS, vacancy management, applicant records, tag writes
API key (server-side)
Greenhouse Core (API access enabled)
Agent 1, Agent 2, Agent 3
LinkedIn
Job posting channel
OAuth 2.0 (3-legged)
LinkedIn Recruiter or Job Posting API access
Agent 1
Gmail
Acknowledgement emails, job ad approval routing
OAuth 2.0 (Google Identity)
Google Workspace (any tier)
Agent 1, Agent 2
Google Sheets
Live hiring status tracker, row writes
OAuth 2.0 (Google Identity, service account)
Google Workspace (any tier)
Agent 2
Slack
Hiring manager shortlist notifications
OAuth 2.0 (Slack app install)
Slack Free or above
Agent 3
Orchestration layer
Workflow execution, credential store, polling and webhook routing, retry logic
Internal (platform credentials)
Standard tier ($150/month)
All agents
Before you connect anything: sandbox-test all connections using non-production credentials and a Greenhouse sandbox environment before any production credentials are stored or used. Confirm every OAuth grant resolves without scope errors before proceeding to the build phase. Never hardcode credentials in workflow steps.

02Per-tool integration detail

Greenhouse

Primary ATS. Acts as the trigger source for Agent 1 (vacancy approved webhook), the applicant record store for Agent 2, and the tag-write target for Agent 3. The Greenhouse API key must be scoped to a dedicated harvest API user created for the automation.

Auth method
API key passed as Basic Auth header: Authorization: Basic base64(API_KEY:)
Required scopes
jobs:read, jobs:write, candidates:read, candidates:write, applications:read, applications:write, users:read, activity_feed:write
Webhook setup
Create a webhook in Greenhouse > Configure > Dev Center > Web Hooks. Event: job_post.approved. Payload URL: set to the orchestration layer inbound webhook URL. Secret token: generate a 32-byte random string and store in the credential store as GREENHOUSE_WEBHOOK_SECRET. Validate the X-Grnhse-Signature header on every inbound request using HMAC-SHA256.
Required configuration
Pipeline stages must include: New Application, Shortlisted, Hold, Rejected. Custom field 'fit_score' (type: number) and 'screening_notes' (type: text) must be created on the candidate record before Agent 3 runs. Screening criteria for each role type must be stored in the Greenhouse job record's 'internal_notes' field using the defined JSON schema (see field mappings, section 03).
Credential store keys
GREENHOUSE_API_KEY, GREENHOUSE_WEBHOOK_SECRET, GREENHOUSE_BASE_URL (https://harvest.greenhouse.io/v1)
Rate limits
Harvest API: 50 requests/10 seconds per API key. At 6-10 active roles/month and ~312 automation runs/month, peak burst is well under the limit. Throttling is not required at current volume, but the orchestration layer must queue concurrent application intake calls with a 300ms inter-request delay as a safeguard.
Hard constraints
Greenhouse webhooks do not support retry on non-200 responses; the orchestration layer must acknowledge with HTTP 200 immediately and process asynchronously. Candidate PII must not be logged to the orchestration platform's debug logs.
LinkedIn

Job posting channel. The automation posts approved job ads to LinkedIn via the LinkedIn Job Posting API. The organisation must hold a LinkedIn Company Page with posting permissions granted to the connected app.

Auth method
OAuth 2.0, 3-legged authorisation code flow. Access token and refresh token stored in credential store. Token lifetime: 60 days (access), 1 year (refresh). Implement automatic refresh before expiry.
Required scopes
r_organization_social, w_organization_social, r_ads_reporting (if tracking apply clicks), rw_jobs (Job Posting API)
Webhook/trigger setup
No inbound webhook from LinkedIn. The orchestration layer posts outbound to LinkedIn REST API endpoint POST /v2/jobPostings when the job ad approval is confirmed. LinkedIn does not push application data; Greenhouse handles applicant intake.
Required configuration
Store LinkedIn Organisation URN as LINKEDIN_ORG_URN (format: urn:li:organization:XXXXXXXXX) in the credential store. Job ad template must map vacancy fields to LinkedIn schema fields: title, description, listedAt, externalJobPostingId (Greenhouse job ID), location, employmentType, workplaceTypes. Store LINKEDIN_COMPANY_ID for the job posting payload.
Credential store keys
LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET, LINKEDIN_ACCESS_TOKEN, LINKEDIN_REFRESH_TOKEN, LINKEDIN_ORG_URN, LINKEDIN_COMPANY_ID
Rate limits
LinkedIn Marketing and Talent APIs: 100 calls/day per app for most endpoints. At current volume (6-10 posts/month), this is negligible. No throttling required. Monitor the X-RateLimit-Remaining header on every response and log when below 20.
Hard constraints
LinkedIn requires the job description HTML to be sanitised. Strip all non-permitted tags before submission. Job posts created via API cannot be boosted without a separate Recruiter contract; this is out of scope for the current build.
Gmail

Used by Agent 1 to route job ad drafts to the hiring manager for approval, and by Agent 2 to dispatch applicant acknowledgement emails. A dedicated sending address (e.g. hiring@[YourCompany.com]) must be authenticated via OAuth 2.0 and stored separately from personal user credentials.

Auth method
OAuth 2.0, Google Identity Platform. Use a service account with domain-wide delegation OR a dedicated OAuth client for the sending mailbox. Store refresh token in credential store.
Required scopes
https://www.googleapis.com/auth/gmail.send, https://www.googleapis.com/auth/gmail.readonly (for approval reply polling), https://www.googleapis.com/auth/gmail.modify (to label processed threads)
Webhook/trigger setup
For approval reply detection (Agent 1): configure Gmail Push Notifications via Google Cloud Pub/Sub. Topic: projects/[PROJECT_ID]/topics/gmail-approval-inbox. Gmail watch() call must be renewed every 7 days via a scheduled task in the orchestration layer. Filter by label APPROVAL_PENDING and subject prefix [APPROVE].
Required configuration
Gmail label 'APPROVAL_PENDING' must exist and be applied to outgoing approval request threads. Approval email template must include a one-click approval link (tokenised URL handled by the orchestration layer) and a plain-text fallback reply instruction. Acknowledgement email template stored in credential store as GMAIL_ACK_TEMPLATE_ID (referencing the stored template string, not hardcoded in the workflow step).
Credential store keys
GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REFRESH_TOKEN, GMAIL_SENDING_ADDRESS, GMAIL_ACK_TEMPLATE_ID, GMAIL_APPROVAL_TEMPLATE_ID, GMAIL_PUBSUB_TOPIC
Rate limits
Gmail API: 250 quota units/user/second; send limit 500 messages/day for Workspace accounts. At ~312 runs/month (~10/day peak), this is well within limits. No throttling required at current volume.
Hard constraints
Do not use a personal Gmail account as the sending address. Gmail push notification watch() expiry at 7 days is a known failure mode; the scheduled renewal task is mandatory, not optional.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical
Google Sheets

Live hiring tracker. Agent 2 writes one row per new application. The sheet must be pre-structured with named columns before the first automation run. A service account with editor permissions on the target spreadsheet is the recommended auth pattern to avoid dependency on any individual user's OAuth session.

Auth method
OAuth 2.0 via Google service account (JSON key). Grant the service account email editor access to the target spreadsheet. Store the service account key JSON in the credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets, https://www.googleapis.com/auth/drive.file
Webhook/trigger setup
No webhook. Google Sheets is an output target only. All writes use the Sheets API v4 spreadsheets.values.append endpoint.
Required configuration
Spreadsheet ID stored as SHEETS_HIRING_TRACKER_ID in credential store (not hardcoded). Sheet tab name: 'Applications'. Column headers (row 1, frozen): role_title, job_id, applicant_name, applicant_email, phone, resume_url, source_channel, application_date, fit_score, tag, screening_notes, greenhouse_profile_url. The orchestration layer must write in this exact column order. Range for appends: 'Applications!A:M'.
Credential store keys
SHEETS_SERVICE_ACCOUNT_JSON, SHEETS_HIRING_TRACKER_ID
Rate limits
Sheets API v4: 300 read requests/minute/project, 60 write requests/minute/user. At current volume (peak ~10 applications/day), well within limits. No throttling required.
Hard constraints
Do not grant the service account access broader than the specific spreadsheet. Avoid using spreadsheets.batchUpdate for row writes; use values.append to prevent row collision on concurrent intake runs.
Slack

Used exclusively by Agent 3 to deliver the ranked shortlist notification to the hiring manager. A dedicated Slack app must be installed in the workspace with the hiring manager's Slack user ID or a designated hiring channel ID stored in the credential store.

Auth method
OAuth 2.0, Slack app install (Bot Token). Scopes granted during app install flow. Bot token stored as SLACK_BOT_TOKEN in credential store.
Required scopes
chat:write, chat:write.public (if posting to public channels), users:read, users:read.email (to resolve hiring manager Slack user ID from email address)
Webhook/trigger setup
No inbound webhook from Slack. The orchestration layer posts outbound to the Slack Web API chat.postMessage endpoint. The hiring manager's Slack user ID or channel ID is resolved once during setup and stored as SLACK_HIRING_MANAGER_CHANNEL. Optionally configure Slack interactive message callbacks (block_actions) if future builds require in-Slack shortlist approval; out of scope for current build.
Required configuration
Slack message template uses Block Kit layout: header block (role title), section block (ranked candidate table with fit score and Greenhouse profile URL per candidate), context block (timestamp and agent attribution). Template structure stored as SLACK_SHORTLIST_BLOCK_TEMPLATE_ID in credential store. Hiring manager Slack user ID stored as SLACK_HIRING_MANAGER_UID.
Credential store keys
SLACK_BOT_TOKEN, SLACK_HIRING_MANAGER_UID, SLACK_HIRING_CHANNEL_ID, SLACK_SHORTLIST_BLOCK_TEMPLATE_ID
Rate limits
Slack Web API: Tier 3 methods (chat.postMessage) limited to 1 call/second. At current volume this is not a concern. No throttling required.
Hard constraints
Do not expose candidate PII beyond name, fit score, and Greenhouse profile URL in the Slack message. The Greenhouse link is the single source of truth for full candidate detail. Bot token must never be embedded in message payloads or logged.

03Field mappings between tools

The following tables define the exact field mappings for each agent handoff. Source and destination field names are given in monospace as they appear in the respective API payloads. All fields are mandatory unless marked optional.

Agent 1 handoff: Greenhouse vacancy record to Gmail approval email and LinkedIn job post.

Source tool
Source field
Destination tool
Destination field
Greenhouse
`jobs[].title`
Gmail
`subject` (approval email subject line)
Greenhouse
`jobs[].departments[].name`
Gmail
`body.department_placeholder`
Greenhouse
`jobs[].offices[].name`
Gmail
`body.location_placeholder`
Greenhouse
`jobs[].internal_notes`
Gmail
`body.screening_criteria_placeholder`
Greenhouse
`jobs[].id`
Gmail
`body.approval_token_payload.job_id`
Greenhouse
`jobs[].hiring_managers[0].email`
Gmail
`to`
Greenhouse
`jobs[].title`
LinkedIn
`jobPostings[].title`
Greenhouse
`jobs[].content`
LinkedIn
`jobPostings[].description.text`
Greenhouse
`jobs[].id`
LinkedIn
`jobPostings[].externalJobPostingId`
Greenhouse
`jobs[].offices[0].location.name`
LinkedIn
`jobPostings[].location`
Greenhouse
`jobs[].employment_type`
LinkedIn
`jobPostings[].employmentType`

Agent 2 handoff: Greenhouse application record to Google Sheets row and Gmail acknowledgement email.

Source tool
Source field
Destination tool
Destination field
Greenhouse
`applications[].candidate.first_name`
Google Sheets
`applicant_name` (concatenated with last_name)
Greenhouse
`applications[].candidate.email_addresses[0].value`
Google Sheets
`applicant_email`
Greenhouse
`applications[].candidate.phone_numbers[0].value`
Google Sheets
`phone`
Greenhouse
`applications[].attachments[0].url`
Google Sheets
`resume_url`
Greenhouse
`applications[].source.public_name`
Google Sheets
`source_channel`
Greenhouse
`applications[].applied_at`
Google Sheets
`application_date`
Greenhouse
`applications[].job_id`
Google Sheets
`job_id`
Greenhouse
`jobs[].title`
Google Sheets
`role_title`
Greenhouse
`applications[].id`
Google Sheets
`greenhouse_profile_url` (constructed as https://app.greenhouse.io/applications/{id})
Greenhouse
`applications[].candidate.first_name`
Gmail
`body.applicant_name_placeholder`
Greenhouse
`jobs[].title`
Gmail
`body.role_title_placeholder`
Greenhouse
`applications[].candidate.email_addresses[0].value`
Gmail
`to`

Agent 3 handoff: Greenhouse scored candidate data to Slack shortlist notification.

Source tool
Source field
Destination tool
Destination field
Greenhouse
`applications[].candidate.first_name` + `last_name`
Slack
`blocks[].text.text` (candidate name in shortlist section)
Greenhouse
`applications[].custom_fields.fit_score`
Slack
`blocks[].text.text` (fit score column)
Greenhouse
`applications[].id`
Slack
`blocks[].accessory.url` (Greenhouse profile deep-link)
Greenhouse
`jobs[].title`
Slack
`blocks[0].text.text` (header: role title)
Greenhouse
`applications[].custom_fields.screening_notes`
Slack
`blocks[].text.text` (reasoning note per candidate)
Greenhouse
`jobs[].hiring_managers[0].slack_user_id`
Slack
`channel` (resolved via SLACK_HIRING_MANAGER_UID)
Integration and API SpecPage 2 of 4
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three discrete workflows, one per agent. Workflows share a single credential store. No workflow calls another directly; handoffs are event-driven via Greenhouse record state changes or the orchestration layer's internal event bus.
Agent 1 trigger
Webhook (inbound). Greenhouse fires a job_post.approved webhook to the orchestration layer's inbound URL. HMAC-SHA256 signature validation performed against GREENHOUSE_WEBHOOK_SECRET before any processing begins. Acknowledged with HTTP 200 immediately; processing queued asynchronously.
Agent 1 approval poll
After the approval email is sent, a Gmail push notification subscription (Pub/Sub) watches the APPROVAL_PENDING label for a reply or link-click token redemption. Pub/Sub message triggers the post-to-LinkedIn step. If no approval is received within 48 hours, the orchestration layer sends a single reminder and logs a pending_approval_timeout event.
Agent 2 trigger
Webhook (inbound). Greenhouse fires a candidate.applied webhook for each new application to any active role. Same HMAC-SHA256 validation as Agent 1. Processing is queued to prevent concurrent write collisions on Google Sheets.
Agent 3 trigger
Poll (scheduled). The orchestration layer polls Greenhouse every 5 minutes for applications in the 'New Application' stage that have not yet been assigned a fit_score. At current volume this poll interval is sufficient. If Greenhouse supports application.updated webhooks in the subscribed plan, migrate to webhook trigger to reduce API calls.
Agent 3 Slack signature
Outbound only; no inbound Slack events at this stage. Slack Bot Token verified at connection time; no per-message signature validation required for outbound posts.
Credential store
All secrets stored in the orchestration platform's encrypted credential store. Never stored in workflow step configuration, environment variable files, or version-controlled config. Rotated on a 90-day schedule or immediately on suspected exposure.
All values in angle brackets must be populated before the first workflow run. Never commit these values to source control.
// Credential store contents (all keys)

// Greenhouse
GREENHOUSE_API_KEY           = <harvest_api_key>
GREENHOUSE_WEBHOOK_SECRET    = <32-byte random hex string>
GREENHOUSE_BASE_URL          = https://harvest.greenhouse.io/v1

// LinkedIn
LINKEDIN_CLIENT_ID           = <oauth_client_id>
LINKEDIN_CLIENT_SECRET       = <oauth_client_secret>
LINKEDIN_ACCESS_TOKEN        = <current_access_token>
LINKEDIN_REFRESH_TOKEN       = <current_refresh_token>
LINKEDIN_ORG_URN             = urn:li:organization:<org_id>
LINKEDIN_COMPANY_ID          = <numeric_company_id>

// Gmail
GMAIL_CLIENT_ID              = <oauth_client_id>
GMAIL_CLIENT_SECRET          = <oauth_client_secret>
GMAIL_REFRESH_TOKEN          = <oauth_refresh_token>
GMAIL_SENDING_ADDRESS        = hiring@[YourCompany.com]
GMAIL_ACK_TEMPLATE_ID        = <stored_template_key>
GMAIL_APPROVAL_TEMPLATE_ID   = <stored_template_key>
GMAIL_PUBSUB_TOPIC           = projects/<project_id>/topics/gmail-approval-inbox

// Google Sheets
SHEETS_SERVICE_ACCOUNT_JSON  = <service_account_key_json>
SHEETS_HIRING_TRACKER_ID     = <spreadsheet_id>

// Slack
SLACK_BOT_TOKEN              = xoxb-<slack_bot_token>
SLACK_HIRING_MANAGER_UID     = U<slack_user_id>
SLACK_HIRING_CHANNEL_ID      = C<slack_channel_id>
SLACK_SHORTLIST_BLOCK_TEMPLATE_ID = <stored_block_kit_template_key>

// Orchestration layer internals
PLATFORM_INBOUND_WEBHOOK_URL = https://hooks.<platform-domain>/inbound/<workflow_id>
PLATFORM_RETRY_MAX           = 3
PLATFORM_RETRY_BACKOFF_MS    = [1000, 5000, 30000]

05Error handling and retry logic

Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors must be written to the orchestration platform's error log with the integration name, error code, payload reference, and timestamp. Where a manual fallback is defined, the HR coordinator must be notified via email to support@gofullspec.com and the sending address within 15 minutes of the failure.

Integration
Scenario
Required behaviour
Greenhouse webhook (inbound)
Webhook payload fails HMAC-SHA256 signature validation
Reject with HTTP 403. Log the raw headers and truncated payload. Do not process. Alert FullSpec monitoring. No retry.
Greenhouse API (read)
GET request returns 429 rate limit response
Pause workflow for 10 seconds, retry up to 3 times with exponential backoff (1s, 5s, 30s). If still failing after 3 retries, log error and alert HR coordinator via email. Do not drop the event.
Greenhouse API (write: tag or score)
PATCH to candidate record returns 422 or 500
Retry 3 times with backoff (1s, 5s, 30s). On third failure, write candidate ID and intended payload to a dead-letter queue in the orchestration platform and alert HR coordinator. Manual fallback: HR coordinator applies tag directly in Greenhouse.
LinkedIn Job Posting API
POST returns 401 (token expired)
Trigger OAuth refresh flow automatically using LINKEDIN_REFRESH_TOKEN. Retry the post once. If refresh fails, alert FullSpec team and HR coordinator. Manual fallback: HR coordinator posts the approved ad directly on LinkedIn.
LinkedIn Job Posting API
POST returns 400 (invalid payload, e.g. unsupported HTML)
Log the full error response and the submitted payload. Do not retry. Alert FullSpec team with the sanitised payload for inspection. HR coordinator notified to post manually while the payload issue is resolved.
Gmail (send: acknowledgement email)
Gmail API returns 429 or 503
Retry up to 3 times with exponential backoff (2s, 10s, 60s). If all retries fail, log the applicant ID and intended recipient to the dead-letter queue. Alert HR coordinator to send acknowledgement manually. SLA: acknowledgement must reach applicant within 30 minutes of intake.
Gmail (push notification: approval reply)
Pub/Sub watch() renewal fails (7-day expiry)
Scheduled renewal task runs every 6 days 20 hours to provide buffer. If renewal returns an error, retry 3 times over 1 hour. If still failing, fall back to polling the APPROVAL_PENDING Gmail label every 10 minutes. Alert FullSpec team immediately.
Gmail (approval timeout)
No approval received within 48 hours of sending
Send one automated reminder to the hiring manager at the 48-hour mark. Log a pending_approval_timeout event. If no response by 72 hours, escalate by emailing the HR coordinator to follow up directly. Workflow pauses; role is not posted until approval is confirmed.
Google Sheets (row append)
Sheets API returns 403 (permission denied)
Log error with spreadsheet ID and service account email. Do not retry until permissions are confirmed. Alert HR coordinator and FullSpec team. Manual fallback: HR coordinator logs the application row directly in Google Sheets. Check service account editor access immediately.
Google Sheets (row append)
Concurrent write collision or 429 rate limit
Queue all Sheets write operations sequentially with a 300ms inter-request delay. If a 429 is returned, back off for 60 seconds and retry up to 3 times. On third failure, route to dead-letter queue and alert HR coordinator.
Slack (chat.postMessage)
API returns 404 (channel not found or user not found)
Log the error with the channel or user ID attempted. Do not retry. Alert HR coordinator via email with the full shortlist content as a plain-text fallback so the hiring manager is not blocked. Update SLACK_HIRING_MANAGER_UID or SLACK_HIRING_CHANNEL_ID in the credential store before next run.
Orchestration layer (global)
Any unhandled exception in any workflow step
Catch-all error handler must write the step name, error message, and payload reference to the error log. Send an alert email to support@gofullspec.com within 5 minutes. Workflow must halt at the failed step and not silently skip to the next step. Dead-letter queue receives the triggering payload for manual replay once the issue is resolved.
Dead-letter queue items must be reviewed by the FullSpec team within one business day. Each item includes the integration name, error code, timestamp, and the original triggering payload so the run can be replayed manually or automatically once the underlying issue is resolved. Contact support@gofullspec.com to escalate any queue items that are not resolved within 24 hours.
Integration and API SpecPage 3 of 4
FS-DOC-05Technical
This document reflects the confirmed Standard build ($3,800 one-off, $150/month orchestration). Any change to tooling, agent scope, or authentication method must be reviewed by the FullSpec team before implementation to ensure this spec remains internally consistent. Send change requests to support@gofullspec.com.
Integration and API SpecPage 4 of 4

More documents for this process

Every document generated for Job Posting & Applicant Tracking.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Developer Handover Pack
Technical · Developer
View
Test and QA Plan
Quality · Developer
View