---
title: Job clients
description: Trigger a public job, retain its run ID, and watch or control that run.
pagination: false
sidebar:
  icon: panels-top-left
---

Trigger a job through the generated client, then use its run handle to observe progress and completion. Only jobs with a `client` policy appear. Triggering one admits work; the returned
handle identifies a run and is **not** the completed result:

Install the [frontend client and development CLI](/docs/modules/client) in the caller's package first.
Submit with `client.jobs.exportOrders.trigger({ input: { orderIds }, options: { operationId } })`,
then read `client.jobs.exportOrders.runs.get({ runId: accepted.runId })`. The caller installs no
provider SDK; those dependencies belong to the backend's selected job binding.

Keep `accepted.runId` and `operationId` so a later page load or retry can inspect the same run.
The app must authorize the caller for this job; a typed client alone does not grant access.

## Use from React

The [`defineJob` example](/docs/modules/jobs/quickstart#2-expose-a-job) names this job `exportOrders`
and exposes `trigger`, `get`, `list`, and `watch`. After [frontend setup](/guides/frontend/connect-react)
and pulling that backend's contract, render this component under `RelkitClientProvider`:

```tsx title="src/ExportOrders.tsx"
"use client";

/** Submits one commerce export and observes the accepted run without replaying an uncertain submission. */
import { useJobRun, useJobTrigger, useRelkitClient } from "@relkit/client/react";

/** Keeps the accepted receipt separate from the task's eventual output.
 * @returns One explicit submission button and its receipt, observation, or failure state. */
export function ExportOrders() {
  const { status } = useRelkitClient();
  const submit = useJobTrigger("exportOrders");
  const run = useJobRun("exportOrders", submit.data ? { runId: submit.data.runId } : {});

  return (
    <section>
      <button
        disabled={status !== "ready" || !submit.isIdle}
        onClick={() => submit.mutate({ input: { orderIds: ["order-1"] } })}
      >
        Export orders
      </button>
      {submit.isPending && <p role="status">Submitting…</p>}
      {submit.isError && (
        <p role="alert">{String(submit.error)}. Check the outcome before submitting again.</p>
      )}
      {submit.data && <p>Run ID: {submit.data.runId}</p>}
      <p role="status">
        Connection: {run.connection}; run: {run.run?.status ?? "not accepted"}
      </p>
    </section>
  );
}
```

The hook selector is the job's public **name**, `exportOrders`, rather than its stable backend ID
`orders.export-orders`. `useJobTrigger` supplies managed operation identity and retains pending
receipt metadata. `useJobRun` waits for an accepted `runId`; mounting it does not submit a job.
Persist that receipt in your application's own UI state if the page must restore it after navigation.

| Client API                                                                                | Backend policy required               | Use                                                          |
| ----------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------ |
| [`useJobTrigger`](/docs/api/client#usejobtrigger)                                         | `trigger`                             | Submit typed task input and receive a run handle.            |
| [`useJobRun`](/docs/api/client#usejobrun) / [`watchJobRun`](/docs/api/client#watchjobrun) | `watch`                               | Observe only the fields selected by the job's client policy. |
| [`useJobCancel`](/docs/api/client#usejobcancel)                                           | `cancel`                              | Send an explicit control with `runId` and `operationId`.     |
| [`useJobRetry`](/docs/api/client#usejobretry)                                             | `retry`                               | Ask the provider to retry a run explicitly.                  |
| [`watchJobStream`](/docs/api/client#watchjobstream)                                       | `streams` and a declared named stream | Read the run's named content stream with its own lifecycle.  |

Cancel, retry, and named streams are not exposed by this example's policy. Declare the needed
operations on the backend and pull a new contract before using them. Generated types also restrict
which run fields the frontend can read. For an uncertain write, follow
[recovery guidance](/docs/modules/client/offline); do not turn reconnect into automatic resubmission.

## Watch one accepted run

`watchJobRun` uses the accepted run ID. This public API example subscribes to state changes,
connects once, and disposes the watcher when its caller is finished:

```ts title="Watch an export run"
import { createClient } from "@relkit/client";
import { watchJobRun } from "@relkit/client/jobs";

export async function watchExport(runId: string): Promise<() => Promise<void>> {
  const client = createClient({ baseUrl: "http://127.0.0.1:3000" });
  const watch = watchJobRun(client, "exportOrders", { runId });
  const unsubscribe = watch.subscribe(({ connection, run }) => {
    console.log(connection, run?.status);
  });
  try {
    await watch.connect();
  } catch (error) {
    unsubscribe();
    await watch.dispose();
    throw error;
  }
  return async () => {
    unsubscribe();
    await watch.dispose();
  };
}
```

Call the returned cleanup function when the component or request scope ends. Reconnection
resumes observation; it does not trigger a second run. Keep credentials on the server-side
boundary for private jobs.

Cancel and retry are explicit controls with `{ runId, operationId }`, allowing audit and recovery.
The server may process them asynchronously; read or watch until the requested state appears.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/jobs` API](/docs/api/jobs)
- [`@relkit/client` API](/docs/api/client)

- [`apps/docs/examples/jobs/watch-run.ts`](https://github.com/rel-kit/relkit/blob/main/apps/docs/examples/jobs/watch-run.ts)
