Discover/Ashbyhq API
live

Ashbyhq APIjobs.ashbyhq.com

Access company info, job listings, full job details, and application form structure from any Ashby-hosted job board via a clean REST API.

Endpoints
5
Updated
2mo ago

What is the Ashbyhq API?

This API exposes 5 endpoints covering company metadata, job listings, job details, application form questions, and keyword/department search across any Ashby-hosted job board at jobs.ashbyhq.com. Use get_company_job_listings to pull all open postings and team structure for a given company, or get_job_application_form to retrieve the exact fields a candidate must complete — down to question type, label, and whether the field is required.

Try it
Company slug as used in the job board URL (e.g., 'ramp' for jobs.ashbyhq.com/ramp)
api.parse.bot/scraper/fac3e9d6-234c-4bf2-b5aa-36d377c3884e/<endpoint>
Ready to send
Fill in the parameters and hit sign in to send to see live response data here.
Call it over HTTPgrab a free API key at signup
curl -X GET 'https://api.parse.bot/scraper/fac3e9d6-234c-4bf2-b5aa-36d377c3884e/get_company_info?company_slug=openai' \
  -H 'X-API-Key: $PARSE_API_KEY'
Python SDK · recommended

Typed, relational, agent-ready

A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.

  • Fully typed · autocompletes
  • Objects link to objects
  • Typed errors & pagination

Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:

uv add parse-sdk
uv run parse init
uv run parse add --marketplace jobs-ashbyhq-com-api

uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.

"""
Ashby Job Board API Client
Practical example for interacting with AshbyHQ job boards.
Get your API key from: https://parse.bot/settings
"""

import os
import requests
from typing import Any, Optional


class ParseClient:
    """Client for interacting with the Parse API for Ashby job boards."""

    def __init__(self, api_key: Optional[str] = None):
        """Initialize the Parse API client.
        
        Args:
            api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
        """
        self.base_url = "https://api.parse.bot"
        self.scraper_id = "fac3e9d6-234c-4bf2-b5aa-36d377c3884e"
        self.api_key = api_key or os.getenv("PARSE_API_KEY")
        
        if not self.api_key:
            raise ValueError("API key must be provided or set in PARSE_API_KEY environment variable")

    def _call(self, endpoint: str, method: str = "POST", **params) -> dict[str, Any]:
        """Make an API call to the Parse endpoint.
        
        Args:
            endpoint: The endpoint name (e.g., 'get_company_info')
            method: HTTP method ('GET' or 'POST')
            **params: Parameters to pass to the endpoint
            
        Returns:
            API response as a dictionary
            
        Raises:
            requests.HTTPError: If the request fails
        """
        url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json"
        }
        
        if method == "GET":
            response = requests.get(url, headers=headers, params=params, timeout=30)
        elif method == "POST":
            response = requests.post(url, headers=headers, json=params, timeout=30)
        else:
            raise ValueError(f"Unsupported HTTP method: {method}")
        
        response.raise_for_status()
        return response.json()

    def get_company_info(self, company_slug: str) -> dict[str, Any]:
        """Retrieve company-level metadata for an Ashby-hosted job board.
        
        Args:
            company_slug: Company slug as used in the job board URL (e.g., 'ramp')
            
        Returns:
            Company information including name, publicWebsite, theme, and timezone
        """
        result = self._call("get_company_info", method="GET", company_slug=company_slug)
        return result.get("data", {})

    def get_company_job_listings(self, company_slug: str) -> dict[str, Any]:
        """Retrieve all open job postings and teams for a company's job board.
        
        Args:
            company_slug: Company slug as used in the job board URL
            
        Returns:
            Dictionary with 'jobPostings' and 'teams' arrays
        """
        result = self._call("get_company_job_listings", method="GET", company_slug=company_slug)
        return result.get("data", {})

    def get_job_details(self, company_slug: str, job_id: str) -> dict[str, Any]:
        """Retrieve full details of a specific job posting.
        
        Args:
            company_slug: Company slug
            job_id: Job posting ID (UUID format)
            
        Returns:
            Job details including title, description, compensation, and location
        """
        result = self._call("get_job_details", method="GET", company_slug=company_slug, job_id=job_id)
        return result.get("data", {})

    def get_job_application_form(self, company_slug: str, job_id: str) -> dict[str, Any]:
        """Retrieve all application form questions for a specific job posting.
        
        Args:
            company_slug: Company slug
            job_id: Job posting ID (UUID format)
            
        Returns:
            Dictionary with job_id, job_title, and questions array
        """
        result = self._call("get_job_application_form", method="GET", company_slug=company_slug, job_id=job_id)
        return result.get("data", {})

    def search_jobs(self, company_slug: str, query: Optional[str] = None, 
                   department_id: Optional[str] = None) -> list[dict[str, Any]]:
        """Search for jobs within a company board by keyword and/or department.
        
        Args:
            company_slug: Company slug as used in the job board URL
            query: Search keyword to filter job titles (optional, case-insensitive)
            department_id: Team/department ID to filter by (optional)
            
        Returns:
            List of job postings matching the search criteria
        """
        params = {"company_slug": company_slug}
        if query:
            params["query"] = query
        if department_id:
            params["department_id"] = department_id
        result = self._call("search_jobs", method="GET", **params)
        return result.get("data", [])


