API Reference

Job Search

Search for jobs across supported ATS platforms by title, location, and filters. Every returned job can be applied to via the Sessions endpoint.

Overview

The Job Search endpoint lets you find open positions across all supported ATS platforms. Results are pre-filtered to only include jobs that can be submitted through the API — so every job you get back is ready to be passed directly to the Sessions apply endpoint.

This is useful for clients who don't have their own job feed and want to offer end-to-end search-and-apply functionality through a single API.

Search Jobs

Search for jobs by title, location, and optional filters. Returns a list of matching jobs with full details including company information, salary data, and direct application URLs.

POST/jobs/search
NameTypeRequiredDescription
titlesstring[]RequiredArray of job titles to search for (e.g. ["Software Engineer", "Frontend Developer"])
locationsstring[]OptionalArray of locations (e.g. ["Remote", "United States", "San Francisco, CA"])
postedAtMaxAgeDaysintegerOptionalMaximum age of job postings in days (default: 30)
resultsintegerOptionalTarget number of results to return (default: 50, max: 500)
excludeJobIdsstring[]OptionalArray of job IDs to exclude from results (e.g. previously applied jobs)
userIdstringOptionalUser ID for user-level deduplication. When provided, repeated searches only exclude jobs previously returned to this specific user. Without it, deduplication is scoped to the entire client/API key.
filtersobjectOptional
Optional filters to narrow results
FieldTypeRequiredDescription
workTypestring | string[]OptionalWork arrangement filter. Accepts a single value or array: "Remote", "On-site", or "Hybrid". Hybrid is treated as non-remote for filtering purposes.
jobTypestring | string[]OptionalEmployment type filter. Accepts a single value or array: "full-time", "part-time", "contract", or "internship"
experienceLevelstring | string[]OptionalSeniority filter. Accepts a single value or array: "junior", "mid", "senior", "lead", or "executive"
salaryMinintegerOptionalMinimum annual salary in USD (e.g. 100000)
companySizeMinintegerOptionalMinimum company employee count (e.g. 50)
curl -X POST https://apply-api.boringproject.ai/api/v1/jobs/search \
  -H "Authorization: Bearer bp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "titles": ["Software Engineer", "Full Stack Developer"],
    "locations": ["Remote", "San Francisco, CA"],
    "userId": "usr_abc123",
    "results": 25,
    "filters": {
      "workType": ["Remote", "Hybrid"],
      "experienceLevel": ["mid", "senior"],
      "salaryMin": 100000
    }
  }'
Response200 OK
{
  "searchId": "srch_abc123",
  "query": "Software Engineer, Full Stack Developer",
  "resultsCount": 25,
  "jobs": [
    {
      "jobId": "48291",
      "title": "Senior Software Engineer",
      "companyName": "Acme Corp",
      "url": "https://boards.greenhouse.io/acme/jobs/48291",
      "jobBoard": "Greenhouse",
      "description": "We are looking for a Senior Software Engineer to join our platform team...",
      "datePosted": "2024-02-10",
      "location": "Remote, US",
      "salary": "$140,000 - $180,000",
      "salaryMin": 140000,
      "salaryMax": 180000,
      "salaryCurrency": "USD",
      "jobType": "full-time",
      "remote": true,
      "seniority": "senior",
      "companyDomain": "acmecorp.com",
      "logoUrl": "https://logo.clearbit.com/acmecorp.com",
      "companySize": 500,
      "companyIndustry": "Technology"
    },
    ...
  ]
}

Job Object Fields

Each job in the response array contains the following fields. Fields marked Always are guaranteed to be present. Fields marked Nullable may be null when data is not available.

NameTypePresenceDescription
jobIdstringAlwaysUnique job identifier
titlestringAlwaysJob title
companyNamestringAlwaysHiring company name
urlstringAlwaysDirect application URL — pass this as the link field in POST /sessions/apply
jobBoardstringAlwaysATS platform name (e.g. Greenhouse, Lever, Ashby)
descriptionstring | nullNullableFull job description text (may be null if source doesn't provide it)
datePostedstring | nullNullableDate the job was posted, YYYY-MM-DD (may be null)
locationstring | nullNullableJob location, e.g. "Remote, US" (may be null)
remotebooleanAlwaysWhether the job is remote
salarystring | nullNullableFormatted salary string (e.g. "$140,000 - $180,000")
salaryMinnumber | nullNullableMinimum annual salary in USD
salaryMaxnumber | nullNullableMaximum annual salary in USD
salaryCurrencystring | nullNullableSalary currency code
jobTypestringNullableEmployment type: full-time, part-time, contract, or internship
senioritystringNullableSeniority level of the role
companyDomainstringNullableCompany website domain
logoUrlstring | nullNullableCompany logo URL
companySizeinteger | nullNullableCompany employee count
companyIndustrystringNullableCompany industry

Search + Apply Flow

The typical workflow is to search for jobs, let the user select which ones to apply to, and then pass them to the Sessions apply endpoint. The job url field maps directly to the link field in the apply request.

Use the excludeJobIds parameter to pass previously applied job IDs and avoid duplicate applications.
# 1. Search for jobs
curl -X POST https://apply-api.boringproject.ai/api/v1/jobs/search \
  -H "Authorization: Bearer bp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"titles": ["Software Engineer"], "locations": ["Remote"]}'

# 2. Apply to selected jobs (using job URLs from search results)
curl -X POST https://apply-api.boringproject.ai/api/v1/sessions/apply \
  -H "Authorization: Bearer bp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "candidateProfileId": "prof_xyz789",
    "jobs": [
      { "companyName": "Acme Corp", "title": "Senior Software Engineer", "jobId": "48291", "link": "https://boards.greenhouse.io/acme/jobs/48291" }
    ]
  }'

