API Documentation

Everything you need to integrate JobsAPI into your application.

Base URL: https://api.jobsapi.com/api/v1 Try the API Explorer

Getting Started

1. Create an Account

Sign up at api.jobsapi.com/register. You'll need:

  • Email address
  • Password (8+ characters)
  • Name (optional)
  • Company name (optional)

2. Verify Your Email

Check your inbox for a verification email and click the link. You must verify before you can log in.

3. Generate an API Key

  1. Log in at api.jobsapi.com/login
  2. Go to Dashboard > API Keys
  3. Click Create API Key
  4. Copy the key immediately — it won't be shown again

Your key looks like: japi_a1b2c3d4e5f6...

4. Make Your First Request

With curl

curl -X POST https://api.jobsapi.com/api/v1/signals/search \
  -H "X-API-Key: japi_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "skills": ["Python"],
      "signal_countries": ["US"]
    },
    "limit": 5
  }'

With the Python SDK

pip install jobsapi
import os
from jobsapi import JobsAPI, SignalFilters

client = JobsAPI(api_key=os.environ["JOBSAPI_KEY"])

results = client.search_signals(
    SignalFilters(skills=["Python"], signal_countries=["US"]),
    limit=5,
)

print(f"Found {results.total_count} signals")
for signal in results.signals:
    print(f"  {signal.title} - {signal.company_name} ({signal.location})")

5. Explore the API

Next Steps

Authentication

All API requests require an API key passed in the X-API-Key header.

Using Your API Key

curl -H "X-API-Key: japi_your_key_here" https://api.jobsapi.com/api/v1/filters/options
from jobsapi import JobsAPI

client = JobsAPI(api_key="japi_your_key_here")

Key Format

API keys are prefixed with japi_ followed by 64 hex characters:

japi_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2

Keys are hashed on the server — if you lose it, generate a new one.

Key Scopes

Scope Access
full All endpoints your plan allows
read Read-only endpoints (GET requests)

Managing Keys

From your dashboard:

  • Create — generate a new key with a name and scope
  • Rename — update the key's name for your reference
  • Revoke — permanently disable a key

You can have multiple active keys (e.g., separate keys per environment).

Error Responses

401 Unauthorized

Missing or invalid API key.

{"detail": "Invalid API key"}

403 Forbidden

Valid key but insufficient plan tier or missing permission.

{"detail": "Your plan (Free) does not include access to this endpoint. Upgrade to Starter or higher."}

Key not working?

  1. Verify the key is correct (no extra whitespace)
  2. Check you're using the X-API-Key header (not Authorization)
  3. Ensure your email is verified
  4. Check if the key has been revoked in your dashboard

Rate Limits

JobsAPI enforces both per-minute rate limits and monthly request quotas.

Limits by Plan

Plan Rate Limit Monthly Quota Max Results
Free 10 req/min 100 requests 25 per request
Starter ($49/mo) 60 req/min 5,000 requests 100 per request
Professional ($199/mo) 300 req/min 50,000 requests 1,000 per request

Request Cost

Not all requests cost the same against your rate limit:

Endpoint Cost
GET (single resource) 0.5
Search (signals, companies, semantic) 1
Analytics (aggregate, timeseries) 2
Enrich company 2
Batch operations 3

Rate Limit Headers

Every API response includes rate limit headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1706745600
Header Description
X-RateLimit-Limit Your per-minute limit
X-RateLimit-Remaining Requests remaining in current window
X-RateLimit-Reset Unix timestamp when the window resets

Handling 429 Errors

When you exceed limits, the API returns 429 Too Many Requests:

{"detail": "Rate limit exceeded. Slow down."}

For monthly quota exceeded:

{"detail": "Monthly request limit (100) exceeded. Upgrade your plan for higher limits."}

Retry Strategy

The Python SDK retries on 429 automatically (up to 3 attempts). For manual handling:

import time

def request_with_backoff(fn, *args, **kwargs):
    for attempt in range(3):
        try:
            return fn(*args, **kwargs)
        except RateLimitError as e:
            if e.retry_after:
                time.sleep(e.retry_after)
            else:
                time.sleep(2 ** attempt)
    raise

Tips

  • Use batch endpoints instead of many individual requests
  • Cache filter_options() results (they change infrequently)
  • Use group_by_company=True in signal search to reduce result volume
  • Monitor your usage at api.jobsapi.com/dashboard/billing

Pricing

Plans