def main():
    """Practical workflow demonstrating the Ashby Jobs API."""
    # Initialize client
    client = ParseClient()
    
    # Use a sample company
    company_slug = "ramp"
    
    print("=" * 80)
    print("ASHBY JOB BOARD API - PRACTICAL WORKFLOW")
    print("=" * 80)
    
    # Step 1: Get company information
    print(f"\n[1] Fetching company info for '{company_slug}'...")
    try:
        company_info = client.get_company_info(company_slug=company_slug)
        company_name = company_info.get("name", "Unknown")
        company_website = company_info.get("publicWebsite", "N/A")
        company_timezone = company_info.get("timezone", "N/A")
        allow_indexing = company_info.get("allowJobPostIndexing", False)
        
        print(f"    ✓ Company: {company_name}")
        print(f"    ✓ Website: {company_website}")
        print(f"    ✓ Timezone: {company_timezone}")
        print(f"    ✓ Jobs Indexed: {allow_indexing}")
    except Exception as e:
        print(f"    ✗ Error: {e}")
        return
    
    # Step 2: Get all job listings and teams
    print(f"\n[2] Fetching job listings and organizational structure...")
    try:
        listings = client.get_company_job_listings(company_slug=company_slug)
        job_postings = listings.get("jobPostings", [])
        teams = listings.get("teams", [])
        
        print(f"    ✓ Found {len(job_postings)} open positions")
        print(f"    ✓ Found {len(teams)} teams/departments")
        
        if teams:
            print(f"\n    Teams: {', '.join([t['name'] for t in teams[:5]])}")
            if len(teams) > 5:
                print(f"    ... and {len(teams) - 5} more")
        
        # Display sample jobs
        if job_postings:
            print(f"\n    Sample positions available:")
            for i, job in enumerate(job_postings[:5], 1):
                location = job.get("locationName", "Remote")
                comp = job.get("compensationTierSummary", "Not specified")
                print(f"    {i}. {job.get('title')} - {location}")
                print(f"       {comp}")
    except Exception as e:
        print(f"    ✗ Error: {e}")
        return
    
    # Step 3: Search for specific job types
    print(f"\n[3] Searching for 'Engineer' positions...")
    try:
        search_results = client.search_jobs(company_slug=company_slug, query="Engineer")
        
        print(f"    ✓ Found {len(search_results)} matching positions")
        engineer_jobs = search_results[:5]
        
        if engineer_jobs:
            print(f"\n    Matching roles:")
            for i, job in enumerate(engineer_jobs, 1):
                comp = job.get("compensationTierSummary", "Not specified")
                workplace = job.get("workplaceType", "N/A")
                emp_type = job.get("employmentType", "N/A")
                print(f"    {i}. {job.get('title')}")
                print(f"       Location: {job.get('locationName')} | {workplace} | {emp_type}")
                print(f"       Compensation: {comp}")
    except Exception as e:
        print(f"    ✗ Error: {e}")
        return
    
    # Step 4: Get detailed information for selected job
    if engineer_jobs:
        print(f"\n[4] Getting full details for selected role...")
        selected_job = engineer_jobs[0]
        job_id = selected_job.get("id")
        
        try:
            job_details = client.get_job_details(company_slug=company_slug, job_id=job_id)
            
            print(f"    ✓ Job ID: {job_details.get('id')}")
            print(f"    ✓ Title: {job_details.get('title')}")
            print(f"    ✓ Department: {job_details.get('departmentName', 'N/A')}")
            print(f"    ✓ Primary Location: {job_details.get('locationName')}")
            print(f"    ✓ Employment Type: {job_details.get('employmentType')}")
            print(f"    ✓ Workplace Type: {job_details.get('workplaceType')}")
            print(f"    ✓ Compensation: {job_details.get('compensationTierSummary', 'N/A')}")
            
            secondary_locs = job_details.get("secondaryLocationNames", [])
            if secondary_locs:
                print(f"    ✓ Secondary Locations: {', '.join(secondary_locs)}")
            
            team_names = job_details.get("teamNames", [])
            if team_names:
                print(f"    ✓ Team Hierarchy: {' → '.join(team_names)}")
            
            deadline = job_details.get("applicationDeadline")
            if deadline:
                print(f"    ✓ Application Deadline: {deadline}")
            
            # Step 5: Get application form requirements
            print(f"\n[5] Fetching application form requirements...")
            form_data = client.get_job_application_form(company_slug=company_slug, job_id=job_id)
            questions = form_data.get("questions", [])
            
            print(f"    ✓ Application form has {len(questions)} fields")
            
            required_fields = [q for q in questions if q.get("required")]
            optional_fields = [q for q in questions if not q.get("required")]
            
            if required_fields:
                print(f"\n    Required fields ({len(required_fields)}):")
                for q in required_fields[:8]:
                    field_type = q.get("type", "Unknown")
                    print(f"    • {q.get('label')} ({field_type})")
                if len(required_fields) > 8:
                    print(f"    ... and {len(required_fields) - 8} more")
            
            if optional_fields:
                print(f"\n    Optional fields ({len(optional_fields)}):")
                for q in optional_fields[:5]:
                    field_type = q.get("type", "Unknown")
                    print(f"    • {q.get('label')} ({field_type})")
                if len(optional_fields) > 5:
                    print(f"    ... and {len(optional_fields) - 5} more")
        
        except Exception as e:
            print(f"    ✗ Error: {e}")
            return
    
    # Step 6: Filter jobs by department
    if teams:
        print(f"\n[6] Filtering jobs by department...")
        sample_team = teams[0]
        team_id = sample_team.get("id")
        team_name = sample_team.get("name")
        
        try:
            dept_jobs = client.search_jobs(company_slug=company_slug, department_id=team_id)
            print(f"    ✓ Found {len(dept_jobs)} positions in '{team_name}' department")
            
            if dept_jobs:
                print(f"\n    Positions in {team_name}:")
                for i, job in enumerate(dept_jobs[:5], 1):
                    print(f"    {i}. {job.get('title')} ({job.get('workplaceType')})")
                if len(dept_jobs) > 5:
                    print(f"    ... and {len(dept_jobs) - 5} more")
        except Exception as e:
            print(f"    ✗ Error: {e}")
    
    # Step 7: Multi-criteria search
    print(f"\n[7] Advanced search: 'Manager' in Sales department...")
    try:
        sales_team = next((t for t in teams if "sales" in t.get("name", "").lower()), None)
        if sales_team:
            manager_sales = client.search_jobs(
                company_slug=company_slug,
                query="Manager",
                department_id=sales_team.get("id")
            )
            print(f"    ✓ Found {len(manager_sales)} Manager positions in {sales_team.get('name')}")
            if manager_sales:
                for job in manager_sales[:3]:
                    print(f"    • {job.get('title')}")
        else:
            print(f"    ! No sales team found in organization")
    except Exception as e:
        print(f"    ✗ Error: {e}")
    
    print("\n" + "=" * 80)
    print("Workflow completed successfully!")
    print("=" * 80)


