# Bring your own providers

Use your own proxies, language models and captcha solvers: configuration, fallback, billing, health checks and how credentials are kept.

Kaabist buys three things on your behalf, each billed per use: **proxies** to reach sites from
(per megabyte), **language models** that help heal broken locators and recognise unfamiliar pages
(per token), and **captcha solvers** (per solve). If you already have an account with a vendor, you
can register it with Kaabist and your jobs use it instead, and that item disappears from your bill.

## When to use it

Bring your own provider when:

- you already pay a proxy vendor, or need exits in a network you control (an office address a site
  has allow-listed, a residential pool you negotiated);
- your organisation has a contract with a model vendor, or a rule that page content may only be sent
  to the model you chose;
- you have a captcha-solving balance you want to use up.

Stay on the platform's providers when you have no such account: they need no configuration, pick a
proxy pool per site, and fall back automatically. Bring-your-own (BYO) providers are part of the
Developer plan and above, and need a verified payment method (`403 plan_required` or
`403 payment_method_required` otherwise).

## Supported vendors

| Kind | Vendors |
|---|---|
| Proxy | Any HTTP, HTTPS or SOCKS5 proxy (`static`, `list`, `gateway`), with presets for **Bright Data**, **Oxylabs**, **Decodo** (Smartproxy), **IPRoyal** and **Webshare** |
| LLM | **OpenAI**, **Anthropic**, **Google Gemini**, **Azure OpenAI**, and any **OpenAI-compatible** server |
| Captcha | **2Captcha**, **CapSolver** |

## Registering a provider

Every provider is created with `POST /v1/byo-providers` and an `admin` key, as
`{ "kind", "name", "config" }`. Creating it tests it straight away:

### TypeScript

```ts
// Credentials are write-only: sealed under your organisation's key, never returned or logged.
const provider = await kaab.byoProviders.create({
  kind: "proxy",
  name: `my-proxy-${Date.now().toString(36)}`,
  config: { type: "static", url: proxyUrl, pool: "datacenter", country: "DE" },
});
console.log(`${provider.id}: ${provider.status} ${JSON.stringify(provider.config_public)}`);
```

The examples on this page use placeholders: never paste a real credential into a ticket or a chat.

### Proxies

A single endpoint:

```json
{ "type": "static", "url": "http://USER:PASSWORD@proxy.example.com:3128", "pool": "datacenter" }
```

A list. Each job sticks to one address for its whole run, and jobs spread across the list:

```json
{ "type": "list", "urls": ["http://USER:PASSWORD@203.0.113.10:3128", "http://USER:PASSWORD@203.0.113.11:3128"], "pool": "datacenter" }
```

A vendor's rotating gateway. With a preset, Kaabist writes the job's country and a per-job session
into the credential in that vendor's own format, so each job keeps one exit IP:

```json
{ "type": "gateway", "vendor": "brightdata", "url": "http://brd-customer-ID-zone-ZONE:PASSWORD@brd.superproxy.io:33335", "pool": "residential", "country": "DE" }
{ "type": "gateway", "vendor": "oxylabs", "url": "http://USER:PASSWORD@pr.oxylabs.io:7777", "pool": "residential" }
{ "type": "gateway", "vendor": "decodo", "url": "http://USER:PASSWORD@gate.decodo.com:7000", "pool": "residential" }
{ "type": "gateway", "vendor": "iproyal", "url": "http://USER:PASSWORD@geo.iproyal.com:12321", "pool": "residential" }
```

Webshare's proxy list is fetched from its API with your key:

```json
{ "type": "list", "vendor": "webshare", "api_key": "WEBSHARE_API_KEY", "pool": "datacenter" }
```

A gateway without a preset can still be steered: `{country}` and `{session}` in the URL are
filled in per job, for example `http://acme-{country}-{session}:PASSWORD@gw.example.com:8000`.

Proxy hosts must be public addresses. A proxy on a private network is refused (`422 ssrf_blocked`).