Feature Free Starter ($49/mo) Professional ($199/mo)
Monthly requests 100 5,000 50,000
Rate limit 10/min 60/min 300/min
Max results per request 25 100 1,000
Signal search yes yes yes
Company search yes yes yes
Semantic title search yes yes yes
Filter options yes yes yes
Batch operations - yes yes
Analytics - yes yes
Company enrichment - yes yes

Endpoint Access by Plan

Free

  • POST /signals/search — search job signals
  • GET /signals/{id} — get single signal
  • POST /companies/search — search companies
  • GET /companies/{id} — get single company
  • POST /semantic-titles/search — semantic job title search
  • GET /filters/options — available filter values

Starter (adds)

  • POST /signals/batch — batch fetch signals by IDs
  • POST /signals/batch-search — multi-query search
  • POST /signals/aggregate — signal aggregation
  • POST /signals/timeseries — signal timeseries
  • POST /signals/timeseries_by_source_country — multi-dimensional timeseries
  • POST /signals/filter-impact — filter impact analysis
  • POST /companies/batch — batch fetch companies
  • POST /companies/enrich — company enrichment

Professional

No additional endpoints over Starter — Professional raises the monthly request quota, per-minute rate limit, and max results per request (see the Plans table above).

Upgrading

Manage your subscription at api.jobsapi.com/dashboard/billing.

Plan changes take effect immediately. You're billed monthly via Stripe.

Signals API

Job signals represent individual job postings discovered from multiple sources.

Search Signals

POST /api/v1/signals/searchFree+

Search for job signals with comprehensive filtering.

Request

{
  "filters": {
    "skills": ["Python", "FastAPI"],
    "signal_countries": ["US", "UK"],
    "yearly_salary_min": 100000,
    "seniority_levels": ["Senior"],
    "enriched_only": true
  },
  "limit": 100,
  "cursor": null,
  "group_by_company": false,
  "signals_per_company": 5
}
Field Type Default Description
filters object required See Filters Reference
limit int 100 Results per page (max varies by plan)
cursor int null Pagination cursor from previous response
group_by_company bool false Group results by company, return most recent signal per company
signals_per_company int 5 Signals per company when grouping (1-20)

Response

{
  "signals": [
    {
      "id": 12345,
      "title": "Senior Python Developer",
      "url": "https://example.com/job/12345",
      "company_id": 100,
      "company_name": "Acme Corp",
      "location": "San Francisco, CA",
      "country": "US",
      "posted_at": "2025-01-15T10:00:00Z",
      "source": "linkedin",
      "remote_type": "Hybrid",
      "yearly_salary_usd": 150000,
      "seniority_level": "Senior",
      "apply_url": "https://example.com/apply/12345",
      "relevance_score": 0.85,
      "title_similarity": 0.92,
      "company_signal_count": 15
    }
  ],
  "companies": {
    "100": {
      "id": 100,
      "name": "Acme Corp",
      "domain": "acme.com",
      "industry": "Technology",
      "headcount": 500,
      "country": "US"
    }
  },
  "next_cursor": 12345,
  "has_more": true,
  "total_count": 2500
}

SDK

from jobsapi import JobsAPI, SignalFilters

client = JobsAPI(api_key="japi_...")
results = client.search_signals(
    SignalFilters(
        skills=["Python"],
        signal_countries=["US"],
        yearly_salary_min=100000,
    ),
    limit=10,
)

Get Signal

GET /api/v1/signals/{id}Free+

Get a single signal by ID.

Response

Returns a single signal object (same shape as in search results).

SDK

signal = client.get_signal(12345)

Batch Get Signals

POST /api/v1/signals/batchStarter+

Fetch multiple signals by ID in one request.

Request

{
  "ids": [1, 2, 3, 4, 5]
}

Max 1000 IDs. No duplicates allowed.

Response

{
  "signals": [...]
}

SDK

signals = client.batch_signals([1, 2, 3, 4, 5])

Batch Search

POST /api/v1/signals/batch-searchStarter+

Run multiple search queries in one request. Returns unique signals matching at least one query.

Request

{
  "queries": [
    {
      "filters": {"skills": ["Python"], "signal_countries": ["US"]},
      "limit": 50
    },
    {
      "filters": {"skills": ["Go"], "signal_countries": ["US"]},
      "limit": 50
    }
  ]
}

Max 50 queries per request.

Response

{
  "signals": [...],
  "companies": {...},
  "total_count": 87
}

SDK

from jobsapi import BatchSearchQuery, SignalFilters

