# SDKs

> The TypeScript and Python clients for Pidgeon, both generated from one OpenAPI document, so an operation has the same name in each.

Two clients, TypeScript and Python, both generated from
[`openapi.json`](https://pidgeon.ai/openapi.json) — which is itself generated
from the schemas the API validates with. An operation has the same name in
both, because both take it from the same field.

```ts
npm install @pidgeon-ai/sdk
```

```python
pip install pidgeon
```

The Python package needs Python 3.11 or later.

## Configure once

```ts
import { client } from '@pidgeon-ai/sdk';

client.setConfig({ auth: () => process.env.PIDGEON_API_KEY! });
```

```python
import os

from pidgeon import AuthenticatedClient

client = AuthenticatedClient(
  base_url="https://pidgeon.ai/api/v1",
  token=os.environ["PIDGEON_API_KEY"],
)
```

TypeScript has a module-level client the functions use when you do not pass
one; Python threads it through each call. That difference is each language's
idiom rather than a disagreement about the API.

## Call an operation

```ts
import { messagesSend } from '@pidgeon-ai/sdk';

const { data, error } = await messagesSend({
body: {
  from: 'you@yourdomain.com',
  to: 'someone@example.com',
  subject: 'First one',
  text: 'It works.',
},
});
```

```python
from pidgeon.api.messages import messages_send
from pidgeon.models import MessagesSendBody

sent = messages_send.sync(
  client=client,
  body=MessagesSendBody(
      from_="you@yourdomain.com",
      to="someone@example.com",
      subject="First one",
      text="It works.",
  ),
)
```

`messagesSend` and `messages_send` are the same operation. Every one is named
this way, so a method you find in one language tells you what to look for in
the other.

## Errors

**TypeScript answers, it does not throw.** A call returns `{ data, error,
response }`, and `error` carries the API's own code and message. Pass
`throwOnError: true` on a call that should raise instead.

```ts
const { data, error } = await identitiesGet({ path: { id } });

if (error) {
console.error(error.error.code, error.error.message);
}
```

**Python raises.** Any failure, a status of 400 or above, raises
`PidgeonError`, whether the contract declares that status or not. A call
returns its success model or does not return, so a refused send cannot pass
for a sent one. `code` is the API's own and is what to branch on; `message`
is safe to show a person; `retry_after` is in seconds when a limit says when
to come back.

```python
from pidgeon.errors import PidgeonError

try:
  result = identities_get.sync(client=client, id=identity_id)
  print(result.data)
except PidgeonError as error:
  print(error.status_code, error.code, error.message)
```

Before 1.19.0 a declared failure came back as an `Error` and an undeclared one
as `None`; a `403` on a send was one of the second kind, and a site read it
as sent.

## Verifying a webhook

The one thing in the TypeScript package that is written rather than
generated, because HMAC is a security contract and not an endpoint:

```ts
import { verifyWebhook } from '@pidgeon-ai/sdk';

const event = await verifyWebhook({
rawBody: await request.text(),
headers: request.headers,
secret: process.env.PIDGEON_WEBHOOK_SECRET!,
});
```

The Python package ships the same thing — `pidgeon.webhooks.verify_webhook`,
keyword arguments, raising `WebhookVerificationError` rather than returning a
boolean nobody checks. [Verifying a signature](https://pidgeon.ai/docs/webhooks/verifying.md) has
both, and the three ways to get it wrong by hand.

## What `message/rfc822` does in Python

`GET /messages/{id}/raw` returns the original message rather than JSON, and
the Python generator will not parse a non-JSON body. The call works: the
bytes are on `.content`, and `.parsed` is `None`.
