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
| 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.
#Run a job
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"
// 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)}`);
# 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}")
// 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)
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
// 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)}`);
# 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}")
// 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,PUTandDELETEare retried on408,429and5xxand on connection failures, twice by default, honouringRetry-After(a wait over a minute is not retried).POSTis retried only with anIdempotency-Key, whichjobs.createandjobs.batchalways 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.Errorin Go) with the API'scode, the HTTPstatus,request_idanddetails; 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)andconstructEventcheckKaab-Signatureon 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
onInputoronAttention,jobs.waitreturns a parked job; checkstatusinstead of assuming it finished. - Treating
waitas a deadline for the job. It bounds the request, not the job: a job still running afterwaitcomes backrunningorqueued. - Parsing a webhook body before verifying it. Verify the raw bytes first.
#Reference
- SDK operation names
- Create a job, read a job, submit an input, resume a job
- Error codes
- 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, webhooks and schedules and change detection