### Language models

```json
{ "kind": "llm", "name": "openai", "config": { "vendor": "openai", "api_key": "sk-…", "model": "gpt-4.1" } }
{ "kind": "llm", "name": "claude", "config": { "vendor": "anthropic", "api_key": "sk-ant-…", "model": "claude-sonnet-5-5" } }
{ "kind": "llm", "name": "gemini", "config": { "vendor": "gemini", "api_key": "AIza…", "model": "gemini-2.5-flash" } }
{ "kind": "llm", "name": "azure", "config": { "vendor": "azure_openai", "api_key": "…", "base_url": "https://RESOURCE.openai.azure.com", "model": "DEPLOYMENT" } }
{ "kind": "llm", "name": "gateway", "config": { "vendor": "openai_compatible", "api_key": "…", "base_url": "https://llm.example.com/v1", "model": "MODEL" } }
```

`base_url` is required for Azure OpenAI and OpenAI-compatible servers. Supported models are listed
per vendor. Models marked **capable** heal locators and classify pages; **basic** models (smaller,
cheaper ones, and any model behind an OpenAI-compatible server until it is listed) are accepted and
marked `unsupported_for_healing` in `config_public`: they classify pages only. A model never decides
anything by itself: every selector it suggests is checked against the page before it becomes a
candidate.

### Captcha solvers

```json
{ "kind": "captcha", "name": "solver", "config": { "vendor": "2captcha", "api_key": "…" } }
{ "kind": "captcha", "name": "solver", "config": { "vendor": "capsolver", "api_key": "…" } }
```

## Using a provider in a job

Name it in the job's (or the program's) options:

```json
{
  "program": "acme-prices",
  "options": {
    "proxy": { "mode": "byo", "byo_id": "byo_…", "fallback": "none" },
    "llm": { "byo_id": "byo_…" },
    "captcha": { "byo_id": "byo_…" }
  }
}
```

Or set organisation defaults once in the panel (*Settings → Providers*), and jobs that name none
use them: `proxy.mode: "byo"` without a `byo_id` uses the default proxy, and every job uses the
default model and solver.

A job naming a provider that is failing its health checks is refused with
`422 byo_provider_failing`; one naming a provider of another kind, or another organisation's, with
`422 validation_failed`. A schedule whose provider is failing skips that run (`byo_unavailable`).

### What `fallback` means

When your proxy cannot be reached:

- **`"none"`** (the default): the job fails with `byo_proxy_unreachable`. Your traffic never
  leaves through an exit you did not choose.
- **`"platform"`**: the job continues through Kaabist's own proxies, the result carries the warning
  `byo_fallback_used`, and that job's proxy traffic is billed at the platform's rate.

## Billing

A BYO provider removes only its own item from the job's bill: a BYO proxy makes the `proxy` line
zero, a BYO model the `llm` line, a BYO solver the `captcha` line. Compute is always billed. The
breakdown in `meta.credits.breakdown` shows each line, including the zeroed ones. You pay your
vendor directly for what they served.

## Tests and health

`POST /v1/byo-providers/{id}/test` tests a provider again on demand and answers
`{ ok, latency_ms, detail, egress_ip? }`:

- a **proxy** is dialled from Kaabist's egress network and fetches an echo page; the answer includes
  the exit IP it saw (`egress_ip`);
- a **model** is asked to classify a small canned page;
- a **solver** reports its balance.

### TypeScript

```ts
// Probes the proxy from Kaabist's egress network and stores the verdict.
const result = await kaab.byoProviders.test(provider.id);
console.log(`ok=${result.ok} latency=${result.latency_ms} ms: ${result.detail}`);
const checked = await kaab.byoProviders.get(provider.id);
console.log(`${checked.id} is ${checked.status} (checked ${checked.last_health_at})`);
```

