# TypeScript and Python SDKs

> Typed clients generated from the OpenAPI document: run a tool, wait for a run, fetch results.

Both SDKs are generated from our OpenAPI document, so every tool input is typed. Same calls in TypeScript and Python.

<Callout title="Before you start">
  You need an API key (it starts with `af_live_`). [Sign up](/login) with your email and you get
  2,000 free credits.
</Callout>

## 1. Install [#1-install]

The packages go on npm and PyPI soon. Until then you can call the HTTP API directly (last step) or use MCP.

## 2. Run a tool [#2-run-a-tool]

Set `PULLWIRE_API_KEY` in your environment, or pass the key in code. `run` waits for the results and returns them with the credits used.

<Tabs items={['TypeScript', 'Python']}>
<Tab value="TypeScript">

```ts
import { createPullwire } from '@af/sdk';

const pw = createPullwire(); // reads PULLWIRE_API_KEY

const { run, results } = await pw.run('youtube-transcripts', {"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"maxResults":3});
console.log(run.creditsUsed, results[0]);
```

</Tab>
<Tab value="Python">

```python
from pullwire import Pullwire

pw = Pullwire()  # reads PULLWIRE_API_KEY

out = pw.run("youtube-transcripts", {"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"maxResults":3})
print(out.run.credits_used, out.results[0])
```

</Tab>
</Tabs>

## 3. Bigger jobs [#3-bigger-jobs]

`start` returns right away; `waitForRun` / `wait_for_run` checks every 2 seconds until the run is done.

<Tabs items={['TypeScript', 'Python']}>
<Tab value="TypeScript">

```ts
const queued = await pw.start('youtube-transcripts', {"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"maxResults":3});
const done = await pw.waitForRun(queued.id);
const rows = await pw.results(done.id); // or pw.resultsCsv(done.id)
```

</Tab>
<Tab value="Python">

```python
queued = pw.start("youtube-transcripts", {"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"maxResults":3})
done = pw.wait_for_run(str(queued.id))
rows = pw.results(str(done.id))  # or pw.results_csv(...)
```

</Tab>
</Tabs>

## 4. Errors [#4-errors]

Every error comes with a stable code: `INSUFFICIENT_CREDITS` (402), `INVALID_INPUT` (400), `RATE_LIMITED` (429), `UNAUTHORIZED` (401). The SDKs throw `PullwireError` with `status`, `code` and `message`.

<Tabs items={['TypeScript', 'Python']}>
<Tab value="TypeScript">

```ts
import { PullwireError } from '@af/sdk';

try {
  await pw.run('youtube-transcripts', { videos: [] });
} catch (err) {
  if (err instanceof PullwireError) console.log(err.status, err.code, err.message);
}
```

</Tab>
<Tab value="Python">

```python
from pullwire import PullwireError

try:
    pw.run("youtube-transcripts", {"videos": []})
except PullwireError as err:
    print(err.status, err.code, err.message)
```

</Tab>
</Tabs>

## 5. Any other language [#5-any-other-language]

The API is plain HTTPS and JSON. The full OpenAPI 3.1 document lives at `/api/v1/openapi.json`, so you can generate a client for Go, Ruby or anything else.

```bash title="cURL"
curl -X POST https://pullwire.dev/api/v1/tools/youtube-transcripts/runs \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"maxResults":3}'
```

The call waits up to 25 seconds for the results. For bigger jobs add ?async=1 to the URL: you get a run id back right away and fetch the results a bit later.