results = client.batch_search([
    BatchSearchQuery(filters=SignalFilters(skills=["Python"]), limit=50),
    BatchSearchQuery(filters=SignalFilters(skills=["Go"]), limit=50),
])

Signal Fields

Field Type Description
id int Unique signal ID
url string Original job posting URL
title string Job title
company_id int Associated company ID
company_name string Company name
location string Job location text
country string ISO country code
posted_at datetime When the job was posted
source string Source platform (linkedin, indeed, etc.)
description string Full job description
remote_type string Remote, Hybrid, On-site
salary_raw string Original salary text
salary_min int Parsed minimum salary
salary_max int Parsed maximum salary
salary_currency string Salary currency code
salary_period string yearly, monthly, hourly
yearly_salary_usd float Normalized yearly salary in USD
seniority_level string Entry, Mid, Senior, Lead, Executive
apply_url string Direct application URL
relevance_score float Relevance ranking score
title_similarity float Semantic similarity (when using semantic_query)
company_signal_count int Total signals for company (when group_by_company=true)

Companies API

Companies are enriched with LinkedIn data, industry classification, headcount, and more.

Search Companies

POST /api/v1/companies/searchFree+

Search companies by their job signals. Returns companies grouped with signal counts and previews.

Request

{
  "filters": {
    "skills": ["Python"],
    "signal_countries": ["US"]
  },
  "sort_by": "signal_count",
  "sort_order": "desc",
  "limit": 50,
  "offset": 0,
  "signals_per_company": 3
}
Field Type Default Description
filters object required Same signal filters as signal search
sort_by string signal_count signal_count, latest_signal, name, headcount
sort_order string desc asc or desc
limit int 50 Companies per page (max 200)
offset int 0 Pagination offset
signals_per_company int 3 Preview signals per company (0-10)

Response

{
  "companies": [
    {
      "company": {
        "id": 100,
        "name": "Acme Corp",
        "domain": "acme.com",
        "industry": "Technology",
        "headcount": 500,
        "country": "US",
        "headquarters": "San Francisco, CA"
      },
      "signal_count": 15,
      "latest_signal_at": "2025-01-15T10:00:00Z",
      "signals": [
        {"id": 1, "title": "Senior Python Developer", "source": "linkedin", "posted_at": "2025-01-15T10:00:00Z", "location": "San Francisco"}
      ]
    }
  ],
  "total_company_count": 250,
  "offset": 0,
  "has_more": true
}

SDK

results = client.search_companies(
    SignalFilters(skills=["Python"]),
    sort_by="signal_count",
    limit=20,
)
for item in results.companies:
    print(f"{item.company.name}: {item.signal_count} jobs")

Get Company

GET /api/v1/companies/{id}Free+

Get a single company by ID.

SDK

company = client.get_company(100)

Batch Get Companies

POST /api/v1/companies/batchStarter+

Fetch multiple companies by ID.

Request

{
  "ids": [100, 200, 300]
}

SDK

companies = client.batch_companies([100, 200, 300])

Enrich Company

POST /api/v1/companies/enrichStarter+

Look up and enrich a company by name, domain, or LinkedIn. At least one identifier required.

Request

{
  "name": "Stripe",
  "domain": "stripe.com",
  "linkedin_slug": "stripe"
}
Field Type Description
name string Company name
domain string Company website domain
company_link string Full company URL
linkedin_slug string LinkedIn company slug

Response

{
  "company": {
    "id": 500,
    "name": "Stripe",
    "domain": "stripe.com",
    "industry": "Financial Services",
    "headcount": 8000,
    "country": "US"
  },
  "confidence": 95,
  "source": "linkedin"
}

SDK

result = client.enrich_company(name="Stripe", domain="stripe.com")
print(f"{result.company.name} - {result.confidence}% confidence")

Company Fields

Field Type Description
id int Unique company ID
name string Company name
domain string Website domain
linkedin_slug string LinkedIn URL slug
linkedin_uid string LinkedIn numeric ID
headquarters string HQ location
location string Primary location
country string Country code
industry string Primary industry
specialties string[] Company specialties
headline string Company tagline
description string Company description
website string Full website URL
type string Company type
founded string Year founded
headcount int Employee count
is_job_board bool Whether this is a job board

Filters Reference

Get Filter Options

GET /api/v1/filters/optionsFree+

Returns all available values for dropdown/autocomplete fields. Cache this response — values change infrequently.

Response