Kaabist repeats the check every hour. One failure makes a provider `degraded`; jobs still run.
Three failures in a row make it `failing`: jobs naming it are refused, the event `byo.unhealthy`
is sent to your webhooks and your organisation's owners and admins get an e-mail. Two passes in a
row make it `healthy` again (`byo.recovered`).

When your own model fails (a refused key, an exhausted quota), healing continues without it, and
the proposal's evidence says why (`byo_llm_auth_failed`, `byo_llm_quota_exceeded`, …). When your
own solver refuses its key or runs out of balance, a job with `policies.captcha: "solve"` fails
with `byo_captcha_auth_failed` or `byo_captcha_balance`, because nobody but you can fix that.

## How your credentials are kept

- Credentials are **write-only**. Kaabist encrypts them with your organisation's key the moment they
  arrive and never returns them: not in `GET`, not in errors, not in logs. You can replace them
  with `PATCH`; you cannot read them back. A `PATCH` that changes only, say, the country keeps the
  stored credentials, and re-tests the provider.
- The plaintext exists only inside the processes that use it: the egress gateway, and the worker or
  healer that calls your model or solver.
- What Kaabist learns about a site through your providers stays with your organisation: it never
  improves the platform-wide site profile.
- A provider cannot be deleted while an organisation default or an active schedule still uses it
  (`409 byo_in_use`).

## Worked example

Send a job through your own proxy and check what the bill says:

1. Register the proxy as in the first snippet. The answer is the provider, `healthy` when its first
   test passed, with your URL nowhere in it: `config_public` shows `type`, `pool` and `country`.
2. Test it. `ok` is `true` and `egress_ip` is your proxy's exit address, not Kaabist's.
3. Run a job on a product page through it:

   ```json
   { "url": "https://shop.example.com/p/1", "extract": { "title": "h1" },
     "options": { "max_tier": "http", "proxy": { "mode": "byo", "byo_id": "byo_…", "fallback": "none" } } }
   ```

4. The job succeeds, and `meta.credits.breakdown.proxy` is `0`: you paid your vendor for those
   bytes, not Kaabist.

## Pitfalls

- **`fallback: "platform"` changes where your traffic exits.** Choose it only when finishing the job
  matters more than the exit; the warning `byo_fallback_used` tells you when it happened.
- **One shared session per gateway.** A gateway without a preset or a `{session}` placeholder may
  rotate the exit IP mid-job, which logs a site out. Use a preset, or put `{session}` in the URL.
- **Basic models do not heal.** A provider marked `unsupported_for_healing` classifies pages but
  never proposes a locator; pick a capable model if you rely on healing.
- **Failing providers block jobs.** After three failed checks, jobs that name the provider are
  refused and schedules skip their runs. Subscribe to `byo.unhealthy` and `byo.recovered`.
- **Deleting a provider in use.** Remove it from the organisation defaults and from active
  schedules first (`409 byo_in_use`).

## Reference

- API: [create](https://docs.kaab.ist/reference/api/byo-providers/create-byo-provider),
  [test](https://docs.kaab.ist/reference/api/byo-providers/test-byo-provider),
  [update](https://docs.kaab.ist/reference/api/byo-providers/update-byo-provider),
  [delete](https://docs.kaab.ist/reference/api/byo-providers/delete-byo-provider),
  [list](https://docs.kaab.ist/reference/api/byo-providers/list-byo-providers)
- [BYO provider schema](https://docs.kaab.ist/reference/api/schemas/byo-provider), job [options](https://docs.kaab.ist/reference/dsl/options)
  (`proxy`, `llm`, `captcha`)
- [Error codes](https://docs.kaab.ist/reference/codes#errors) (`byo_*`), [events](https://docs.kaab.ist/reference/events) (`byo.unhealthy`,
  `byo.recovered`), [plans](https://docs.kaab.ist/reference/plans)
- Concepts: [egress and proxies](https://docs.kaab.ist/concepts/egress-and-proxies),
  [credits and billing](https://docs.kaab.ist/concepts/credits-and-billing)
