# SDKs (/sdks)



Three libraries, maintained by SendKuy, cover every endpoint in this reference. Field names are the API's own, so each request in the reference maps one to one onto an SDK call.

<Cards>
  <Card title="Node.js" href="https://github.com/sendkuy/sendkuy-node" description="npm i sendkuy · Node.js 20.3+, Bun, Deno and edge runtimes" />

  <Card title="PHP" href="https://github.com/sendkuy/sendkuy-php" description="composer require sendkuy/sdk · PHP 8.2+" />

  <Card title="Laravel" href="/laravel" description="composer require sendkuy/laravel · notifications, a facade and webhooks" />
</Cards>

## Send your first message [#send-your-first-message]

<CodeBlockTabs defaultValue="Node.js">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Node.js">
      Node.js
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="PHP">
      PHP
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Laravel">
      Laravel
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Node.js">
    ```ts
    import { SendKuy } from 'sendkuy';

    const sendkuy = new SendKuy('sk_live_YOUR_API_KEY');

    const [message] = await sendkuy.messages.send({
      channel: 'whatsapp',
      to: ['+6281234567890'],
      body: 'Your order is on its way.',
    });
    ```
  </CodeBlockTab>

  <CodeBlockTab value="PHP">
    ```php
    <?php

    use SendKuy\SendKuy;

    $sendkuy = new SendKuy('sk_live_YOUR_API_KEY');

    $messages = $sendkuy->messages()->send([
        'channel' => 'whatsapp',
        'to' => ['+6281234567890'],
        'body' => 'Your order is on its way.',
    ]);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Laravel">
    ```php
    use SendKuy\Laravel\Facades\SendKuy;

    $messages = SendKuy::messages()->send([
        'channel' => 'whatsapp',
        'to' => ['+6281234567890'],
        'body' => 'Your order is on its way.',
    ]);
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## What the SDKs handle for you [#what-the-sdks-handle-for-you]

* **Retries without duplicates.** Every write carries an `Idempotency-Key`, and each retry of that call reuses it, so a retry after a timeout or a dropped connection gets the first answer instead of sending again. The SDKs retry network errors, timeouts, `5xx`, `idempotency_request_in_progress`, and `429` when `Retry-After` is a minute or less, up to twice. See [Idempotency](/idempotency).
* **Pagination.** `autoPaginate()` follows `next_cursor` until the last page. See [Pagination](/pagination#with-an-sdk).
* **Typed errors**, one class per `error.type`, carrying `code`, `param`, the documentation link and the request id.
* **Webhook signatures**, checked in one call. See [Webhooks](/webhooks#verify-with-an-sdk).
* **Types.** The Node.js SDK ships TypeScript types generated from this API's contract; the PHP SDK gives your editor every field of every object.

## Errors [#errors]

| `error.type`            | Node.js               | PHP                       |
| ----------------------- | --------------------- | ------------------------- |
| `authentication_error`  | `AuthenticationError` | `AuthenticationException` |
| `authorization_error`   | `AuthorizationError`  | `AuthorizationException`  |
| `invalid_request_error` | `InvalidRequestError` | `InvalidRequestException` |
| `rate_limit_error`      | `RateLimitError`      | `RateLimitException`      |
| `api_error`             | `ApiError`            | `ApiException`            |
| no response at all      | `ConnectionError`     | `ConnectionException`     |

Every class extends `SendKuyError` in Node.js and `SendKuyException` in PHP, so one `catch` covers them all. Branch on the code, never on the message: `error.code` in Node.js, `$e->getErrorCode()` in PHP. The full list is on [Errors](/errors).

Each error also carries the HTTP status (`error.status`, `$e->getHttpStatus()`) and, on a `429`, the seconds to wait (`error.retryAfter`, `$e->getRetryAfter()`). A webhook that fails verification raises `SignatureVerificationError` or `SignatureVerificationException`, whose `reason` is one of `malformed_header`, `timestamp_out_of_tolerance`, `signature_mismatch` or `invalid_payload`.

## Configure the client [#configure-the-client]

The API key is the first argument. In Node.js you may leave it out, and `SENDKUY_API_KEY` is read from the environment. The other options go in the second argument:

<CodeBlockTabs defaultValue="Node.js">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Node.js">
      Node.js
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="PHP">
      PHP
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Node.js">
    ```ts
    const sendkuy = new SendKuy('sk_live_YOUR_API_KEY', { timeout: 10_000, maxRetries: 1 });
    ```
  </CodeBlockTab>

  <CodeBlockTab value="PHP">
    ```php
    $sendkuy = new SendKuy('sk_live_YOUR_API_KEY', ['timeout' => 10, 'max_retries' => 1]);
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| Node.js      | PHP           | Default                            | Sets                                                                       |
| ------------ | ------------- | ---------------------------------- | -------------------------------------------------------------------------- |
| `timeout`    | `timeout`     | `30000` milliseconds; `30` seconds | how long one attempt may take                                              |
| `maxRetries` | `max_retries` | `2`                                | retries after the first attempt                                            |
| `fetch`      | `http_client` | the runtime's `fetch`; Guzzle      | the HTTP client, for a proxy or a fake in tests                            |
| `baseUrl`    | `base_url`    | `https://app.sendkuy.com/api/v1`   | the API address; change it only to reach a proxy or a mock server in tests |

With your own `http_client` in PHP, set the timeout on that client: the SDK's `timeout` does not reach it. The Node.js SDK refuses to run in a browser, where anyone who opens the page could read your key; `dangerouslyAllowBrowser: true` overrides that. In Laravel, every option is an environment variable; see [Configuration](/laravel#configuration).

One call can override the timeout and carry its own idempotency key. Node.js also takes an `AbortSignal`:

<CodeBlockTabs defaultValue="Node.js">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Node.js">
      Node.js
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="PHP">
      PHP
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Node.js">
    ```ts
    await sendkuy.messages.send(params, { timeout: 5_000, idempotencyKey: 'order-1042-shipped' });
    ```
  </CodeBlockTab>

  <CodeBlockTab value="PHP">
    ```php
    $sendkuy->messages()->send($params, ['timeout' => 5, 'idempotency_key' => 'order-1042-shipped']);
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Versions [#versions]

Version 1.x of each SDK covers API v1. Every release is listed in the CHANGELOG for [Node.js](https://github.com/sendkuy/sendkuy-node/blob/main/CHANGELOG.md), [PHP](https://github.com/sendkuy/sendkuy-php/blob/main/CHANGELOG.md) and [Laravel](https://github.com/sendkuy/sendkuy-laravel/blob/main/CHANGELOG.md).

## Methods [#methods]

Every endpoint in this reference has a method. An endpoint newer than your installed version is still one call away with `request()`: `sendkuy.request({ method: 'GET', path: '/account' })` in Node.js, `$sendkuy->request('GET', '/account')` in PHP.

<SdkMethods />