{
  "sources": ["linkedin", "indeed", "glassdoor", "workday"],
  "signal_countries": ["US", "UK", "DE", "CA", "AU", ...],
  "company_countries": ["US", "UK", "DE", ...],
  "industries": ["Technology", "Financial Services", "Healthcare", ...],
  "skills": ["Python", "JavaScript", "Go", "Kubernetes", ...],
  "remote_types": ["Remote", "Hybrid", "On-site"],
  "seniority_levels": ["Intern", "Entry", "Mid", "Senior", "Lead", "Executive"]
}

SDK

options = client.filter_options()
print("Available skills:", options.skills[:10])
print("Countries:", options.signal_countries)

Signal Filter Fields

These fields are used in filters for signal search, company search, analytics, and filter impact endpoints.

Text Search

Field Type Description
semantic_query string[] Semantic job title search — one or more terms, each expands to related titles via embeddings
search_terms string[] Match title OR description (OR logic)
title_search_terms string[] Match title only
description_search_terms string[] Match description only
stop_words string[] Exclude if found in title OR description
title_stop_words string[] Exclude if found in title
description_stop_words string[] Exclude if found in description
company_search_terms string[] Match company name/description
company_stop_words string[] Exclude if found in company fields

Skills

Field Type Description
skills string[] Include signals with ALL of these skills (AND logic)
exclude_skills string[] Exclude signals with ANY of these skills (OR logic)

Salary

Field Type Description
yearly_salary_min float Minimum yearly salary in USD
yearly_salary_max float Maximum yearly salary in USD

Remote Type

Field Type Description
remote_types string[] Include: Remote, Hybrid, On-site (OR logic)
exclude_remote_types string[] Exclude remote types (OR logic)

Seniority

Field Type Description
seniority_levels string[] Include seniority levels (OR logic)
exclude_seniority_levels string[] Exclude seniority levels (OR logic)

Location

Field Type Description
signal_countries string[] Include signals from these countries
exclude_signal_countries string[] Exclude signals from these countries
location_patterns string[] Match location text patterns (2-100 chars each)

Source & Time

Field Type Description
sources string[] Filter by signal sources (linkedin, indeed, etc.)
posted_after datetime Only signals posted after this time

Flags

Field Type Default Description
enriched_only bool true Only signals with enriched company data
healthy_only bool true Only signals from healthy companies
has_apply_url bool false Only signals with apply URLs
exclude_staffing bool false Exclude staffing agencies

Company Filters

Nested under company_filters:

Field Type Description
industries string[] Filter by company industries
industries_mode string include or exclude
headcount_min int Minimum employee count
headcount_max int Maximum employee count
unknown_headcount bool Include companies with unknown headcount
company_countries string[] Include companies from these countries
exclude_company_countries string[] Exclude companies from these countries
open_jobs_min int Min signals per company
open_jobs_max int Max signals per company

ID Filters

Field Type Description
company_ids int[] Only signals from these company IDs
exclude_company_ids int[] Exclude signals from these company IDs
exclude_signal_ids int[] Exclude these signal IDs

Role Datasets

Field Type Description
role_dataset_ids string[] Filter to signals matching these role datasets (UUIDs)
role_dataset_min_score int Min score for role dataset filter (0-100)

Example: Complex Filter

from jobsapi import SignalFilters, CompanyFilters

filters = SignalFilters(
    skills=["Python", "FastAPI"],
    exclude_skills=["Java"],
    signal_countries=["US", "UK"],
    yearly_salary_min=120000,
    seniority_levels=["Senior", "Lead"],
    remote_types=["Remote", "Hybrid"],
    exclude_staffing=True,
    posted_after="2025-01-01T00:00:00Z",
    company_filters=CompanyFilters(
        headcount_min=50,
        headcount_max=5000,
        industries=["Technology", "Financial Services"],
    ),
)

results = client.search_signals(filters, limit=50)

Analytics API

Aggregate and analyze signal data. Starter plan and above.

Aggregate Signals

POST /api/v1/signals/aggregateStarter+

Aggregate signal counts by source and country.

Request

{
  "filters": {
    "skills": ["Python"],
    "signal_countries": ["US", "UK", "DE"]
  }
}

Filters are optional. Without filters, aggregates all signals.

Response

{
  "data": [
    {"source": "linkedin", "country": "US", "count": 1200},
    {"source": "indeed", "country": "US", "count": 800},
    {"source": "linkedin", "country": "UK", "count": 400}
  ],
  "total": 2400
}

SDK

result = client.aggregate_signals(SignalFilters(skills=["Python"]))
for item in result.data:
    print(f"{item.source} / {item.country}: {item.count}")

