Services
Background jobs
Run long API calls and AI workflows in the background.
How it works
Click Deploy on the Jobs card. You get two values, JOBS_API_URL and JOBS_API_KEY. Keep the key on your server: it starts runs and signs what we send you.
- Your server asks for a run with the URL to call, for example an endpoint of your own that generates a report or runs an AI workflow.
- We answer at once with the run's
idand statusqueued. - Our worker calls your URL and waits for the answer, for up to 5 minutes on Free and 15 on Pro.
- The run ends as
succeeded(a 2xx answer),failedorcancelled. Read it with its id, or give acallbackUrland we POST the result to you.
Start a run
const res = await fetch(`${process.env.JOBS_API_URL}/runs`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.JOBS_API_KEY!,
},
body: JSON.stringify({
url: "https://api.example.com/generate-report",
method: "POST",
headers: { authorization: `Bearer ${process.env.MY_API_TOKEN}` },
body: { reportId: 42 },
timeoutSeconds: 300,
retries: 3,
callbackUrl: "https://example.com/api/jobs-webhook",
}),
});
const { id, status, statusUrl } = await res.json(); // 202, status "queued"| Field | Default | Meaning |
|---|---|---|
url | required | The http or https URL we call. Must be a public address. |
method | POST | GET, POST, PUT, PATCH or DELETE. |
headers | none | Sent with the request. Stored encrypted. At most 30. |
body | none | A string is sent as is; anything else as JSON. Up to 256 KB. |
timeoutSeconds | 300 | Longest we wait for the full answer. Up to 300 on Free, 900 on Pro. |
retries | 3 | Extra tries after a network error, 429 or 5xx. 0 to 5. |
delaySeconds | 0 | Start the run later, up to 7 days. |
callbackUrl | none | Where we POST the finished run. |
A timeout or any other answer, such as 400, is final and not retried. If your workflow is not safe to run twice, set retries: 0.
Poll for the result
async function waitForRun(id: string) {
for (;;) {
const res = await fetch(`${process.env.JOBS_API_URL}/runs/${id}`, {
headers: { "x-api-key": process.env.JOBS_API_KEY! },
});
const run = await res.json();
if (["succeeded", "failed", "cancelled"].includes(run.status)) return run;
await new Promise((r) => setTimeout(r, 3000));
}
}{
"id": "4b5dea73-bf6d-4a5d-94b6-833b80c9241c",
"status": "succeeded",
"method": "POST",
"url": "https://api.example.com/generate-report",
"attempts": 1,
"maxAttempts": 4,
"createdAt": "2026-10-21T09:00:00.000Z",
"runAt": "2026-10-21T09:00:00.000Z",
"startedAt": "2026-10-21T09:00:00.120Z",
"finishedAt": "2026-10-21T09:02:14.900Z",
"durationMs": 134780,
"response": { "status": 200, "body": { "reportUrl": "https://..." }, "truncated": false },
"error": null,
"callback": { "url": "https://example.com/api/jobs-webhook", "status": "delivered", "attempts": 1, "error": null }
}statusisqueued,running,succeeded,failedorcancelled.response.bodyis parsed when your URL answered JSON, otherwise it is text. We keep the first 256 KB;truncatedsays when there was more.errorexplains a failure, such asThe URL answered 500.or a timeout.GET {JOBS_API_URL}/runs?limit=20&status=failedlists recent runs, newest first.POST {JOBS_API_URL}/runs/<id>/cancelcancels a run. A queued run never starts; a running one is left to finish, but its result is dropped and no webhook is sent.
Webhooks
With a callbackUrl, we POST the finished run there, with the same JSON as polling returns. Answer with any 2xx. Other answers are retried 6 times over about 15 minutes.
Every request we send, to your url and to your callbackUrl, has an x-instacloud-signature header: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> with your JOBS_API_KEY. Check it, and refuse old timestamps, so nobody else can call your endpoints. Requests also carry x-instacloud-run-id.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(body: string, header: string | null) {
const parts = Object.fromEntries((header ?? "").split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", process.env.JOBS_API_KEY!).update(`${t}.${body}`).digest("hex");
return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
export async function POST(request: Request) {
const body = await request.text();
if (!verify(body, request.headers.get("x-instacloud-signature"))) {
return new Response("bad signature", { status: 401 });
}
const run = JSON.parse(body);
// run.status, run.response.body ...
return new Response("ok");
}Runs are delivered at least once. In rare cases, such as a worker restart mid-run, your URL can be called twice for the same run. Use x-instacloud-run-id to skip a run you already handled.
Limits
| Free | Pro | |
|---|---|---|
| Runs per 24 hours | 500 | 25,000 |
| Runs per minute | 30 | 300 |
| Runs in progress at once | 2 | 10 |
| Longest timeout | 5 minutes | 15 minutes |
- Over a limit, starting a run answers 429. Runs beyond the in-progress limit wait their turn.
- Runs and their results are kept for 30 days. The Runs page in the dashboard shows the last 100.
- URLs must be public: localhost, private networks and cloud metadata addresses are refused.
- Background jobs are free during the beta.
Use with AI
Let your coding agent move slow work into background jobs.
- 1
Open a terminal in your app's folder and start Claude Code.
claude
- 2
Paste the prompt below and press Enter. Approve the file edits and commands it asks about.
- 3
Optional: save a short note about CloudHike.dev in CLAUDE.md so every session knows about it.
Read https://cloudhike.dev/docs/md/jobs. Move this app's slow API calls into CloudHike.dev background jobs: start a run with JOBS_API_URL and JOBS_API_KEY from server code, receive the result on a webhook route that verifies x-instacloud-signature, and skip run ids already handled.
Replace the placeholders in <angle brackets> before sending. Always review what the agent changes.
