Browse docs

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.

  1. 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.
  2. We answer at once with the run's id and status queued.
  3. Our worker calls your URL and waits for the answer, for up to 5 minutes on Free and 15 on Pro.
  4. The run ends as succeeded (a 2xx answer), failed or cancelled. Read it with its id, or give a callbackUrl and we POST the result to you.

Start a run

From your server
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"
FieldDefaultMeaning
urlrequiredThe http or https URL we call. Must be a public address.
methodPOSTGET, POST, PUT, PATCH or DELETE.
headersnoneSent with the request. Stored encrypted. At most 30.
bodynoneA string is sent as is; anything else as JSON. Up to 256 KB.
timeoutSeconds300Longest we wait for the full answer. Up to 300 on Free, 900 on Pro.
retries3Extra tries after a network error, 429 or 5xx. 0 to 5.
delaySeconds0Start the run later, up to 7 days.
callbackUrlnoneWhere 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

ts
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));
  }
}
A finished run
{
  "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 }
}
  • status is queued, running, succeeded, failed or cancelled.
  • response.body is parsed when your URL answered JSON, otherwise it is text. We keep the first 256 KB; truncated says when there was more.
  • error explains a failure, such as The URL answered 500. or a timeout.
  • GET {JOBS_API_URL}/runs?limit=20&status=failed lists recent runs, newest first.
  • POST {JOBS_API_URL}/runs/<id>/cancel cancels 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.

Next.js route: app/api/jobs-webhook/route.ts
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

FreePro
Runs per 24 hours50025,000
Runs per minute30300
Runs in progress at once210
Longest timeout5 minutes15 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. 1

    Open a terminal in your app's folder and start Claude Code.

    claude
  2. 2

    Paste the prompt below and press Enter. Approve the file edits and commands it asks about.

  3. 3

    Optional: save a short note about CloudHike.dev in CLAUDE.md so every session knows about it.

PROMPT
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.