Automatic Deduplication

The API automatically excludes jobs that were returned in previous searches for your account within the last 24 hours. Each search only returns fresh, never-before-seen jobs — so you can call the search endpoint repeatedly without worrying about duplicates.

This works by tracking the job IDs returned in your recent search records (within a 24-hour rolling window). When a new search is performed, those job IDs are excluded server-side so results are always fresh.

For Autopilot Recurring sessions, deduplication is even more specific — it filters out jobs that the candidate has already applied to (regardless of success or failure), ensuring each recurring run only applies to genuinely new positions.

You don't need to manage excludeJobIds manually for deduplication — the API handles it automatically. The excludeJobIds parameter is still available for additional client-side exclusions if needed.

Filter Behavior

Filter parameters (workType, jobType, experienceLevel) accept either a single string or an array of strings. When multiple values are provided, results matching any of the specified values are returned.

If fewer jobs match your filters than the requested results count, the API progressively relaxes filters to find more results. The relaxation order is: salary minimum and company size are dropped first, then experience level and job type, and finally work type. This ensures you always get the most relevant results possible while still meeting your target count.

Note: Hybrid work type is treated as non-remote for filtering purposes, since the underlying job data only distinguishes between fully remote and non-remote positions.

Titles and locations are never relaxed — results always match at least one of your specified job titles and locations.

List Past Searches

Retrieve a paginated list of your previous job searches. Each record includes the search query, filters used, result count, and how many applications were submitted from that search.

GET/searches
NameTypeRequiredDescription
pageintegerOptionalPage number (default: 1)
limitintegerOptionalItems per page, max 100 (default: 20)
curl "https://apply-api.boringproject.ai/api/v1/searches?page=1&limit=20" \
          -H "Authorization: Bearer bp_live_..."
Response200 OK
{
  "data": [
    {
      "searchId": "srch_abc123",
      "query": "Software Engineer, Full Stack Developer",
      "titles": ["Software Engineer", "Full Stack Developer"],
      "locations": ["Remote", "San Francisco, CA"],
      "filters": { "workType": ["Remote"], "experienceLevel": ["mid", "senior"] },
      "resultsCount": 25,
      "totalAvailable": 2413,
      "totalCompanies": 1208,
      "appliedCount": 5,
      "createdAt": "2024-02-14T10:30:00Z"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 12, "totalPages": 1 }
}

Get Search Record

Retrieve a specific search record by ID, including the full jobs array. Use this to show the original search results or to let users select jobs to apply to from a previous search.

Pass the searchId when creating a run_once session to automatically populate the session's search context with the original search titles and locations.
GET/searches/:searchId
curl "https://apply-api.boringproject.ai/api/v1/searches/srch_abc123" \
          -H "Authorization: Bearer bp_live_..."
Response200 OK
{
  "searchId": "srch_abc123",
  "query": "Software Engineer",
  "titles": ["Software Engineer"],
  "locations": ["Remote"],
  "filters": {},
  "resultsCount": 25,
  "totalAvailable": 2413,
  "totalCompanies": 1208,
  "appliedCount": 5,
  "jobs": [
    {
      "jobId": "48291",
      "title": "Senior Software Engineer",
      "companyName": "Acme Corp",
      "url": "https://boards.greenhouse.io/acme/jobs/48291",
      "jobBoard": "Greenhouse",
      "location": "Remote, US",
      "salary": "$140,000 - $180,000",
      "remote": true
    }
  ],
  "createdAt": "2024-02-14T10:30:00Z"
}

Rate Limiting

Job Search requests count toward your API key's 25 concurrent request limit (shared with Sessions). Each search holds a concurrent slot until the results are fully returned. If you exceed 25 simultaneous in-flight requests, additional requests will receive a 429 response.

For high-volume search use cases, stagger requests or wait for previous searches to complete before sending new ones. See the Rate Limiting guide for details on response headers and retry strategies.

Unlike session creation (which releases the slot once the session is created), search requests hold the concurrent slot until results are returned. Plan your request concurrency accordingly.