Docs
API 1.39
Status
Guides

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 is usually shorter; from another language, call the HTTP API directly, sending your own Idempotency-Key on job creation.

#Install and configure

LanguagePackageInstall
TypeScript (Node 18+)@kaabist/sdknpm install @kaabist/sdk
Python (3.11+)kaabistpip install kaabist
Go (1.24+)github.com/kaabist/kaab-gogo 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.

#Run a job

curl
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
// 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
# 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
// 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
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
// 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
# 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
// 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).
  • 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).

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 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:

{ "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

Was this page helpful?