jobspipe 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual envs
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # Editors / OS
23
+ .vscode/
24
+ .idea/
25
+ .DS_Store
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project adheres
4
+ to [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [0.1.0] - 2026-06-29
7
+
8
+ Initial release.
9
+
10
+ - Synchronous `Jobspipe` and asynchronous `AsyncJobspipe` clients.
11
+ - `client.jobs.search(...)` and auto-paginating `client.jobs.iter(...)`.
12
+ - `client.stack.scan(...)` for website tech-stack detection.
13
+ - Fully typed pydantic response models; ships `py.typed`.
14
+ - Automatic retries with exponential backoff and `Retry-After` support.
15
+ - Typed exception hierarchy (`AuthenticationError`, `RateLimitError`, ...).
jobspipe-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JobsPipe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: jobspipe
3
+ Version: 0.1.0
4
+ Summary: The official Python SDK for the JobsPipe API - search live job postings and detect website tech stacks.
5
+ Project-URL: Homepage, https://jobspipe.dev
6
+ Project-URL: Documentation, https://jobspipe.dev/docs
7
+ Project-URL: Repository, https://github.com/jobspipe/jobspipe-python
8
+ Project-URL: Issues, https://github.com/jobspipe/jobspipe-python/issues
9
+ Project-URL: Changelog, https://github.com/jobspipe/jobspipe-python/blob/main/CHANGELOG.md
10
+ Author-email: JobsPipe <support@jobspipe.dev>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: api,job search,jobs,jobspipe,recruiting,sdk,tech stack,wappalyzer
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.8
28
+ Requires-Dist: httpx<1,>=0.23.0
29
+ Requires-Dist: pydantic<3,>=2.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.0; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
33
+ Requires-Dist: pytest>=7.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.20; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # JobsPipe Python SDK
39
+
40
+ [![PyPI version](https://img.shields.io/pypi/v/jobspipe.svg)](https://pypi.org/project/jobspipe/)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/jobspipe.svg)](https://pypi.org/project/jobspipe/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
43
+
44
+ The official Python library for the [JobsPipe API](https://jobspipe.dev).
45
+
46
+ Every job posting, one API. Search millions of live jobs from 30+ sources
47
+ (LinkedIn, Y Combinator, company career pages, and more), normalized into one
48
+ clean schema — plus website tech-stack detection.
49
+
50
+ - Synchronous **and** asynchronous clients
51
+ - Fully typed responses (pydantic models, ships `py.typed`)
52
+ - Automatic retries with `Retry-After` support
53
+ - Transparent offset pagination (`client.jobs.iter(...)`)
54
+ - A typed exception hierarchy you can branch on
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install jobspipe
60
+ ```
61
+
62
+ Requires Python 3.8+.
63
+
64
+ ## Authentication
65
+
66
+ Get an API key from your [JobsPipe dashboard](https://jobspipe.dev) (it starts
67
+ with `jp_live_`). Pass it directly or set the `JOBSPIPE_API_KEY` environment
68
+ variable — the client reads it automatically.
69
+
70
+ ```bash
71
+ export JOBSPIPE_API_KEY="jp_live_..."
72
+ ```
73
+
74
+ ## Quickstart
75
+
76
+ ```python
77
+ from jobspipe import Jobspipe
78
+
79
+ client = Jobspipe() # reads JOBSPIPE_API_KEY, or Jobspipe(api_key="jp_live_...")
80
+
81
+ results = client.jobs.search(
82
+ description_or=["python", "django"],
83
+ job_country_code_or=["US"],
84
+ remote=True,
85
+ posted_at_max_age_days=7,
86
+ limit=25,
87
+ include_total_results=True,
88
+ )
89
+
90
+ print(f"{results.metadata.total_results} matches")
91
+ for job in results.data:
92
+ print(f"{job.job_title} @ {job.company} ({job.country_code}) — {job.url}")
93
+ ```
94
+
95
+ ### Detect a website's tech stack
96
+
97
+ ```python
98
+ scan = client.stack.scan("stripe.com")
99
+ print(f"{scan.domain} scanned at {scan.scanned_at} (HTTP {scan.http_status})")
100
+ for tech in scan.detected:
101
+ print(f" {tech.name:20} {tech.confidence:>3.0f}% {', '.join(tech.categories)}")
102
+ ```
103
+
104
+ ## Pagination
105
+
106
+ `client.jobs.iter(...)` walks every page for you and yields individual jobs.
107
+ It stops automatically when there are no more results, or when `max_results`
108
+ is reached.
109
+
110
+ ```python
111
+ for job in client.jobs.iter(
112
+ description_or=["rust"],
113
+ remote=True,
114
+ page_size=100,
115
+ max_results=500,
116
+ ):
117
+ print(job.job_title, "-", job.company)
118
+ ```
119
+
120
+ Prefer manual control? `search()` accepts `offset`/`page` and the response
121
+ exposes `metadata.next_cursor` (which is `None` on the last page).
122
+
123
+ ## Async
124
+
125
+ Every method has an `async` counterpart on `AsyncJobspipe`:
126
+
127
+ ```python
128
+ import asyncio
129
+ from jobspipe import AsyncJobspipe
130
+
131
+ async def main():
132
+ async with AsyncJobspipe() as client:
133
+ results = await client.jobs.search(remote=True, limit=10)
134
+ async for job in client.jobs.iter(description_or=["go"], max_results=50):
135
+ print(job.job_title)
136
+
137
+ asyncio.run(main())
138
+ ```
139
+
140
+ ## Error handling
141
+
142
+ All errors derive from `jobspipe.JobspipeError`. HTTP errors are
143
+ `APIStatusError` subclasses carrying the status code, request id, and parsed body.
144
+
145
+ ```python
146
+ from jobspipe import (
147
+ Jobspipe,
148
+ AuthenticationError,
149
+ PaymentRequiredError,
150
+ RateLimitError,
151
+ APIStatusError,
152
+ APIConnectionError,
153
+ )
154
+
155
+ client = Jobspipe()
156
+ try:
157
+ client.jobs.search(limit=10)
158
+ except AuthenticationError:
159
+ print("Bad or missing API key")
160
+ except PaymentRequiredError:
161
+ print("Monthly quota exceeded — upgrade your plan")
162
+ except RateLimitError as e:
163
+ print(f"Rate limited; retry after {e.retry_after}s")
164
+ except APIStatusError as e:
165
+ print(f"API error {e.status_code}: {e.message} (request {e.request_id})")
166
+ except APIConnectionError:
167
+ print("Could not reach the API")
168
+ ```
169
+
170
+ | Exception | When |
171
+ |--------------------------|-----------------------------------------|
172
+ | `BadRequestError` | 400 — malformed request / invalid domain |
173
+ | `AuthenticationError` | 401 — missing or invalid API key |
174
+ | `PaymentRequiredError` | 402 — monthly quota exceeded |
175
+ | `NotFoundError` | 404 — resource not found |
176
+ | `RateLimitError` | 429 — too many requests (`.retry_after`) |
177
+ | `InternalServerError` | 5xx — server error / gateway timeout |
178
+ | `APITimeoutError` | request timed out |
179
+ | `APIConnectionError` | network failure |
180
+
181
+ ## Retries & timeouts
182
+
183
+ Transient failures (429, 408, 409, 5xx, and network errors) are retried
184
+ automatically with exponential backoff, honoring the server's `Retry-After`
185
+ header. Configure per-client:
186
+
187
+ ```python
188
+ import httpx
189
+ from jobspipe import Jobspipe
190
+
191
+ client = Jobspipe(
192
+ max_retries=4, # default 2
193
+ timeout=httpx.Timeout(30.0, connect=5.0), # default 60s
194
+ )
195
+ ```
196
+
197
+ ## Configuration
198
+
199
+ | Parameter | Default | Env var |
200
+ |-------------------|-------------------------------|---------------------|
201
+ | `api_key` | — | `JOBSPIPE_API_KEY` |
202
+ | `base_url` | `https://api.jobspipe.dev` | `JOBSPIPE_BASE_URL` |
203
+ | `timeout` | 60s (10s connect) | — |
204
+ | `max_retries` | 2 | — |
205
+ | `default_headers` | — | — |
206
+ | `http_client` | new `httpx.Client` | — |
207
+
208
+ The response models allow extra fields, so new attributes added by the API are
209
+ preserved (accessible via attribute or `model_extra`) without an SDK upgrade.
210
+ Likewise, `search()` forwards any unknown keyword argument as a filter.
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ pip install -e ".[dev]"
216
+ ruff check .
217
+ mypy src
218
+ pytest
219
+ ```
220
+
221
+ The test suite mocks HTTP with [respx](https://lundberg.github.io/respx/) — no
222
+ network or API key required. Set `JOBSPIPE_API_KEY` to additionally run the live
223
+ smoke test.
224
+
225
+ ## License
226
+
227
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,190 @@
1
+ # JobsPipe Python SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/jobspipe.svg)](https://pypi.org/project/jobspipe/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/jobspipe.svg)](https://pypi.org/project/jobspipe/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+
7
+ The official Python library for the [JobsPipe API](https://jobspipe.dev).
8
+
9
+ Every job posting, one API. Search millions of live jobs from 30+ sources
10
+ (LinkedIn, Y Combinator, company career pages, and more), normalized into one
11
+ clean schema — plus website tech-stack detection.
12
+
13
+ - Synchronous **and** asynchronous clients
14
+ - Fully typed responses (pydantic models, ships `py.typed`)
15
+ - Automatic retries with `Retry-After` support
16
+ - Transparent offset pagination (`client.jobs.iter(...)`)
17
+ - A typed exception hierarchy you can branch on
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install jobspipe
23
+ ```
24
+
25
+ Requires Python 3.8+.
26
+
27
+ ## Authentication
28
+
29
+ Get an API key from your [JobsPipe dashboard](https://jobspipe.dev) (it starts
30
+ with `jp_live_`). Pass it directly or set the `JOBSPIPE_API_KEY` environment
31
+ variable — the client reads it automatically.
32
+
33
+ ```bash
34
+ export JOBSPIPE_API_KEY="jp_live_..."
35
+ ```
36
+
37
+ ## Quickstart
38
+
39
+ ```python
40
+ from jobspipe import Jobspipe
41
+
42
+ client = Jobspipe() # reads JOBSPIPE_API_KEY, or Jobspipe(api_key="jp_live_...")
43
+
44
+ results = client.jobs.search(
45
+ description_or=["python", "django"],
46
+ job_country_code_or=["US"],
47
+ remote=True,
48
+ posted_at_max_age_days=7,
49
+ limit=25,
50
+ include_total_results=True,
51
+ )
52
+
53
+ print(f"{results.metadata.total_results} matches")
54
+ for job in results.data:
55
+ print(f"{job.job_title} @ {job.company} ({job.country_code}) — {job.url}")
56
+ ```
57
+
58
+ ### Detect a website's tech stack
59
+
60
+ ```python
61
+ scan = client.stack.scan("stripe.com")
62
+ print(f"{scan.domain} scanned at {scan.scanned_at} (HTTP {scan.http_status})")
63
+ for tech in scan.detected:
64
+ print(f" {tech.name:20} {tech.confidence:>3.0f}% {', '.join(tech.categories)}")
65
+ ```
66
+
67
+ ## Pagination
68
+
69
+ `client.jobs.iter(...)` walks every page for you and yields individual jobs.
70
+ It stops automatically when there are no more results, or when `max_results`
71
+ is reached.
72
+
73
+ ```python
74
+ for job in client.jobs.iter(
75
+ description_or=["rust"],
76
+ remote=True,
77
+ page_size=100,
78
+ max_results=500,
79
+ ):
80
+ print(job.job_title, "-", job.company)
81
+ ```
82
+
83
+ Prefer manual control? `search()` accepts `offset`/`page` and the response
84
+ exposes `metadata.next_cursor` (which is `None` on the last page).
85
+
86
+ ## Async
87
+
88
+ Every method has an `async` counterpart on `AsyncJobspipe`:
89
+
90
+ ```python
91
+ import asyncio
92
+ from jobspipe import AsyncJobspipe
93
+
94
+ async def main():
95
+ async with AsyncJobspipe() as client:
96
+ results = await client.jobs.search(remote=True, limit=10)
97
+ async for job in client.jobs.iter(description_or=["go"], max_results=50):
98
+ print(job.job_title)
99
+
100
+ asyncio.run(main())
101
+ ```
102
+
103
+ ## Error handling
104
+
105
+ All errors derive from `jobspipe.JobspipeError`. HTTP errors are
106
+ `APIStatusError` subclasses carrying the status code, request id, and parsed body.
107
+
108
+ ```python
109
+ from jobspipe import (
110
+ Jobspipe,
111
+ AuthenticationError,
112
+ PaymentRequiredError,
113
+ RateLimitError,
114
+ APIStatusError,
115
+ APIConnectionError,
116
+ )
117
+
118
+ client = Jobspipe()
119
+ try:
120
+ client.jobs.search(limit=10)
121
+ except AuthenticationError:
122
+ print("Bad or missing API key")
123
+ except PaymentRequiredError:
124
+ print("Monthly quota exceeded — upgrade your plan")
125
+ except RateLimitError as e:
126
+ print(f"Rate limited; retry after {e.retry_after}s")
127
+ except APIStatusError as e:
128
+ print(f"API error {e.status_code}: {e.message} (request {e.request_id})")
129
+ except APIConnectionError:
130
+ print("Could not reach the API")
131
+ ```
132
+
133
+ | Exception | When |
134
+ |--------------------------|-----------------------------------------|
135
+ | `BadRequestError` | 400 — malformed request / invalid domain |
136
+ | `AuthenticationError` | 401 — missing or invalid API key |
137
+ | `PaymentRequiredError` | 402 — monthly quota exceeded |
138
+ | `NotFoundError` | 404 — resource not found |
139
+ | `RateLimitError` | 429 — too many requests (`.retry_after`) |
140
+ | `InternalServerError` | 5xx — server error / gateway timeout |
141
+ | `APITimeoutError` | request timed out |
142
+ | `APIConnectionError` | network failure |
143
+
144
+ ## Retries & timeouts
145
+
146
+ Transient failures (429, 408, 409, 5xx, and network errors) are retried
147
+ automatically with exponential backoff, honoring the server's `Retry-After`
148
+ header. Configure per-client:
149
+
150
+ ```python
151
+ import httpx
152
+ from jobspipe import Jobspipe
153
+
154
+ client = Jobspipe(
155
+ max_retries=4, # default 2
156
+ timeout=httpx.Timeout(30.0, connect=5.0), # default 60s
157
+ )
158
+ ```
159
+
160
+ ## Configuration
161
+
162
+ | Parameter | Default | Env var |
163
+ |-------------------|-------------------------------|---------------------|
164
+ | `api_key` | — | `JOBSPIPE_API_KEY` |
165
+ | `base_url` | `https://api.jobspipe.dev` | `JOBSPIPE_BASE_URL` |
166
+ | `timeout` | 60s (10s connect) | — |
167
+ | `max_retries` | 2 | — |
168
+ | `default_headers` | — | — |
169
+ | `http_client` | new `httpx.Client` | — |
170
+
171
+ The response models allow extra fields, so new attributes added by the API are
172
+ preserved (accessible via attribute or `model_extra`) without an SDK upgrade.
173
+ Likewise, `search()` forwards any unknown keyword argument as a filter.
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ pip install -e ".[dev]"
179
+ ruff check .
180
+ mypy src
181
+ pytest
182
+ ```
183
+
184
+ The test suite mocks HTTP with [respx](https://lundberg.github.io/respx/) — no
185
+ network or API key required. Set `JOBSPIPE_API_KEY` to additionally run the live
186
+ smoke test.
187
+
188
+ ## License
189
+
190
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jobspipe"
7
+ dynamic = ["version"]
8
+ description = "The official Python SDK for the JobsPipe API - search live job postings and detect website tech stacks."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.8"
13
+ authors = [{ name = "JobsPipe", email = "support@jobspipe.dev" }]
14
+ keywords = ["jobspipe", "jobs", "job search", "api", "sdk", "tech stack", "wappalyzer", "recruiting"]
15
+ dependencies = [
16
+ "httpx>=0.23.0,<1",
17
+ "pydantic>=2.0,<3",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.8",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Topic :: Software Development :: Libraries :: Python Modules",
32
+ "Typing :: Typed",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://jobspipe.dev"
37
+ Documentation = "https://jobspipe.dev/docs"
38
+ Repository = "https://github.com/jobspipe/jobspipe-python"
39
+ Issues = "https://github.com/jobspipe/jobspipe-python/issues"
40
+ Changelog = "https://github.com/jobspipe/jobspipe-python/blob/main/CHANGELOG.md"
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "pytest>=7.0",
45
+ "pytest-asyncio>=0.21",
46
+ "respx>=0.20",
47
+ "ruff>=0.4",
48
+ "mypy>=1.0",
49
+ ]
50
+
51
+ [tool.hatch.version]
52
+ path = "src/jobspipe/_version.py"
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["src/jobspipe"]
56
+
57
+ [tool.hatch.build.targets.sdist]
58
+ include = ["src/jobspipe", "README.md", "LICENSE", "CHANGELOG.md"]
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ asyncio_mode = "auto"
63
+
64
+ [tool.ruff]
65
+ line-length = 100
66
+ target-version = "py38"
67
+ src = ["src", "tests"]
68
+
69
+ [tool.ruff.lint]
70
+ # No "UP" (pyupgrade): it would rewrite Optional[X]/List[X] to X | None / list[X],
71
+ # which pydantic evaluates at runtime and which breaks on Python 3.8/3.9.
72
+ select = ["E", "F", "I", "B"]
73
+ ignore = ["E501", "B008"]
74
+
75
+ [tool.mypy]
76
+ python_version = "3.8"
77
+ ignore_missing_imports = true
@@ -0,0 +1,65 @@
1
+ """JobsPipe Python SDK.
2
+
3
+ Every job posting, one API. Search millions of live jobs from 30+ sources
4
+ (LinkedIn, Y Combinator, company career pages, ...) normalized into one clean
5
+ schema, plus website tech-stack detection.
6
+
7
+ from jobspipe import Jobspipe
8
+
9
+ client = Jobspipe(api_key="jp_live_...") # or set JOBSPIPE_API_KEY
10
+ results = client.jobs.search(description_or=["python"], remote=True, limit=10)
11
+ for job in results.data:
12
+ print(job.job_title, "-", job.company)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from ._client import AsyncJobspipe, Jobspipe
18
+ from ._exceptions import (
19
+ APIConnectionError,
20
+ APIError,
21
+ APIStatusError,
22
+ APITimeoutError,
23
+ AuthenticationError,
24
+ BadRequestError,
25
+ InternalServerError,
26
+ JobspipeError,
27
+ NotFoundError,
28
+ PaymentRequiredError,
29
+ RateLimitError,
30
+ )
31
+ from ._version import __version__
32
+ from .types import (
33
+ DetectedTechnology,
34
+ Job,
35
+ JobSearchResponse,
36
+ SearchMetadata,
37
+ StackScanResponse,
38
+ TechnologySignal,
39
+ )
40
+
41
+ __all__ = [
42
+ "__version__",
43
+ # clients
44
+ "Jobspipe",
45
+ "AsyncJobspipe",
46
+ # models
47
+ "Job",
48
+ "JobSearchResponse",
49
+ "SearchMetadata",
50
+ "StackScanResponse",
51
+ "DetectedTechnology",
52
+ "TechnologySignal",
53
+ # errors
54
+ "JobspipeError",
55
+ "APIError",
56
+ "APIConnectionError",
57
+ "APITimeoutError",
58
+ "APIStatusError",
59
+ "BadRequestError",
60
+ "AuthenticationError",
61
+ "PaymentRequiredError",
62
+ "NotFoundError",
63
+ "RateLimitError",
64
+ "InternalServerError",
65
+ ]