---
title: "HTTP Client"
description: "Call the account API with the same token used to start shards."
canonical: "https://developers.fnlb.net/docs/sdk/http-client"
markdown: "/docs/sdk/http-client.md"
---

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

# HTTP Client

---
title: "HTTP client"
sidebarTitle: "HTTP client"
description: "Use fnlb.api to call https://api.fnlb.net/v1 with the token passed to start()."
icon: "api"
---

`fnlb.api` is created inside `start()`, using the same token. It calls `https://api.fnlb.net/v1`. Set `apiBaseUrl` on the `FNLB` constructor to point somewhere else.

OAuth tokens still need the scope each route requires. See [OAuth2](/docs/getting-started/oauth2.md#scopes) and the [API reference](/api.md) for the route list. Limits are the same per-route limits as a direct HTTP call. See [Rate limits](/docs/api/rate-limits.md).

A non-2xx response throws `ApiError`. `status` is the HTTP status, `path` is the route, and `body` is the parsed error payload.

```ts
import FNLB, { ApiError } from 'fnlb';

const fnlb = new FNLB();
await fnlb.start({ token: 'FNLB_...', categories: ['your-category-id'] });

try {
  const user = await fnlb.api.user.get({ includeEmail: true });
  const bots = await fnlb.api.bots.list();
  console.log(user.username, bots.length);
} catch (error) {
  if (error instanceof ApiError) console.error(error.status, error.body);
}
```

<Info>
`fnlb.api` is undefined until `start()` has run. It is not a substitute for local party meta. Meta is only on the shard. See [Bots and parties](/docs/sdk/parties.md#meta).
</Info>

## Account

| Call | What it does |
| :--- | :--- |
| `api.user.get()` | The account that owns the token. `includeEmail: true` adds the email when the token may read it. |
| `api.bots.list()` / `get(id)` | Every bot on the account, including ones this process is not running. |
| `api.bots.create` / `update` / `delete` | Change bots. |
| `api.bots.stats()` | Counts for the account. |
| `api.bots.bulkExport` / `bulkImport` | Move device auth in bulk. Same limits as the [bulk guide](/docs/api/bulk-operations.md). |
| `api.categories.list` / `get` / `create` / `update` / `delete` | Categories. `delete(id, { includeBots: true })` also deletes bots in that category. |
| `api.presets.list` / `get` / `create` / `update` / `delete` | Cosmetic presets. Creating and listing presets requires a VIP account, or staff. |

## Commands and chat

Cloud commands work for a bot that is not running in this process. Local `bot.runCommand` does not round-trip through the API.

```ts
const bots = await fnlb.api.bots.list();
const id = bots[0].id;

await fnlb.api.bots.commands.run(id, { command: 'outfit', args: ['renegade'] });
await fnlb.api.bots.chat.send(id, { content: 'hello from the api' });
```

`chat.send` sends one line and waits for the bot's reply text. It is not party meta and it is not the local `Message` event.

## Friends and party snapshot

These routes read a bot that is connected to FNLB. They are not the local IPC friends list.

```ts
await fnlb.api.bots.friends.list(botId);
await fnlb.api.bots.friends.pending(botId);
await fnlb.api.bots.users.search(botId, 'name');
await fnlb.api.bots.users.blocked(botId);
await fnlb.api.bots.party.member(botId, memberId);
```

`party.member` is the cosmetic snapshot from the API, not `bot.party.me.meta`.

## Creating a bot

`create` stores Epic device auth. All three device fields are required. `parent` is the category id.

```ts
const created = await fnlb.api.bots.create({
  nickname: 'Alpha',
  parent: 'category-id',
  deviceAuth: {
    accountId: 'epic-account-id',
    deviceId: 'device-id',
    secret: 'device-secret'
  }
});

await fnlb.api.bots.update(created.id, { disabled: false });
```

`update` accepts `nickname`, `email`, `parent` (`null` removes the category), `disabled`, and a replacement `deviceAuth`. `delete` removes that bot. `categories.delete(id, { includeBots: true })` also deletes the bots in the category.

`bulkExport({ deleteAfterExport: false })` returns device auth for the account. `bulkImport` writes a list back. Both follow the [bulk operations](/docs/api/bulk-operations.md) limits. Treat the secret as a credential.

## Scopes

An API token can call the routes that token is allowed to call. An OAuth access token needs the scope on the route. The usual ones for this client:

| Work | Scopes |
| :--- | :--- |
| `start()` | `bots.run`, `categories.read`, `bots.read` |
| List and edit bots | `bots.read`, `bots.write` |
| Categories | `categories.read`, `categories.write` |
| Cloud command or chat | The bots scope that route documents in the [API reference](/api.md) |

A missing scope is `ApiError` with status 401 or 403. `body` is the API error payload.

## Reference

| Topic | Where |
| :--- | :--- |
| Client | [`RestClient`](/sdk?symbol=RestClient) |
| Thrown error | [`ApiError`](/sdk?symbol=ApiError) |
| Bot record | [`ApiBot`](/sdk?symbol=ApiBot) |
| Command reply | [`ApiCommandResult`](/sdk?symbol=ApiCommandResult) |
| Route list and scopes | [API reference](/api.md) |
| Local equivalent of commands and chat | [Chat and commands](/docs/sdk/chat-and-commands.md) |