if __name__ == "__main__":
    main()
All endpoints · 5 totalmissing one? ·

Retrieve company-level metadata for an Ashby-hosted job board by company slug. Returns stale_input if the company slug does not exist on Ashby.

Input
ParamTypeDescription
company_slugrequiredstringCompany slug as used in the job board URL (e.g., 'ramp' for jobs.ashbyhq.com/ramp)
Response
{
  "type": "object",
  "fields": {
    "name": "string — company display name",
    "theme": "object — visual theme settings including colors, logos, and display flags",
    "timezone": "string — IANA timezone identifier",
    "publicWebsite": "string — company public website URL",
    "customJobsPageUrl": "string or null",
    "activeFeatureFlags": "array of strings — enabled feature flag names",
    "hostedJobsPageSlug": "string — the slug used in the Ashby URL",
    "allowJobPostIndexing": "boolean",
    "recruitingPrivacyPolicyUrl": "string or null"
  },
  "sample": {
    "data": {
      "name": "Ramp",
      "theme": {
        "colors": {
          "version": "1",
          "colorPrimary600": "#1F1F1F"
        },
        "showTeams": false,
        "showJobFilters": true,
        "showLocationAddress": false,
        "showAutofillApplicationsBox": true
      },
      "timezone": "America/New_York",
      "publicWebsite": "https://ramp.com",
      "customJobsPageUrl": null,
      "activeFeatureFlags": [
        "ApplicationForReferrals",
        "JobPostingListV2"
      ],
      "hostedJobsPageSlug": "ramp",
      "allowJobPostIndexing": true,
      "recruitingPrivacyPolicyUrl": "https://ramp.com/legal/applicant-privacy-notice"
    },
    "status": "success"
  }
}

