# TypeScript SDK

Source: https://tenergy.me/docs/sdk/typescript
Last updated: 2026-09-25

`@tenergy/sdk` is a typed client for the contract: one method per `operationId`, request and response types generated from [`openapi.yaml`](https://tenergy.me/openapi.yaml), and every request signed the way the API verifies it.

> [!NOTE]
> The package is not published to npm yet. Until it is, the `tenergy()` helper in the [quickstart](https://tenergy.me/docs/quickstart#step-2--sign-requests-with-your-key) does the same signing in twenty lines of Node, Python or shell. Other languages have no SDK; generate a client from [`openapi.yaml`](https://tenergy.me/openapi.yaml).

## Before you begin

| What the SDK does | Why |
|---|---|
| Signs every call with a fresh timestamp, retries included | A signature is single-use: a reused one is `1009 replayed_signature` |
| Serialises the body once and signs those bytes | A re-serialised body stops matching its signature (`1002`) |
| Retries `GET` and `DELETE` on `429`, `5xx` or `retryable`, honouring `Retry-After` | Those calls are safe to repeat |
| Sends `POST` and `PATCH` **once** | A create that timed out may have run; repeat it yourself with the same `client_order_id` |
| Needs `apiKey` + `apiSecret` for every call except `getOrderBook` and the signup calls | For the other public reads without a key, call `GET /v1/prices` or `/v1/estimate` with plain `fetch` |

## Step 1 — Create a client

```ts title="TypeScript"
import { TenergyClient } from "@tenergy/sdk";

const tenergy = new TenergyClient({
  baseUrl: "https://api-nile.tenergy.me/v1", // https://api.tenergy.me/v1 on mainnet
  apiKey: process.env.TENERGY_KEY!, // ak_test_… on Nile
  apiSecret: process.env.TENERGY_SECRET!, // shown once, when the key was created
});
```

| Option | Default | Meaning |
|---|---|---|
| `baseUrl` | — (required) | Including `/v1`. Trailing slashes are stripped. |
| `apiKey`, `apiSecret` | — | Required for signed calls. |
| `timeoutMs` | `15000` | Per request. |
| `retry` | `{ maxRetries: 2, baseDelayMs: 200, maxDelayMs: 5000 }` | For idempotent calls only. |
| `fetch` | `globalThis.fetch` | Any fetch-shaped function. |

## Step 2 — Quote, order, wait

```ts title="TypeScript"
const quote = await tenergy.createQuote({
  resource: "energy",
  amount: 65_000,
  tier: "1h",
  receiver: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
});
const order = await tenergy.createOrder({ quote_id: quote.id, client_order_id: "payout-8821" });
const settled = await tenergy.waitForOrder(order.id, { timeoutMs: 30_000 });

if (settled.partial) console.log("delivered", settled.delivered_amount, "of", settled.amount);
```

`waitForOrder` polls `GET /v1/orders/{id}` once a second until the order is `active`, `expired`, `reclaimed`, `failed` or `refunded`. On timeout it throws `TenergyTimeoutError` carrying the last order it saw — the order itself is not cancelled.

## Step 3 — Handle errors

```ts title="TypeScript"
import { TenergyApiError, TenergyTransportError } from "@tenergy/sdk";

try {
  await tenergy.createOrder({ quote_id: quote.id, client_order_id: "payout-8821" });
} catch (error) {
  if (error instanceof TenergyApiError && error.slug === "insufficient_funds") {
    // top up, then repeat the same call: the same client_order_id cannot charge twice
  } else if (error instanceof TenergyTransportError) {
    // the API did not answer: repeat with the same client_order_id
  } else {
    throw error;
  }
}
```

| Class | When | Fields |
|---|---|---|
| `TenergyApiError` | The API answered with an error envelope | `code`, `slug`, `httpStatus`, `field`, `retryable`, `details`, `requestId`, `retryAfterSeconds` |
| `TenergyTransportError` | No answer, or one that is not an envelope (a proxy's 502) | `httpStatus`, `bodyExcerpt` |
| `TenergyTimeoutError` | `waitForOrder` ran out of time | `waitedMs`, `last` |

Branch on `slug`, never on `message`.

## Step 4 — Verify a webhook

```ts title="TypeScript"
import { verifyWebhookSignature } from "@tenergy/sdk";

// rawBody: the request body exactly as received, before any JSON parsing
const valid = verifyWebhookSignature(
  process.env.TENERGY_WEBHOOK_SECRET!,
  request.headers["x-api-timestamp"],
  rawBody,
  request.headers["x-api-sign"],
);
```

It checks the signature only; check that `X-API-TIMESTAMP` is within ±300 s yourself, as [Webhooks](https://tenergy.me/docs/webhooks#signature) shows.

## Methods

| Area | Methods |
|---|---|
| Signup | `createAccountChallenge`, `verifyAccountChallenge`, `createAccount`, `getSignupDepositAddress`, `bootstrap` (all five steps in one call; your `signMessage` signs) |
| Account | `getAccount`, `getBalance`, `listDepositAddresses` |
| Pricing | `getPrices`, `estimateOrder`, `getOrderBook`, `createQuote`, `getQuote` |
| Orders | `createOrder`, `listOrders`, `getOrder` (our id or `cid:<client_order_id>`), `reclaimOrder`, `waitForOrder` |
| Batches | `createBatch`, `listBatches`, `getBatch`, `cancelBatch` |
| Subscriptions | `createSubscription`, `listSubscriptions`, `getSubscription`, `updateSubscription`, `cancelSubscription` |
| Webhooks | `createWebhook`, `listWebhooks`, `getWebhook`, `updateWebhook`, `deleteWebhook`, `rotateWebhookSecret`, `testWebhook` |
| Chain | `getAddressResources`, `estimateTransferEnergy` |
| API keys | `listApiKeys`, `createApiKey`, `updateApiKey`, `deleteApiKey` |
| Signing | `signRequest`, `canonicalString`, `apiTimestamp`, `webhookSignature`, `verifyWebhookSignature` |

## Next steps

- [API reference](https://tenergy.me/docs/api/orders) — the fields behind every method.
- [Errors & rate limits](https://tenergy.me/docs/errors) — every `slug` a `TenergyApiError` can carry.
- [Webhooks](https://tenergy.me/docs/webhooks) — `order.confirmed` instead of `waitForOrder`.
