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 60 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
TypeScript
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, 3600 on Pro.
retries3Extra tries after a network error, 429 or 5xx. 0 to 5.
delaySeconds0Start the run later, up to 7 days.
callbackUrldashboard webhookWhere we POST the finished run. Leave it out to use the webhook URL set in the dashboard; null sends none.

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

TypeScript
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
JSON
{
  "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.

To get every run's result without passing callbackUrl each time, set a webhook URL under Jobs → Manage → Webhook in the dashboard. A run's own callbackUrl wins over it, and callbackUrl: null turns it off for one run. From that page you can also send a signed test event.

Every request we send, to your url and to your callbackUrl, has an x-cloudhike-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-cloudhike-run-id.

Next.js route: app/api/jobs-webhook/route.ts
TypeScript
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-cloudhike-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-cloudhike-run-id to skip a run you already handled.

Limits

FreePro
Runs per 24 hours500No limit
PriceFree50,000 run-seconds a month per project included, then $0.01 per 1,000 run-seconds
Runs per minute30600
Runs in progress at once525
Longest timeout5 minutes60 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 7 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 on Free, within its limits. On Pro, run time is the time we wait on your URL, in whole seconds per attempt (at least 1), so a retried run counts each attempt. It is charged from your credits every hour.

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.

    Terminal
    Shell
    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-cloudhike-signature, and skip run ids already handled.

Replace the placeholders in <angle brackets> before sending. Always review what the agent changes.