# SDKs

The official TypeScript, Python and Go clients: install, authenticate, run a job, wait for it, answer it, and verify webhooks.

Kaabist has three official clients, generated from the same OpenAPI document as the API reference
and sharing one behaviour: the same retries, the same idempotency keys, the same errors. Use one
instead of raw HTTP to get typed requests and responses, safe retries and a `wait` helper that
answers parked jobs for you.

## When to use it

Use an SDK whenever your code talks to Kaabist from TypeScript, Python or Go. From a shell script
or CI, the [`kaab` CLI](https://docs.kaab.ist/guides/cli) is usually shorter; from another language, call the
[HTTP API](https://docs.kaab.ist/reference/api) directly, sending your own `Idempotency-Key` on job creation.

## Install and configure

| Language | Package | Install |
|---|---|---|
| TypeScript (Node 18+) | `@kaabist/sdk` | `npm install @kaabist/sdk` |
| Python (3.11+) | `kaabist` | `pip install kaabist` |
| Go (1.24+) | `github.com/kaabist/kaab-go` | `go get github.com/kaabist/kaab-go` |

The Go client is in the package `github.com/kaabist/kaab-go/kaab`, its request and response types
in `github.com/kaabist/kaab-go/client`, and webhook verification in
`github.com/kaabist/kaab-go/webhooks`.

Each client reads `KAAB_API_KEY` (and `KAAB_BASE_URL`, for a deployment other than
`https://api.kaab.ist/v1`) when you pass no key. Use an `sk_test_` key while you build; see
[keys and modes](https://docs.kaab.ist/getting-started/keys-and-modes).

## Run a job

### curl

```bash
response=$(curl -sS --fail-with-body "${KAAB_BASE_URL:-https://api.kaab.ist/v1}/jobs" \
  -H "Authorization: Bearer $KAAB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=30" \
  -d '{
    "url": "'"${SITE_URL:-https://shop.example.com}"'/p/1",
    "extract": {
      "title": "h1",
      "price": { "selector": ".price", "type": "money", "locale": "tr", "currency": "TRY" }
    }
  }')
echo "$response"
```

### TypeScript

```ts
// One call: create the job and hold the request up to 30 s for its result (`Prefer: wait`).
const job = await kaab.jobs.create({
  url: `${site}/p/1`,
  extract: {
    title: "h1",
    price: { selector: ".price", type: "money", locale: "tr", currency: "TRY" },
  },
  wait: 30,
});
if (job.status !== "succeeded") throw new Error(`job ${job.id} is ${job.status}`);
console.log(`${job.id}: ${JSON.stringify(job.data)}`);
```

### Python

```python
# One call: create the job and hold the request up to 30 s for its result (Prefer: wait).
job = kaab.jobs.create(
    url=f"{site}/p/1",
    extract={"title": "h1", "price": {"selector": ".price", "type": "money", "locale": "tr", "currency": "TRY"}},
    wait=30,
)
if job.status != "succeeded":
    raise SystemExit(f"job {job.id} is {job.status}")
print(f"{job.id}: {job.data.to_dict() if job.data else None}")
```

### Go

```go
// One call: create the job and hold the request up to 30 s for its result (Prefer: wait).
body, err := kaab.Body[client.CreateJobJSONBody](map[string]any{
	"url": site + "/p/1",
	"extract": map[string]any{
		"title": "h1",
		"price": map[string]any{"selector": ".price", "type": "money", "locale": "tr", "currency": "TRY"},
	},
})
if err != nil {
	log.Fatal(err)
}
job, err := c.Jobs.Create(ctx, body, kaab.WithWait(30))
if err != nil {
	log.Fatal(err)
}
if job.Status != "succeeded" {
	log.Fatalf("job %s is %s", job.ID, job.Status)
}
fmt.Printf("%s: %v\n", job.ID, *job.Data)
```

### CLI

```bash
data=$(kaab jobs run \
  --url "${SITE_URL:-https://shop.example.com}/p/1" \
  --extract '{"title": "h1", "price": {"selector": ".price", "type": "money", "locale": "tr", "currency": "TRY"}}' \
  --wait 30)
echo "$data"
```

`wait` holds the request open up to 60 seconds (`Prefer: wait`). A job that finishes in time comes
back complete; one that does not comes back in its current state, and you follow it with
`jobs.wait`.

## Wait for a job, and answer it

### TypeScript

```ts
// Or start it and wait separately: `jobs.wait` long-polls until the job ends.
const started = await kaab.jobs.create({
  url: `${site}/p/2`,
  extract: { title: "h1" },
});
const done = await kaab.jobs.wait(started.id, { timeoutMs: 120_000 });
console.log(`${done.id}: ${done.status} ${JSON.stringify(done.data)}`);
```

### Python

```python
# Or start it and wait separately: jobs.wait long-polls until the job ends.
started = kaab.jobs.create(url=f"{site}/p/2", extract={"title": "h1"})
done = kaab.jobs.wait(started.id, timeout=120)
print(f"{done.id}: {done.status}")
```

### Go

```go
// Or start it and wait separately: Jobs.Wait long-polls until the job ends.
body, _ = kaab.Body[client.CreateJobJSONBody](map[string]any{"url": site + "/p/2", "extract": map[string]any{"title": "h1"}})
started, err := c.Jobs.Create(ctx, body)
if err != nil {
	log.Fatal(err)
}
done, err := c.Jobs.Wait(ctx, started.ID, kaab.WaitOptions{Timeout: 2 * time.Minute})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("%s: %s\n", done.ID, done.Status)
```

`jobs.wait(id)` long-polls until the job succeeds, fails or is canceled, and answers it on the way:

- **`onInput`** (`on_input`, `OnInput`) supplies a value the job asked for, such as a one-time
  code. The value is posted to the job and the wait goes on.
- **`onAttention`** (`on_attention`, `OnAttention`) decides what to do with a job that needs
  attention: a resume action (apply a proposal, skip the step, retry from a checkpoint, abort), or
  `"wait"` to leave it to somebody in the panel or on a hosted page.

A parked job with no handler is returned to you as it is. The whole wait times out after five
minutes by default (`timeoutMs`).

## Behaviour all three share

- **Retries.** `GET`, `PUT` and `DELETE` are retried on `408`, `429` and `5xx` and on connection
  failures, twice by default, honouring `Retry-After` (a wait over a minute is not retried). `POST`
  is retried only with an `Idempotency-Key`, which `jobs.create` and `jobs.batch` always send (a
  ULID unless you give one), so a retried create never runs twice. To make your own retries safe,
  pass your own key (see [side-effect safety](https://docs.kaab.ist/guides/side-effect-safety)).
- **Errors.** Every failure is a `KaabError` (`*kaab.Error` in Go) with the API's `code`, the HTTP
  `status`, `request_id` and `details`; quote the request id to support.
- **Lists** iterate across pages: `for await (const job of kaab.jobs.list())`, `for job in
  kaab.jobs.list()`, `for job, err := range c.Jobs.ListAll(ctx, nil)`.
- **Webhooks.** `webhooks.verify(rawBody, signatureHeader, secret)` and `constructEvent` check
  `Kaab-Signature` on the raw request body (see [webhooks](https://docs.kaab.ist/guides/webhooks)).

Every API operation has the same name in all three: a resource, then a method
(`kaab.jobs.create`, `kaab.jobs.create`, `c.Jobs.Create`). [SDK reference](https://docs.kaab.ist/reference/sdks) lists
each operation with its name per language and links its API page, where the request and response
fields are documented.

## Worked example

The quickstart extracts the title and price of a product page, `https://shop.example.com/p/1`, in
one call, then starts a second job and follows it with `jobs.wait`. For a page that shows a pair of
headphones priced `1.337,40 TL`, the first job returns:

```json
{ "title": "Kulaklık Klasik 101", "price": { "amount": 1337.4, "currency": "TRY" } }
```

## Pitfalls

- **Generating a new idempotency key per attempt.** The SDK's own retries reuse the key they sent;
  if *your* code retries a create, pass the same key each time.
- **Waiting forever on a parked job.** Without `onInput` or `onAttention`, `jobs.wait` returns a
  parked job; check `status` instead of assuming it finished.
- **Treating `wait` as a deadline for the job.** It bounds the request, not the job: a job still
  running after `wait` comes back `running` or `queued`.
- **Parsing a webhook body before verifying it.** Verify the raw bytes first.

## Reference

- [SDK operation names](https://docs.kaab.ist/reference/sdks)
- [Create a job](https://docs.kaab.ist/reference/api/jobs/create-job), [read a job](https://docs.kaab.ist/reference/api/jobs/get-job),
  [submit an input](https://docs.kaab.ist/reference/api/jobs/submit-job-input), [resume a job](https://docs.kaab.ist/reference/api/jobs/resume-job)
- [Error codes](https://docs.kaab.ist/reference/codes#errors)
- Guides with SDK code for a login that asks for a one-time code, a webhook receiver and a schedule
  with change detection: [authenticated flows](https://docs.kaab.ist/guides/authenticated-flows),
  [webhooks](https://docs.kaab.ist/guides/webhooks) and [schedules and change detection](https://docs.kaab.ist/guides/schedules-and-change-detection)