About the Ashbyhq API

Company and Job Listing Data

Start with get_company_info by passing a company_slug (the identifier in the board URL, e.g. ramp for jobs.ashbyhq.com/ramp). The response includes the company display name, IANA timezone, public website URL, visual theme settings (colors, logos, display flags), and whether job posts are allowed to be indexed. If the slug doesn't match a live Ashby board, the endpoint returns stale_input.

get_company_job_listings returns two parallel arrays: teams (with id, name, externalName, and parentTeamId for hierarchy reconstruction) and jobPostings (with id, title, teamId, locationId, locationName, workplaceType, employmentType, and compensation tier summaries). This is the primary way to enumerate a company's full open headcount.

Job Details and Application Forms

get_job_details accepts a job_id UUID plus the company_slug and returns the full HTML job description (descriptionHtml), team hierarchy via teamNames, workplaceType (Remote / Hybrid / OnSite), employmentType, structured compensationTiers with titles and summaries, and an applicationDeadline in ISO format when set.

get_job_application_form retrieves the complete set of form questions for a posting: each question object carries an id, path, label, type, required flag, description, and options for select-type fields. This makes it possible to build a local representation of the application flow without visiting the board directly.

Search and Filtering

search_jobs accepts a company_slug, an optional query string for case-insensitive title matching, and an optional department_id UUID. When department_id is supplied, results include jobs in that team and all its child teams, so filtering by a parent department surfaces the full subtree. Team UUIDs come from the teams array returned by get_company_job_listings.

Reliability & maintenance

The Ashbyhq API is a managed, monitored endpoint for jobs.ashbyhq.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when jobs.ashbyhq.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.

This isn't an official jobs.ashbyhq.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.

