API Documentation
Everything you need to integrate JobsAPI into your application.
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
- Log in at api.jobsapi.com/login
- Go to Dashboard > API Keys
- Click Create API Key
- 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
- Browse available filter options to see what you can filter by
- Try company search to find hiring companies
- Use semantic search to find related job titles
- Check the examples for more use cases
Next Steps
- Authentication — how API keys work
- Rate Limits — understand your plan's limits
- Pricing — compare plans and features
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?
- Verify the key is correct (no extra whitespace)
- Check you're using the
X-API-Keyheader (notAuthorization) - Ensure your email is verified
- 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=Truein 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 signalsGET /signals/{id}— get single signalPOST /companies/search— search companiesGET /companies/{id}— get single companyPOST /semantic-titles/search— semantic job title searchGET /filters/options— available filter values
Starter (adds)
POST /signals/batch— batch fetch signals by IDsPOST /signals/batch-search— multi-query searchPOST /signals/aggregate— signal aggregationPOST /signals/timeseries— signal timeseriesPOST /signals/timeseries_by_source_country— multi-dimensional timeseriesPOST /signals/filter-impact— filter impact analysisPOST /companies/batch— batch fetch companiesPOST /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/search — Free+
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/batch — Starter+
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-search — Starter+
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/search — Free+
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/batch — Starter+
Fetch multiple companies by ID.
Request
{
"ids": [100, 200, 300]
}
SDK
companies = client.batch_companies([100, 200, 300])
Enrich Company
POST /api/v1/companies/enrich — Starter+
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 |
Semantic Search API
AI-powered job title search. Find related job titles using embeddings and technology constraints.
Search Semantic Titles
POST /api/v1/semantic-titles/search — Free+
Given a query like "python backend developer", finds semantically similar job titles ranked by relevance and signal volume.
Request
{
"query": "python backend developer",
"limit": 20,
"exclude_languages": ["Java"],
"exclude_platforms": [],
"exclude_domains": []
}
| Field | Type | Default | Description |
|---|---|---|---|
query |
string | required | Job title query (1-255 chars) |
limit |
int | 50 | Max results (1-200) |
exclude_languages |
string[] | null | Exclude titles with these languages |
exclude_platforms |
string[] | null | Exclude titles with these platforms |
exclude_domains |
string[] | null | Exclude titles with these domains |
Response
{
"results": [
{
"title": "Python Backend Developer",
"similarity": 0.98,
"signal_count": 450,
"score": 441.0
},
{
"title": "Senior Python Engineer",
"similarity": 0.91,
"signal_count": 320,
"score": 291.2
},
{
"title": "Backend Software Engineer - Python",
"similarity": 0.87,
"signal_count": 180,
"score": 156.6
}
],
"query": "python backend developer",
"constraints": {
"language": "python",
"platform": null,
"domain": null
},
"total_filtered": 3
}
How It Works
- The query is parsed for technology constraints (language, platform, domain)
- An embedding is generated for the query
- Job titles are ranked by cosine similarity
- Results are scored:
similarity * signal_count— balancing relevance with market signal volume
Use Cases
- Discover related titles: "data engineer" → Data Engineer, Analytics Engineer, Big Data Engineer, ETL Developer
- Technology-specific search: "react developer" → React Developer, Frontend React Engineer, React Native Developer
- Scope exploration: Understand what job titles exist for a given role
SDK
results = client.search_semantic_titles("python backend developer", limit=10)
for r in results.results:
print(f"{r.title} (similarity: {r.similarity:.2f}, signals: {r.signal_count})")
Result Fields
| Field | Type | Description |
|---|---|---|
title |
string | Job title |
similarity |
float | Cosine similarity to query (0-1) |
signal_count |
int | Number of signals with this title |
score |
float | Combined relevance score |
Constraints Fields
| Field | Type | Description |
|---|---|---|
language |
string | Detected programming language constraint |
platform |
string | Detected platform constraint |
domain |
string | Detected domain/industry constraint |
Filters Reference
Get Filter Options
GET /api/v1/filters/options — Free+
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/aggregate — Starter+
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/timeseries — Starter+
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_country — Starter+
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-impact — Starter+
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.pyand 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