Timeseries

POST /api/v1/signals/timeseriesStarter+

Get daily signal counts over time.

Request

{
  "filters": {
    "skills": ["Python"]
  },
  "start_date": "2025-01-01",
  "end_date": "2025-01-31"
}
Field Type Description
filters object Optional signal filters
start_date date Start of date range
end_date date End of date range

Response

{
  "data": [
    {"date": "2025-01-01", "count": 150},
    {"date": "2025-01-02", "count": 180},
    {"date": "2025-01-03", "count": 120}
  ],
  "total": 450
}

SDK

from datetime import date

result = client.timeseries(
    SignalFilters(skills=["Python"]),
    start_date=date(2025, 1, 1),
    end_date=date(2025, 1, 31),
)
for point in result.data:
    print(f"{point.date}: {point.count}")

Timeseries by Source & Country

POST /api/v1/signals/timeseries_by_source_countryStarter+

Multi-dimensional timeseries broken down by source and country.

Request

Same as timeseries.

Response

{
  "data": [
    {"source": "linkedin", "country": "US", "date": "2025-01-01", "count": 80},
    {"source": "indeed", "country": "US", "date": "2025-01-01", "count": 40},
    {"source": "linkedin", "country": "UK", "date": "2025-01-01", "count": 30}
  ],
  "total": 150
}

SDK

result = client.timeseries_by_source_country(
    SignalFilters(skills=["Python"]),
    start_date=date(2025, 1, 1),
    end_date=date(2025, 1, 31),
)

Filter Impact

POST /api/v1/signals/filter-impactStarter+

Analyze how each active filter affects the result count. Useful for understanding which filters are most restrictive.

Request

{
  "filters": {
    "skills": ["Python"],
    "signal_countries": ["US"],
    "yearly_salary_min": 100000
  }
}

Response

{
  "current_count": 500,
  "impacts": [
    {
      "filter_name": "yearly_salary_min",
      "display_name": "Min Salary",
      "filter_value": "100000",
      "current_count": 500,
      "count_without_filter": 2000,
      "signals_gained": 1500,
      "breakdown": {
        "no_data_count": 800,
        "different_values": [
          ["50000-75000", 400],
          ["75000-100000", 300]
        ]
      }
    }
  ],
  "field_coverage": [
    {
      "field_name": "yearly_salary_usd",
      "display_name": "Salary",
      "total_with_data": 1200,
      "total_without_data": 800
    }
  ],
  "default_filter_insights": {
    "total_signals": 5000,
    "enriched_count": 4000,
    "healthy_count": 4500,
    "both_count": 3800
  }
}

SDK

impact = client.filter_impact(
    SignalFilters(skills=["Python"], yearly_salary_min=100000)
)
print(f"Current results: {impact.current_count}")
for item in impact.impacts:
    print(f"  {item.display_name}: removing gains +{item.signals_gained} signals")

JobsAPI Python SDK

Frozen — superseded by the generated plane SDK (signalsapi-3791). This client is hand-maintained: its response models are transcribed by hand from a contract they never read, which is exactly the drift the "one contract" SDK pipeline exists to prevent. New work belongs in the generated SDK, which is emitted from the plane's own OpenAPI schema by signals-service/signals/generate_sdks.py and carries the contract hash it was cut from.

This package is not deleted and still works: it targets the jobs-api gateway (api.jobsapi.com), a different base URL with its own consumers, and retiring it is a gateway decision rather than an SDK-pipeline one. Treat it as frozen — fix what breaks, add nothing new.

Official Python client for the JobsAPI job market data platform.

Installation

pip install jobsapi

Quick Start

from jobsapi import JobsAPI, SignalFilters

client = JobsAPI(api_key="japi_your_key_here")

# Search for Python jobs in the US
results = client.search_signals(
    SignalFilters(skills=["Python"], signal_countries=["US"]),
    limit=10,
)

for signal in results.signals:
    print(f"{signal.title} at {signal.company_name}")

Async Support

from jobsapi import AsyncJobsAPI, SignalFilters

async with AsyncJobsAPI(api_key="japi_...") as client:
    results = await client.search_signals(
        SignalFilters(skills=["Python"]),
    )

Features

  • Sync and async clients
  • Typed response models (Pydantic v2)
  • Automatic retry on rate limits (429)
  • All API endpoints covered: signals, companies, semantic search, analytics

Documentation

See the full documentation for API reference, guides, and examples.

Development

pip install -e ".[dev]"
pytest