Will this API break when the source site changes?+
It's built not to. Every endpoint is health-checked on a schedule with automated test probes. When the source site changes and a check fails, the API is automatically queued for repair and re-verified — that's the self-healing layer. Each API page shows when its endpoints were last verified. And because marketplace APIs are shared, any fix reaches everyone using it.
Is this an official API from the source site?+
No — Parse APIs are independent, managed REST wrappers over publicly available data. That is the point: where a site has no official API (or only a limited one), Parse gives you a maintained, monitored endpoint for that data and keeps it working as the site changes — so you get a stable contract over a source that never promised one.
Can I fix or extend this API myself if I need a new endpoint or field?+
Yes — and you don't have to wait on us. This API was generated by the Parse agent, which stays attached. Describe the change in plain English ("add an endpoint that returns reviews", "fix the price field") in the revise box on the API page or via the revise_api MCP tool, and the agent rebuilds it against the live site in minutes. Contributing the change back to the public API is free.
What happens if I call an endpoint that has an issue?+
Errors are machine-readable: a bad call returns a clean status with the list of available endpoints and a repair hint, so an agent (or you) can recover or trigger a fix instead of failing silently. Confirmed failures feed the automatic repair queue.
Common use cases
  • Aggregate open roles across multiple Ashby-hosted company boards into a single job feed
  • Filter remote or hybrid postings by workplaceType for a remote-jobs aggregator
  • Extract compensationTiers data to compare pay ranges across companies in the same sector
  • Reconstruct department hierarchies using parentTeamId from the teams array
  • Pre-fill or validate application form fields by fetching question types and options via get_job_application_form
  • Monitor new job postings for a specific company slug to trigger alerts when headcount changes
  • Search engineering or product roles within a specific department subtree using department_id in search_jobs
Pricing & limitsSee full pricing →
TierPriceCredits/monthRate limit
Free$0/mo1005 req/min
Hobby$30/mo1,00020 req/min
Developer$100/mo5,000100 req/min

One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.

Frequently asked questions
Does Ashby provide an official public developer API?+
Ashby does not publish a general-purpose public API for its hosted job boards. This Parse API provides structured access to data that Ashby exposes through its job board pages at jobs.ashbyhq.com.
What does `get_job_application_form` actually return — is it enough to know what questions a candidate faces?+
Yes. The endpoint returns the full list of form questions for a given job posting, including each question's label, type, required status, description, and options (for select or radio fields). You get enough structure to map the complete form without loading the board in a browser.
Can I retrieve closed or archived job postings through this API?+
The API covers currently open job postings as they appear on active Ashby-hosted boards. Closed or archived postings are not exposed. You can fork the API on Parse and revise it to target any additional endpoints that surface historical or closed postings if Ashby makes them accessible.
Does the API cover job boards hosted on custom domains rather than jobs.ashbyhq.com?+
The API is scoped to boards served under jobs.ashbyhq.com, identified by their company slug. Companies using a fully custom domain for their Ashby board are not currently covered. You can fork the API on Parse and revise it to add support for custom-domain boards.
How does department filtering work in `search_jobs`, and where do I get the department IDs?+
Pass a department_id UUID to search_jobs and the results will include jobs in that team and every child team beneath it in the hierarchy. Department/team UUIDs come from the teams array returned by get_company_job_listings, where parentTeamId lets you trace the tree.
Page content last updated . Spec covers 5 endpoints from jobs.ashbyhq.com.
Related APIs in JobsSee all →
ashbyhq.com API
Search and filter job postings from Ashby-powered job boards by title, department, and experience level, then retrieve detailed information about specific positions.
job-boards.greenhouse.io API
Access job listings, department information, office locations, and application forms from Greenhouse.io job boards, then submit applications directly through the platform. Search and filter available positions by keywords to find roles that match your qualifications.
jobs.lever.co API
Access job postings on any Lever-hosted company job board. List, filter, search, and group open roles, retrieve full posting details, and extract application form questions via Lever's public API.
welcometothejungle.com API
Search and discover job postings and company information from Welcome to the Jungle, including detailed job listings, company profiles with employee statistics and social links, and the ability to browse all available positions. Find the perfect role by searching jobs and companies, then access comprehensive details about positions and organizations in one place.
indeed.com API
Search and discover job opportunities on Indeed while accessing detailed job descriptions, company profiles, and salary insights all in one place. Get comprehensive career information including specific compensation data to help you find and evaluate the right job opportunity for you.
totaljobs.com API
Search and browse job listings from across the UK on TotalJobs, then access detailed information about specific positions including requirements, salary, and application details. Quickly compare opportunities and find roles that match your criteria.
shine.com API
Search and discover job listings on Shine.com with detailed information including job descriptions, categories, locations, and top hiring companies. Find similar job opportunities and explore roles across different industries and geographical areas to match your career goals.
indeed.co.uk API
Search for jobs across Indeed UK and retrieve detailed information including job listings, application links, and company profiles. Access comprehensive job data to compare opportunities, learn about employers, and find direct application pathways.