JobsPipe Python SDK
The official Python library for the JobsPipe API.
Every job posting, one API. Search millions of live jobs from 30+ sources (LinkedIn, Y Combinator, company career pages, and more), normalized into one clean schema — plus website tech-stack detection.
- Synchronous and asynchronous clients
- Fully typed responses (pydantic models, ships
py.typed) - Automatic retries with
Retry-Aftersupport - Transparent offset pagination (
client.jobs.iter(...)) - A typed exception hierarchy you can branch on
Installation
pip install jobspipe
Requires Python 3.8+.
Authentication
Get an API key from your JobsPipe dashboard (it starts
with jp_live_). Pass it directly or set the JOBSPIPE_API_KEY environment
variable — the client reads it automatically.
export JOBSPIPE_API_KEY="jp_live_..."
Quickstart
from jobspipe import Jobspipe
client = Jobspipe() # reads JOBSPIPE_API_KEY, or Jobspipe(api_key="jp_live_...")
results = client.jobs.search(
description_or=["python", "django"],
job_country_code_or=["US"],
remote=True,
posted_at_max_age_days=7,
limit=25,
include_total_results=True,
)
print(f"{results.metadata.total_results} matches")
for job in results.data:
print(f"{job.job_title} @ {job.company} ({job.country_code}) — {job.url}")
Detect a website's tech stack
scan = client.stack.scan("stripe.com")
print(f"{scan.domain} scanned at {scan.scanned_at} (HTTP {scan.http_status})")
for tech in scan.detected:
print(f" {tech.name:20} {tech.confidence:>3.0f}% {', '.join(tech.categories)}")
Pagination
client.jobs.iter(...) walks every page for you and yields individual jobs.
It stops automatically when there are no more results, or when max_results
is reached.
for job in client.jobs.iter(
description_or=["rust"],
remote=True,
page_size=100,
max_results=500,
):
print(job.job_title, "-", job.company)
Prefer manual control? search() accepts offset/page and the response
exposes metadata.next_cursor (which is None on the last page).
Async
Every method has an async counterpart on AsyncJobspipe:
import asyncio
from jobspipe import AsyncJobspipe
async def main():
async with AsyncJobspipe() as client:
results = await client.jobs.search(remote=True, limit=10)
async for job in client.jobs.iter(description_or=["go"], max_results=50):
print(job.job_title)
asyncio.run(main())
Error handling
All errors derive from jobspipe.JobspipeError. HTTP errors are
APIStatusError subclasses carrying the status code, request id, and parsed body.
from jobspipe import (
Jobspipe,
AuthenticationError,
PaymentRequiredError,
RateLimitError,
APIStatusError,
APIConnectionError,
)
client = Jobspipe()
try:
client.jobs.search(limit=10)
except AuthenticationError:
print("Bad or missing API key")
except PaymentRequiredError:
print("Monthly quota exceeded — upgrade your plan")
except RateLimitError as e:
print(f"Rate limited; retry after {e.retry_after}s")
except APIStatusError as e:
print(f"API error {e.status_code}: {e.message} (request {e.request_id})")
except APIConnectionError:
print("Could not reach the API")
| Exception | When |
|---|---|
BadRequestError |
400 — malformed request / invalid domain |
AuthenticationError |
401 — missing or invalid API key |
PaymentRequiredError |
402 — monthly quota exceeded |
NotFoundError |
404 — resource not found |
RateLimitError |
429 — too many requests (.retry_after) |
InternalServerError |
5xx — server error / gateway timeout |
APITimeoutError |
request timed out |
APIConnectionError |
network failure |
Retries & timeouts
Transient failures (429, 408, 409, 5xx, and network errors) are retried
automatically with exponential backoff, honoring the server's Retry-After
header. Configure per-client:
import httpx
from jobspipe import Jobspipe
client = Jobspipe(
max_retries=4, # default 2
timeout=httpx.Timeout(30.0, connect=5.0), # default 60s
)
Configuration
| Parameter | Default | Env var |
|---|---|---|
api_key |
— | JOBSPIPE_API_KEY |
base_url |
https://api.jobspipe.dev |
JOBSPIPE_BASE_URL |
timeout |
60s (10s connect) | — |
max_retries |
2 | — |
default_headers |
— | — |
http_client |
new httpx.Client |
— |
The response models allow extra fields, so new attributes added by the API are
preserved (accessible via attribute or model_extra) without an SDK upgrade.
Likewise, search() forwards any unknown keyword argument as a filter.
Development
pip install -e ".[dev]"
ruff check .
mypy src
pytest
The test suite mocks HTTP with respx — no
network or API key required. Set JOBSPIPE_API_KEY to additionally run the live
smoke test.
License
MIT — see LICENSE.
Release files for jobspipe 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jobspipe-0.1.0.tar.gz | 11.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jobspipe-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.6 kB
Release files / jobspipe-0.1.0.tar.gz
| Download URL | jobspipe-0.1.0.tar.gz |
|---|---|
| Size | 11.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a561bad31bec5857a0f188d42ca0b9eadee56ea54f0adc8ea0842d717d3449a
|
|
BLAKE2b-256 checksum How to use checksums |
848788e8bd8e722a9f5d40c1ca2a6387afda0c662faa495e711eea67a764edd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / jobspipe-0.1.0-py3-none-any.whl
| Download URL | jobspipe-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa0fab33f6ea6450cae1c57036f6e9055705a48ad837bc1f50b0a79b071316f7
|
|
BLAKE2b-256 checksum How to use checksums |
d2b13fc7ddc033d5048f6c6d86e95eed24385b67becd389bc13ed04791d04244
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|