Prototype with the sandbox
Wire up and test your client against the free sandbox with no API key, then switch to live data by changing the path and adding a key.
Every route under /v1/sandbox works with no API key and no quota. It returns a couple of fixed sample jobs in the same { metadata, data } shape as the live search, with a reduced set of fields. Use it to build and test your client, your CI and your agent's tool calls before you sign up; it is not a copy of live data.
Search
Send the same body you will send to jobs search. The sandbox returns the same sample jobs whatever the filters say, so it tests your wiring, not your query.
curl https://api.jobspipe.dev/v1/sandbox/jobs/search \
-H "Content-Type: application/json" \
-d '{
"job_title_or": ["software engineer"],
"job_country_code_or": ["US"],
"remote": true,
"limit": 10
}'import requests
resp = requests.post(
"https://api.jobspipe.dev/v1/sandbox/jobs/search",
json={"job_title_or": ["software engineer"], "job_country_code_or": ["US"], "remote": True, "limit": 10},
)
resp.raise_for_status()
page = resp.json()
for job in page["data"]:
print(job["id"], job["job_title"], "-", job["company"])
print("next_cursor:", page["metadata"]["next_cursor"])const resp = await fetch("https://api.jobspipe.dev/v1/sandbox/jobs/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
job_title_or: ["software engineer"],
job_country_code_or: ["US"],
remote: true,
limit: 10,
}),
});
const page = await resp.json();
for (const job of page.data) console.log(job.id, job.job_title, "-", job.company);
console.log("next_cursor:", page.metadata.next_cursor);Batch and async export
The sandbox has two more routes for exercising client patterns: a batch of up to 10 searches in one call, and an asynchronous export that answers 202 Accepted and is then polled.
Batch and export have no live equivalent yet. Use them to test your client code, not as the basis of a production integration.
# Batch: one response entry per request, in order
curl https://api.jobspipe.dev/v1/sandbox/jobs/search/batch \
-H "Content-Type: application/json" \
-d '{
"requests": [
{ "job_title_or": ["data engineer"], "job_country_code_or": ["GB"] },
{ "job_title_or": ["product designer"], "remote": true }
]
}'
# Export: returns 202 with job_id and status_url (also in the Location header)
curl -i https://api.jobspipe.dev/v1/sandbox/jobs/export \
-H "Content-Type: application/json" \
-d '{ "job_title_or": ["data engineer"] }'
# Poll the status URL until status is "completed", then fetch result_url
curl https://api.jobspipe.dev/v1/sandbox/jobs/export/EXPORT_JOB_IDimport time
import requests
SANDBOX = "https://api.jobspipe.dev/v1/sandbox/jobs"
batch = requests.post(f"{SANDBOX}/search/batch", json={
"requests": [
{"job_title_or": ["data engineer"], "job_country_code_or": ["GB"]},
{"job_title_or": ["product designer"], "remote": True},
]
}).json()
for entry in batch["responses"]:
print(entry["status"], len(entry["body"]["data"]), "jobs")
job = requests.post(f"{SANDBOX}/export", json={"job_title_or": ["data engineer"]})
assert job.status_code == 202
status_url = job.json()["status_url"]
while True:
status = requests.get(status_url).json()
if status["status"] == "completed":
print("result:", status["result_url"])
break
time.sleep(2)const SANDBOX = "https://api.jobspipe.dev/v1/sandbox/jobs";
const json = { "Content-Type": "application/json" };
const batch = await fetch(`${SANDBOX}/search/batch`, {
method: "POST",
headers: json,
body: JSON.stringify({
requests: [
{ job_title_or: ["data engineer"], job_country_code_or: ["GB"] },
{ job_title_or: ["product designer"], remote: true },
],
}),
}).then((r) => r.json());
for (const entry of batch.responses) console.log(entry.status, entry.body.data.length, "jobs");
const job = await fetch(`${SANDBOX}/export`, {
method: "POST",
headers: json,
body: JSON.stringify({ job_title_or: ["data engineer"] }),
});
const { status_url } = await job.json();
let status;
do {
status = await fetch(status_url).then((r) => r.json());
if (status.status !== "completed") await new Promise((r) => setTimeout(r, 2000));
} while (status.status !== "completed");
console.log("result:", status.result_url);{
"job_id": "job_03585382-6928-43f8-98e2-3b846ca39fdf",
"status": "completed",
"status_url": "https://api.jobspipe.dev/v1/sandbox/jobs/export/job_03585382-6928-43f8-98e2-3b846ca39fdf"
}Switch to live data
When your client works, create an API key and change two things: drop /sandbox from the path and add the Authorization header. The request body stays the same.
- curl https://api.jobspipe.dev/v1/sandbox/jobs/search \
+ curl https://api.jobspipe.dev/v1/jobs/search \
+ -H "Authorization: Bearer jp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "job_title_or": ["software engineer"], "job_country_code_or": ["US"], "remote": true, "limit": 10 }'What changes once you are live:
- Responses carry the full job schema instead of the sandbox subset, and real, changing results.
- Each job costs one credit the first time you receive it in a calendar month;
metadata.credits_chargedsays what each call cost. See rate limits. - Page through large result sets with
metadata.next_cursor, as in pagination.