> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nylon.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> The official Nylon client for Node, Bun, Deno and the edge.

`@nylon/sdk` is a typed client over the same REST API these docs describe. It has no runtime dependencies, and its types are generated from Nylon's OpenAPI specification, so they cannot drift from the API.

```bash theme={null}
npm install @nylon/sdk
```

Node 18+, Bun, Deno, Cloudflare Workers and Vercel's edge runtime.

## Your first post

```ts theme={null}
import { Nylon } from '@nylon/sdk'

const nylon = new Nylon({ apiKey: process.env.NYLON_API_KEY })

const { data: profiles } = await nylon.profiles.list()

const post = await nylon.posts.create({
  profile_ids: profiles.map(profile => profile.id),
  text: 'Shipping today.',
  media: [{ url: 'https://example.com/launch.jpg' }],
})

for (const target of post.targets) {
  console.log(target.network, target.status, target.url ?? target.error?.message)
}
```

`apiKey` defaults to `process.env.NYLON_API_KEY`, so `new Nylon()` is enough when that is set.

<Warning>
  Server-side only. A key can publish to every social account your organization has connected, so anything shipped to a browser or a mobile app can be read off it.
</Warning>

## What the SDK adds over `fetch`

Everything here is reachable with raw HTTP — these are the parts that are easy to get wrong by hand.

<AccordionGroup>
  <Accordion title="Retries that cannot double-post" icon="shield-check">
    `posts.create` generates an `idempotency_key` for you, so a retry after a network blip returns the original post instead of publishing a second time. Retries use exponential backoff with full jitter and honour `Retry-After`.

    Supply your own key when the natural one lives in your system, so your retries collapse too:

    ```ts theme={null}
    await nylon.posts.create({
      profile_ids: [profileId],
      text: 'Your order shipped.',
      idempotency_key: `order-${order.id}`,
    })
    ```
  </Accordion>

  <Accordion title="Typed errors" icon="triangle-alert">
    Branch with `instanceof` rather than on a string. `code` is stable; `message` is for humans.

    ```ts theme={null}
    import { NylonValidationError, NylonPaymentRequiredError } from '@nylon/sdk'

    try {
      await nylon.posts.create({ profile_ids: [id], text })
    }
    catch (error) {
      // Nothing published — `details` names the rule and the network.
      if (error instanceof NylonValidationError) return showToUser(error.details)
      // A billing problem, not a user problem. Do not prompt them to reconnect.
      if (error instanceof NylonPaymentRequiredError) return alertBilling()
      throw error
    }
    ```

    One class per [error code](/errors), plus `NylonConnectionError` and `NylonTimeoutError` for requests that never arrived.
  </Accordion>

  <Accordion title="Webhook verification" icon="webhook">
    `verifyWebhook` signs the raw body, checks the timestamp against a 5-minute tolerance, and compares in constant time — the three things hand-rolled verification usually gets wrong. It is a pure WebCrypto function, so it needs no API key and runs on every supported runtime.

    ```ts theme={null}
    import { verifyWebhook, NylonSignatureVerificationError } from '@nylon/sdk'

    app.post('/nylon/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
      try {
        const event = await verifyWebhook({
          payload: req.body,                    // the RAW body
          signature: req.get('Nylon-Signature'),
          secret: process.env.NYLON_WEBHOOK_SECRET,
        })
        res.sendStatus(200)                     // acknowledge first
        await handle(event)                     // then do the work
      }
      catch (error) {
        if (error instanceof NylonSignatureVerificationError) return res.sendStatus(400)
        throw error
      }
    })
    ```

    See [Webhooks](/webhooks) for the payload shapes and the retry schedule.
  </Accordion>

  <Accordion title="Lazy pagination" icon="list">
    `listAll` fetches each page only as you consume it, so a large account holds one page in memory rather than all of it. Breaking out stops the requests.

    ```ts theme={null}
    for await (const post of nylon.posts.listAll({ status: 'failed' })) {
      console.log(post.id)
    }

    const everything = await nylon.posts.listAll().all()
    ```
  </Accordion>
</AccordionGroup>

## Resources

|                     |                                                                                                             |
| ------------------- | ----------------------------------------------------------------------------------------------------------- |
| `nylon.posts`       | `create` `validate` `list` `listAll` `get` `update` `cancel` `retry`                                        |
| `nylon.profiles`    | `list` `listAll` `get` `disconnect` `listBoards`                                                            |
| `nylon.connections` | `list` `create`                                                                                             |
| `nylon.networks`    | `list`                                                                                                      |
| `nylon.webhooks`    | `list` `create` `get` `update` `delete` `rotateSecret` `test` `listDeliveries` `listAllDeliveries` `verify` |
| `nylon.media`       | `list` `listAll` `createFromUrl` `upload`                                                                   |

## Handling a partial failure

A post to four profiles is four independent attempts, and three succeeding while one fails is a normal outcome. Retry only the failed targets — re-sending the whole post would publish it twice on the ones that already worked.

```ts theme={null}
if (post.status === 'partially_published') {
  await nylon.posts.retry(post.id)
}
```

## Configuration

```ts theme={null}
const nylon = new Nylon({
  apiKey: process.env.NYLON_API_KEY,
  timeout: 60_000,          // per request
  publishTimeout: 300_000,  // publishing calls, which are synchronous
  maxRetries: 2,            // three attempts
  headers: { 'X-App': 'my-service' },
  fetch: myInstrumentedFetch,
})
```

Per call, including your own `AbortSignal`:

```ts theme={null}
await nylon.posts.list({}, { signal: controller.signal, timeout: 5_000 })
```

Your remaining budget from the most recent response, read from the API's own headers:

```ts theme={null}
nylon.rateLimit   // { limit, remaining, reset: Date } | undefined
```

## Types

Every schema is exported, generated from the OpenAPI specification:

```ts theme={null}
import type { Post, Profile, Network, CreatePostRequest } from '@nylon/sdk'
```

The raw `paths`, `components` and `operations` are exported too, for reaching past the SDK.

## Next

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    The same first steps in raw HTTP.
  </Card>

  <Card title="API reference" icon="list-tree" href="/api-reference">
    Every endpoint the SDK wraps.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/errors">
    The error taxonomy behind the typed classes.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/webhooks">
    Payloads, retries and signature verification.
  </Card>
</CardGroup>
