# Build (Full)

---
source: /docs/build.md

# Build

Bkper is designed to be extended. Whether you're piping CLI commands in a shell script or building a full platform app with a UI, events, and managed hosting — the same APIs and tools power everything.

## The building spectrum

**Scripts & CLI** — Pipe data through the CLI, write Node.js scripts, or call the REST API directly. No infrastructure needed.

**Google Workspace** — Build automations with Apps Script, extend Google Sheets with custom functions and triggers.

**Platform Apps** — Full applications with managed hosting, authentication, event handling, and deployment on the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md).

## Where to start

  - [Development Setup](http://bkper.com/docs/build/getting-started/setup.md): Install the CLI, authenticate, and verify your environment.
  - [Quick Wins](http://bkper.com/docs/build/getting-started/quick-wins.md): The fastest ways to create value with Bkper programmatically.
  - [Your First App](http://bkper.com/docs/build/getting-started/first-app.md): Build and deploy a platform app in minutes.
  - [The Agent Model](http://bkper.com/docs/build/concepts/agent-model.md): How apps, bots, and integrations are identified in Bkper.

## Build by section

  - [Scripts & Integrations](http://bkper.com/docs/build/scripts/cli-pipelines.md): CLI piping, Node.js scripts, and direct API usage.
  - [Apps](http://bkper.com/docs/build/apps/overview.md): The Bkper Platform — managed hosting, auth, events, and deployment.
  - [Google Workspace](http://bkper.com/docs/build/google-workspace/apps-script.md): Apps Script development and Sheets integrations.
  - [Tools](http://bkper.com/docs/build/tools/cli.md): CLI and libraries for building on Bkper.
  - [Examples & Patterns](http://bkper.com/docs/build/examples.md): Real-world open source projects to learn from.

---
source: /docs/build/apps/app-listing.md

# App Listing

All Bkper apps are listed on the Automations Portal at _[app.bkper.com](https://app.bkper.com/) > Automations > Apps_. Each app has its own page with logo, description, and details:

![App listing on the Automations Portal](http://bkper.com/docs/_astro/bkper-app-listing.BgcbAsjE.png)

App listings are populated from the fields you declare in [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md). Sync metadata changes with `bkper app sync`. Deploying code is a separate step.

## Listing fields

Make sure your `bkper.yaml` has the following fields populated for a complete listing:

```yaml
id: your-app-id
name: Your App Name
description: A clear description of what your app does

logoUrl: https://your-app.bkper.app/images/logo.svg
logoUrlDark: https://your-app.bkper.app/images/logo-dark.svg

ownerName: Your Name or Organization
ownerWebsite: https://yourwebsite.com

website: https://your-app.bkper.app
```

See [App Configuration](http://bkper.com/docs/build/apps/configuration.md) for the full `bkper.yaml` reference.

## Default visibility

By default, installation is limited to the users you've declared in `bkper.yaml`:

```yaml
# Specific Bkper usernames
users: alice bob

# Your entire domain
users: *@yourcompany.com
```

Use Bkper usernames for individual access, not email addresses.

Your team can install and use the app, but it doesn't appear in the public Bkper app directory for other users.

## Publishing to all users

To make your app available to all Bkper users, contact us at [support@bkper.com](mailto:support@bkper.com?subject=Publish+Bkper+App). We'll review your app and, once approved, publish it.

### What the review involves

- **Functionality check** — The app works correctly and handles errors gracefully
- **Security review** — Event handlers are idempotent and include loop prevention
- **Listing quality** — The app has a clear name, description, and logo

### Where published apps appear

Once published, your app appears in:

- **[bkper.com/apps](https://bkper.com/apps)** — The public app directory
- **Automations Portal** — Inside every Bkper book, users can find and install your app

---
source: /docs/build/apps/architecture.md

# App Architecture

Bkper platform apps follow a three-package monorepo pattern. Each package handles a distinct concern, all deployed to the same `{appId}.bkper.app` domain.

## Structure

```
packages/
├── shared/     — Shared types and utilities
├── web/
│   ├── client/ — Frontend UI (Vite + Lit)
│   └── server/ — Backend API (Hono)
└── events/     — Event handler (webhooks)
```

The packages are connected via [Bun workspaces](https://bun.sh/docs/install/workspaces). Import shared code from `@my-app/shared` in any package.

## Web client

The client package builds a browser UI with [Lit](https://lit.dev/) and [@bkper/web-design](https://www.npmjs.com/package/@bkper/web-design) for consistent Bkper styling. Authentication uses [@bkper/web-auth](https://www.npmjs.com/package/@bkper/web-auth).

- Built with [Vite](https://vitejs.dev/) — configured in the project's `vite.config.ts` for fast builds and HMR during development
- Static assets served by the web server handler
- Communicates with Bkper via `bkper-js` (authenticated with the web-auth SDK)

This is where your app's UI lives — book pickers, account lists, reports, forms.

## Web server

The server package runs on [Cloudflare Workers](https://developers.cloudflare.com/workers/) using [Hono](https://hono.dev/) as the web framework. It handles:

- Serving the client's static assets
- Custom API routes for your app's backend logic
- Type-safe access to platform services (KV, secrets) via `c.env`

```ts
import { Hono } from 'hono';
import type { Env } from '../../../../env.js';

const app = new Hono<{ Bindings: Env }>();

app.get('/api/data', async c => {
    const cached = await c.env.KV.get('my-key');
    return c.json({ data: cached });
});

export default app;
```

## Events handler

The events package receives webhook calls from Bkper when subscribed [events](http://bkper.com/docs/build/concepts/events.md) occur. It's a separate Hono app that processes events and returns responses.

```ts
import { Hono } from 'hono';
import { Bkper, Book } from 'bkper-js';
import { handleTransactionChecked } from './handlers/transaction-checked.js';
import type { Env } from '../../../env.js';

const app = new Hono<{ Bindings: Env }>().basePath('/events');

app.post('/', async c => {
    const event: bkper.Event = await c.req.json();

    if (!event.book) {
        return c.json({ error: 'Missing book in event payload' }, 400);
    }

    const apiKey = c.env.BKPER_API_KEY;
    const bkper = new Bkper({
        apiKeyProvider: apiKey ? async () => apiKey : undefined,
        oauthTokenProvider: async () => c.req.header('bkper-oauth-token'),
        agentIdProvider: async () => c.req.header('bkper-agent-id'),
    });
    const book = new Book(event.book, bkper.getConfig());

    switch (event.type) {
        case 'TRANSACTION_CHECKED':
            return c.json(await handleTransactionChecked(book, event));
        default:
            return c.json({ result: false });
    }
});

export default app;
```

Event handlers run at `https://{appId}.bkper.app/events` in production. During development, a Cloudflare tunnel routes events to your local machine.

See [Event Handlers](http://bkper.com/docs/build/apps/event-handlers.md) for patterns and details.

## Shared package

Common types, utilities, and constants used across packages:

```ts
// packages/shared/src/types.ts
export interface EventResult {
    result?: string | string[] | boolean;
    error?: string;
    warning?: string;
}

// packages/shared/src/constants.ts
export const APP_NAME = 'my-app';
```

Import in any package:

```ts
import type { EventResult } from '@my-app/shared';
```

> **Note:** The `Env` type (KV bindings, secrets) lives in the root `env.d.ts` file, auto-generated from `bkper.yaml`. Import it as `import type { Env } from '../../../env.js'` — it is not part of the shared package.

## When you don't need all three

Not every app needs a UI, API, and event handler:

- **Event-only app** — Just the `events` package. Automates reactions to book events without a user interface. Remove the `web` section from `bkper.yaml`.
- **UI-only app** — Just the `web` packages. Opens via a [context menu](http://bkper.com/docs/build/apps/context-menu.md) to display data or collect input. Remove the `events` section from `bkper.yaml`.
- **Full app** — All three packages. Interactive UI with backend logic and event-driven automation.

The template includes all three by default. Remove what you don't need.

---
source: /docs/build/apps/configuration.md

# App Configuration

The `bkper.yaml` file is the single configuration file for your Bkper app. It defines the app's identity, access control, menu integration, event handling, and deployment settings.

It lives in the root of your project. Use `bkper app sync` to push metadata changes to Bkper, and use `bkper app deploy` to upload built code to the platform.

## Minimal example

```yaml
id: my-app
name: My App
description: A Bkper app that does something useful
developers: myuser
```

## Full example

From the [app template](https://github.com/bkper/bkper-app-template):

```yaml
id: my-app
name: My App
description: A Bkper app that does something useful

logoUrl: https://my-app.bkper.app/images/logo-light.svg
logoUrlDark: https://my-app.bkper.app/images/logo-dark.svg

website: https://bkper.com/apps/bkper-cli
ownerName: Bkper
ownerLogoUrl: https://avatars.githubusercontent.com/u/11943086?v=4
ownerWebsite: https://bkper.com

repoUrl: https://github.com/bkper/bkper-app-template
repoPrivate: true

developers: someuser *@yoursite.com
users: someuser *@yoursite.com

menuUrl: https://my-app.bkper.app?bookId=${book.id}
menuUrlDev: http://localhost:8787?bookId=${book.id}
menuPopupWidth: 500
menuPopupHeight: 300

webhookUrl: https://my-app.bkper.app/events
apiVersion: v5
events:
    - TRANSACTION_CHECKED

deployment:
    web:
        main: packages/web/server/src/index.ts
        client: packages/web/client
    events:
        main: packages/events/src/index.ts
    services:
        - KV
    secrets:
        - BKPER_API_KEY
    compatibility_date: '2026-01-28'
```

### App identity

| Field         | Description                                                                                               |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| `id`          | Permanent app identifier. Lowercase letters, numbers, and hyphens only. Cannot be changed after creation. |
| `name`        | Display name shown in the Bkper UI.                                                                       |
| `description` | Brief description of what the app does.                                                                   |

### Branding

| Field         | Description                                |
| ------------- | ------------------------------------------ |
| `logoUrl`     | App logo for light mode (SVG recommended). |
| `logoUrlDark` | App logo for dark mode.                    |
| `website`     | App website or documentation URL.          |

### Ownership

| Field          | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| `ownerName`    | Developer or company name.                                   |
| `ownerLogoUrl` | Owner's logo/avatar URL.                                     |
| `ownerWebsite` | Owner's website.                                             |
| `repoUrl`      | Source code repository URL.                                  |
| `repoPrivate`  | Whether the repository is private.                           |
| `deprecated`   | Hides from app listings; existing installs continue working. |

### Access control

| Field        | Description                                                                                                                   |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `developers` | Who can update the app and deploy new versions. Comma-separated Bkper usernames. Supports domain wildcards: `*@yoursite.com`. |
| `users`      | Who can install and use the app. Same format as developers. Leave empty for public apps.                                      |

### Menu integration

| Field             | Description                                                                 |
| ----------------- | --------------------------------------------------------------------------- |
| `menuUrl`         | Production menu URL. Supports [variable substitution](#menu-url-variables). |
| `menuUrlDev`      | Development menu URL (used when the developer clicks the menu).             |
| `menuText`        | Custom menu text (defaults to app name).                                    |
| `menuPopupWidth`  | Popup width in pixels.                                                      |
| `menuPopupHeight` | Popup height in pixels.                                                     |

See [Context Menu](http://bkper.com/docs/build/apps/context-menu.md) for details on building menu integrations.

### Menu URL variables

The following variables can be used in `menuUrl` and `menuUrlDev`:

| Variable                    | Description                              |
| --------------------------- | ---------------------------------------- |
| `${book.id}`                | Current book ID                          |
| `${book.properties.xxx}`    | Book property value                      |
| `${account.id}`             | Selected account ID                      |
| `${account.name}`           | Selected account name                    |
| `${account.properties.xxx}` | Account property value                   |
| `${group.id}`               | Selected group ID                        |
| `${group.name}`             | Selected group name                      |
| `${group.properties.xxx}`   | Group property value                     |
| `${transactions.ids}`       | Comma-separated selected transaction IDs |
| `${transactions.query}`     | Current search query                     |

### Event handling

| Field           | Description                                                                     |
| --------------- | ------------------------------------------------------------------------------- |
| `webhookUrl`    | Production webhook URL for receiving events.                                    |
| `webhookUrlDev` | Development webhook URL (auto-updated by `bkper app dev`).                      |
| `apiVersion`    | API version for event payloads (currently `v5`).                                |
| `events`        | List of [event types](http://bkper.com/docs/build/concepts/events.md#event-types) to subscribe to. |

See [Event Handlers](http://bkper.com/docs/build/apps/event-handlers.md) for details on handling events.

### File patterns

| Field          | Description                                                                                                            |
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `filePatterns` | List of glob patterns (e.g., `*.ofx`, `*.csv`). When a matching file is uploaded, a `FILE_CREATED` event is triggered. |

### Properties schema

The `propertiesSchema` field defines autocomplete suggestions for custom properties in the Bkper UI, helping users discover the correct property keys and values for your app:

```yaml
propertiesSchema:
    book:
        keys:
            - my_app_enabled
        values:
            - 'true'
            - 'false'
    group:
        keys:
            - my_app_category
    account:
        keys:
            - my_app_sync_id
    transaction:
        keys:
            - my_app_reference
```

### Deployment

For apps deployed to the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md):

| Field                           | Description                                                                                                            |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `deployment.web.main`           | Entry point for the web handler (serves UI and API).                                                                   |
| `deployment.web.client`         | Directory for static client assets.                                                                                    |
| `deployment.events.main`        | Entry point for the events handler (processes webhooks).                                                               |
| `deployment.services`           | Platform services to provision. Currently: `KV` (key-value storage).                                                   |
| `deployment.secrets`            | Secret names used by the app. Managed via `bkper app secrets`.                                                         |
| `deployment.compatibility_date` | [Cloudflare Workers compatibility date](https://developers.cloudflare.com/workers/configuration/compatibility-dates/). |

See [Building & Deploying](http://bkper.com/docs/build/apps/deploying.md) for the full deployment workflow.

---
source: /docs/build/apps/context-menu.md

# Context Menu

Apps can add context menu items on the Transactions page **More** menu in your Books. This lets you open dynamically built URLs with reference to the current Book's context — the active query, selected account, date range, and more.

## How it works

Once you install an App with a menu configuration, a new menu item appears in your Book:

![Custom menu item in the More menu](http://bkper.com/docs/_astro/bkper-report-menu.eu_pyhWe.png)

When clicked, a popup opens carrying the particular context of that book at that moment:

![App menu popup with book context](http://bkper.com/docs/_astro/bkper-app-menu-popup.BQ95Y-ki.png)

## Configuration

Configure the menu URL in your [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md):

```yaml
menuUrl: https://my-app.bkper.app?bookId=${book.id}&query=${transactions.query}
```

When the user clicks the menu item, the URL expressions `${xxxx}` are replaced with contextual information from the Book:

```
https://my-app.bkper.app?bookId=abc123&query=account:Sales
```

Where `abc123` is the current Book id and `account:Sales` is the current query being executed.

### Development URL

Use `menuUrlDev` for a separate URL during development:

```yaml
menuUrl: https://my-app.bkper.app?bookId=${book.id}&query=${transactions.query}
menuUrlDev: http://localhost:8787?bookId=${book.id}&query=${transactions.query}
```

The development URL is used when the app developer is the one clicking the menu item.

### Popup dimensions

Control the popup size with:

```yaml
menuPopupWidth: 800
menuPopupHeight: 600
```

### Available expressions

The menu URL supports these dynamic expressions:

| Expression | Description |
| --- | --- |
| `${book.id}` | The current Book ID |
| `${transactions.query}` | The current query string |
| `${account.id}` | The selected account ID |
| `${account.name}` | The selected account name |
| `${group.id}` | The selected group ID |
| `${group.name}` | The selected group name |

For the full list of accepted expressions, see the [Menu URL variables](http://bkper.com/docs/build/apps/configuration.md#menu-url-variables) reference.

---
source: /docs/build/apps/deploying.md

# Building & Deploying

## The deployment workflow

1. **Build** — Compile your code

   ```bash
   npm run build
   ```

   This runs two build steps:
   - Client (Vite) to static assets in `dist/web/client/`
   - Worker bundles (esbuild) — web server to `dist/web/server/`, events handler to `dist/events/`

   Build output includes size reporting so you can monitor bundle sizes.

2. **Sync** — Update app metadata

   ```bash
   bkper app sync
   ```

   Syncs your `bkper.yaml` configuration to Bkper — name, description, menu URLs, webhook URLs, access control, and branding. Run this whenever you change app settings.

3. **Deploy** — Upload code to the platform

   ```bash
   bkper app deploy
   ```

   Deploys your pre-built code from `dist/` to the Bkper Platform. Your app is live at `https://{appId}.bkper.app`.

The typical workflow combines all three:

```bash
npm run build && bkper app sync && bkper app deploy
```

### Production

The default deployment target. Your app runs at `https://{appId}.bkper.app`.

```bash
bkper app deploy
```

### Preview

Deploy to a separate preview environment for testing before production:

```bash
bkper app deploy --preview
```

Preview has independent secrets and KV storage from production.

### Independent handler deployment

Deploy only the events handler:

```bash
bkper app deploy --events
```

Useful when you've only changed the events handler and want a faster deployment. Web is deployed by default.

## Secrets management

Secrets are environment variables stored securely on the platform. Declare them in `bkper.yaml`:

```yaml
deployment:
    secrets:
        - BKPER_API_KEY
        - EXTERNAL_SERVICE_TOKEN
```

### Setting secrets

```bash
# Set for production
bkper app secrets put BKPER_API_KEY

# Set for preview
bkper app secrets put BKPER_API_KEY --preview
```

You'll be prompted to enter the value.

### Listing and deleting

```bash
# List all secrets
bkper app secrets list

# Delete a secret
bkper app secrets delete BKPER_API_KEY
```

### Accessing in code

Secrets are available as `c.env.SECRET_NAME` in your Hono handlers:

```ts
app.get('/api/data', async (c) => {
    const apiKey = c.env.BKPER_API_KEY;
    // use apiKey
});
```

During local development, use the `.dev.vars` file instead. See [Development Experience](http://bkper.com/docs/build/apps/development.md#local-secrets).

### KV storage

Declare KV in `bkper.yaml`:

```yaml
deployment:
    services:
        - KV
```

The platform provisions a KV namespace for your app. Access it via `c.env.KV`:

```ts
await c.env.KV.put('key', 'value', { expirationTtl: 3600 });
const value = await c.env.KV.get('key');
```

KV storage is separate between production and preview environments.

## Deployment status

Check the current state of your deployment:

```bash
bkper app status
```

## Installing on books

After deploying, install the app on specific books to activate it:

```bash
# Install on a book
bkper app install <appId> -b <bookId>

# Uninstall from a book
bkper app uninstall <appId> -b <bookId>
```

Once installed, the app's [event handlers](http://bkper.com/docs/build/apps/event-handlers.md) receive events from that book, and the app's [context menu](http://bkper.com/docs/build/apps/context-menu.md) appears in the book's UI.

---
source: /docs/build/apps/development.md

# Development Experience

Local development uses two composable processes — the worker runtime and the client dev server — that run concurrently.

## What runs

```bash
npm run dev
```

The project template runs both processes via `concurrently`:

1. **`vite dev`** — Client dev server with HMR. Changes to Lit components reflect instantly in the browser. Configured in `vite.config.ts`.
2. **`bkper app dev`** — The worker runtime:
   - **Miniflare** — Simulates the Cloudflare Workers runtime locally for the web server and events handler.
   - **Cloudflare tunnel** — Exposes the events handler via a public URL so Bkper can route webhook events to your machine.
   - **File watching** — Server and shared package changes trigger automatic rebuilds via esbuild.

You can also run them independently: `npm run dev:client` for just the UI, or `npm run dev:server` / `npm run dev:events` for specific workers.

## URLs

| Handler | URL |
| --- | --- |
| Client (Vite dev server) | `http://localhost:5173` |
| Web server (Miniflare) | `http://localhost:8787` |
| Events (via tunnel) | `https://<random>.trycloudflare.com/events` |

The Vite dev server proxies `/api` requests to `http://localhost:8787` (configured in `vite.config.ts`). The tunnel URL is automatically registered as the `webhookUrlDev` in Bkper, so events from books where you're the developer are routed to your local machine.

## Configuration flags

You can run specific handlers independently:

```bash
# Start only the web worker
bkper app dev --web

# Start only the events worker
bkper app dev --events

# Override default ports
bkper app dev --sp 8787 --ep 8791
```

## Client configuration

The client dev server is configured in `vite.config.ts` at the project root. This is a standard Vite config — add plugins, adjust settings, or customize the dev server as needed.

The template includes a Bkper auth middleware plugin that handles OAuth token refresh during local development, and an `/api` proxy to the Miniflare worker.

## Local secrets

Environment variables for local development live in a `.dev.vars` file at the project root:

```bash
# .dev.vars (gitignored)
BKPER_API_KEY=your-api-key-here
```

Copy from the provided template:

```bash
cp .dev.vars.example .dev.vars
```

These variables are available as `c.env.SECRET_NAME` in your Hono handlers during development.

## KV storage

KV data persists locally in the `.mf/kv/` directory during development. This means your data survives restarts — useful for testing caching and state patterns.

```ts
// Read
const value = await c.env.KV.get('my-key');

// Write with TTL
await c.env.KV.put('my-key', 'value', { expirationTtl: 3600 });
```

See the [Cloudflare KV documentation](https://developers.cloudflare.com/kv/) for more usage patterns.

## Type generation

The `env.d.ts` file provides TypeScript types for the Worker environment — KV bindings, secrets, and other platform services. It's auto-generated based on your `bkper.yaml` configuration and checked into version control.

Rebuild it after changing services or secrets in `bkper.yaml`:

```bash
bkper app build
```

## The development loop

1. Run `npm run dev`
2. Edit client code — see changes instantly via Vite HMR
3. Edit server code — auto-rebuilds and reloads via esbuild watch
4. Trigger events in Bkper — your local handler receives them via the tunnel
5. Check the activity stream in Bkper to see handler responses
6. Iterate

## Debugging

- **Server errors** — Check the terminal output from `bkper app dev`. Worker runtime errors appear here.
- **Event handler errors** — Check the Bkper activity stream. Click on an event handler response to see the result or error, and replay failed events.
- **Client errors** — Use browser DevTools. The Vite dev server provides source maps.

---
source: /docs/build/apps/event-handlers.md

# Event Handlers

Event handlers are the code that reacts to [events](http://bkper.com/docs/build/concepts/events.md) in your Bkper Books. When a transaction is checked, an account is created, or any other event occurs, your handler receives it and can take action — calculate taxes, sync data between books, post to external services, and more.

![Bkper Event Handler](https://bkper.com/images/bots/bkper-tax-bot/bkper-tax-bot.gif)

## How it works

1. You declare which events your app handles in [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md)
2. Bkper sends an HTTP POST to your webhook URL when those events fire
3. Your handler processes the event and returns a response

On the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md), events are routed to your `events` package automatically — including local development via tunnels. For [self-hosted](http://bkper.com/docs/build/apps/self-hosted.md) setups, you configure the webhook URL directly.

## Agent identity

Event handlers **run on behalf of the user who installed the app**. Their transactions and activities are identified in the UI by the app's logo and name:

![Event handler agents identified in the activity stream](http://bkper.com/docs/_astro/bkper-bot-agents.CtsWIZEd.png)

## Responses

Handler responses are recorded in the activity that triggered the event. You can view and replay them by clicking the response at the bottom of the activity:

![Event handler responses in the activity stream](http://bkper.com/docs/_astro/bkper-bot-responses.UQXhqdai.png)

### Response format

Your handler must return a response in this format:

```ts
{ result?: string | string[] | boolean; error?: string; warning?: string }
```

- The `result` is recorded as the handler response in the book activity
- If you return `{ result: false }`, the response is suppressed and not recorded
- Errors like `{ error: "This is an error" }` show up as error responses

To show the full error stack trace for debugging:

```ts
try {
    // handler logic
} catch (err) {
    return { error: err instanceof Error ? err.message : String(err) };
}
```

### HTML in responses

If you return an **HTML snippet** (e.g., a link) in the result, it will be rendered in the response popup.

## Development mode

Event handlers run in _Development Mode_ when executed by the **developer or owner** of the App.

In development mode, both successful results and errors are shown as responses:

![Event handler error in development mode](http://bkper.com/docs/_astro/bkper-bot-error.4eq2AKEM.png)

You can click a response to **replay** failed executions — useful for debugging without recreating the triggering event.

To find transactions with bot errors in a book, run the query:

```
error:true
```

## Preventing loops

When your event handler creates or modifies transactions, those changes fire new events. To prevent infinite loops, check the `event.agent.id` field:

```ts
function handleEvent(event: bkper.Event) {
    // Skip events triggered by this app
    if (event.agent?.id === 'your-app-id') {
        return { result: false };
    }

    // Process the event
    // ...
}
```

This pattern is essential for any handler that writes back to the same book.

## Event routing pattern

On the Bkper Platform, the `events` package uses [Hono](https://hono.dev) to receive webhook calls. A typical pattern routes events by type:

```ts
import { Bkper, Book } from 'bkper-js';

app.post('/', async c => {
    const event: bkper.Event = await c.req.json();

    if (!event.book) {
        return c.json({ error: 'Missing book in event payload' }, 400);
    }

    const bkper = new Bkper({
        oauthTokenProvider: async () => c.req.header('bkper-oauth-token'),
        agentIdProvider: async () => c.req.header('bkper-agent-id'),
    });
    const book = new Book(event.book, bkper.getConfig());

    switch (event.type) {
        case 'TRANSACTION_CHECKED':
            return c.json(await handleTransactionChecked(book, event));
        default:
            return c.json({ result: false });
    }
});
```

For the full event type reference, see [Events](http://bkper.com/docs/build/concepts/events.md).

---
source: /docs/build/apps/overview.md

# The Bkper Platform

The Bkper Platform is a complete managed environment for building, deploying, and hosting apps on Bkper. It removes infrastructure complexity so you can focus on business logic.

### Hosting

Apps are deployed to `{appId}.bkper.app` on a global edge network powered by [Cloudflare Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/). Your app runs close to your users, with zero infrastructure to manage.

Preview environments are built in — deploy to a preview URL to test before going to production.

### Authentication

OAuth is pre-configured. No client IDs, no redirect URIs, no consent screens to build. In your client code, call `auth.getAccessToken()` and the platform handles the rest. In your event handlers, the user's OAuth token arrives automatically in the request headers.

### Services

Declare the services you need in [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md) and the platform provisions them:

- **KV storage** — Key-value storage for caching and state. Access via `c.env.KV` in your handlers.
- **Secrets** — Securely stored environment variables. Set via `bkper app secrets put`, access via `c.env.SECRET_NAME`.

### Developer experience

The project template composes the full development environment:

```bash
npm run dev
```

This runs two processes concurrently: `vite dev` for the client UI (HMR), and `bkper app dev` for the worker runtime (Miniflare for your server and event handlers, plus a Cloudflare tunnel so Bkper can route webhook events to your laptop). Your entire development environment, running locally.

### Deployment

Build and deploy your app:

```bash
npm run build && bkper app sync && bkper app deploy
```

Your app is live at `{appId}.bkper.app`. The platform handles routing, SSL, and edge distribution.

## What you'd build yourself without it

Without the platform, creating a Bkper app with a UI, event handling, and authentication requires:

| Concern | Without the platform | With the platform |
| --- | --- | --- |
| **Hosting** | Provision servers, configure domains, SSL, CDN | `bkper app deploy` |
| **Authentication** | Register OAuth client, build consent screen, handle token refresh, manage redirect URIs | `auth.getAccessToken()` |
| **Event webhooks** | Set up a public endpoint, configure DNS, handle JWT verification | Declare in `bkper.yaml`, platform routes events |
| **Local dev webhooks** | Install ngrok or similar, manually configure tunnel URL | `bkper app dev` starts tunnel automatically |
| **Secrets** | Set up a secrets manager, configure access | `bkper app secrets put` |
| **KV storage** | Deploy Redis/Memcached, manage connections | Declare `KV` in `bkper.yaml` |
| **Preview environments** | Build a staging pipeline | `bkper app deploy --preview` |
| **Type safety** | Manually create type definitions | `env.d.ts` auto-generated |

The platform eliminates all of this. You write business logic, the platform handles infrastructure.

## Getting started

```bash
# Create a new app from the template
bkper app init my-app

# Start developing
npm run dev
```

This gives you a working app with a client UI, server API, and event handler — all running locally with full HMR and webhook tunneling.

See [Your First App](http://bkper.com/docs/build/getting-started/first-app.md) for a complete walkthrough, or continue to [App Architecture](http://bkper.com/docs/build/apps/architecture.md) to understand how platform apps are structured.

---
source: /docs/build/apps/self-hosted.md

# Self-Hosted Alternative

The [Bkper Platform](http://bkper.com/docs/build/apps/overview.md) handles hosting, authentication, and deployment for you. However, you can host event handlers on your own infrastructure if you have specific requirements — existing cloud setup, compliance constraints, or legacy apps.

> **Tip**
> Use the Bkper Platform unless you have a specific reason to self-host. It eliminates the need to manage authentication, secrets, hosting, and deployment yourself.
## Cloud Functions

A Bkper event handler running on [Google Cloud Functions](https://cloud.google.com/functions/) receives authenticated calls from the `bkper-hrd@appspot.gserviceaccount.com` service account. You need to grant this service account the [Cloud Functions Invoker IAM role](https://cloud.google.com/functions/docs/securing/managing-access-iam) (`roles/cloudfunctions.invoker`).

Set the production endpoint in [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md):

```yaml
webhookUrl: https://us-central1-my-project.cloudfunctions.net/events
```

### Authentication

An OAuth Access Token **of the user who installed the app** is sent to the production `webhookUrl` endpoint in the `bkper-oauth-token` HTTP header, along with the agent identifier in `bkper-agent-id`, on each event. Your handler uses this token to call the API back on behalf of the user.

The development endpoint (`webhookUrlDev`) does **not** receive these tokens. During development, you need to authenticate locally — this can be simplified using the [CLI](http://bkper.com/docs/build/tools/cli.md).

### Throughput and scaling

Event throughput can be high, especially when processing large batches. Set the [max instance limit](https://cloud.google.com/functions/docs/max-instances#setting_max_instances_limits) — usually **1-2 is enough**. When the function returns `429 Too Many Requests`, the event is automatically retried with incremental backoff until it receives an HTTP `200`.

### Response format

The function response must follow the standard format:

```ts
{ result?: any, error?: any }
```

See [Event Handlers](http://bkper.com/docs/build/apps/event-handlers.md#response-format) for details on response handling.

### Considerations

- Execution environment is subject to [Cloud Function Quotas](https://cloud.google.com/functions/quotas) — quota counts against the developer account, not the end user
- Recommended for scenarios where event throughput exceeds **1 event/second/user** and processing can be handled asynchronously
- Can be combined with context menus built with [Apps Script HTML Service](https://developers.google.com/apps-script/guides/html) or any other UI infrastructure

---

## Generic Webhooks

You can host event handlers on any infrastructure — other cloud providers, containers, on-premise servers.

Configure the same `webhookUrl` property in [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md):

```yaml
webhookUrl: https://my-server.example.com/bkper/events
```

### Authentication

Calls to the production webhook URL are signed with a JWT token using the [Service to Function](https://cloud.google.com/functions/docs/securing/authenticating#service-to-function) method. You can verify this token to assert the identity of the Bkper service.

> **Note**
> Cloud Functions handles JWT verification automatically. For other infrastructure, you need to implement verification yourself. We strongly recommend Cloud Functions for this reason.
### Retry behavior

If your infrastructure returns an HTTP `429` status, the event is automatically retried with incremental backoff until it receives an HTTP `200`. Use this to handle temporary overload gracefully.

---
source: /docs/build/concepts/agent-model.md

# The Agent Model

When you build something that interacts with Bkper — a script, an automation, a full platform app, or even a bank integration — Bkper treats it as an **agent**: any application that can perform actions on books **on behalf of a user**.

These agents can take various forms such as Apps, Bots, Assistants, or even Banks that interact with your books:

<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; align-items: center;">
  <div style="padding-bottom: 20px;">
    [Image: Bkper Agents Model]
  </div>
  <div style="padding-bottom: 20px;">
    <div style="position: relative; padding-bottom: 70.25%; height: 0; overflow: hidden;">
      <iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4px solid lightgrey; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);" src="https://www.youtube.com/embed/ZZ2QUCePgYw" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
    </div>
  </div>
</div>

## Permissions

Agents can only access books that have been explicitly shared with the user they're acting on behalf of. Your code never has elevated access — it operates within the same permission boundaries as the human user who authorized it.

## Identity

Every API request your app makes includes a `bkper-agent-id` header. This lets Bkper attribute actions to the correct agent, so activities and transactions appear with your app's logo and name throughout the Bkper interface — making it easy for book owners to see which entity performed specific actions:

![Agents on Bkper](http://bkper.com/docs/_astro/bkper-app-agents.Dse93XFf.png)

## Bots vs AI Agents

The distinction between a "bot" and an "AI agent" is about capability, not a different type of Bkper primitive. Both are just apps:

| | **Bot** | **AI Agent** |
| --- | --- | --- |
| **Purpose** | Automating predefined tasks | Autonomously perform tasks |
| **Capabilities** | Follows rules; limited learning; basic interactions | Complex, multi-step actions; learns and adapts; makes decisions independently |
| **Interaction** | Reactive; responds to triggers or commands | Proactive; goal-oriented |

In Bkper, what people call "bots" are typically apps whose primary capability is [event handling](http://bkper.com/docs/build/apps/event-handlers.md) — reacting to things that happen in a book. AI agents go further, combining event handling with LLM reasoning to make decisions.

---
source: /docs/build/concepts/authentication.md

# Authentication

All Bkper API access uses [OAuth 2.0](https://developers.google.com/identity/protocols/oauth2) with the `email` scope. The approach depends on your environment.

## CLI and Node.js scripts

The simplest path. The CLI handles the OAuth flow and stores credentials locally.

```bash
bkper auth login
```

Then use `getOAuthToken()` as the auth provider for `bkper-js`:

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});
```

This works for CLI scripts, Node.js automations, and local development of platform apps.

## Browser with an access token

For any browser environment, use `bkper-js` directly with a valid access token via CDN — no build tools required, works on **any domain**:

```html
<script src="https://cdn.jsdelivr.net/npm/bkper-js@2/dist/bkper.min.js"></script>
<script>
    const { Bkper } = bkperjs;

    async function listBooks(token) {
        Bkper.setConfig({
            oauthTokenProvider: async () => token,
        });
        const bkper = new Bkper();
        return await bkper.getBooks();
    }

    // Example: prompt for a token and list books
    document.addEventListener('DOMContentLoaded', () => {
        document.getElementById('go').addEventListener('click', async () => {
            const token = document.getElementById('token').value;
            const books = await listBooks(token);
            document.getElementById('output').textContent =
                books.map(b => b.getName()).join('\n');
        });
    });
</script>

<input id="token" placeholder="Paste your access token" />
<button id="go">List Books</button>
<pre id="output"></pre>
```

Get an access token via the [Bkper CLI](http://bkper.com/docs/build/tools/cli.md):

```bash
bkper auth login   # one-time setup
bkper auth token   # prints a token (valid for 1 hour)
```

Access tokens are automatically refreshed by the CLI and SDKs. When using a token directly (e.g. in a `<script>` tag or with `curl`), run `bkper auth token` again to get a fresh one.

## Web applications on the Bkper Platform

> **Note**
> `@bkper/web-auth` **only works on `*.bkper.app` subdomains**. Its session cookies are scoped to the `.bkper.app` domain and will not work on any other domain. For apps on other domains, use the [Browser with an access token](#browser-with-an-access-token) approach instead.
For apps hosted on `*.bkper.app` subdomains, use the [`@bkper/web-auth`](https://www.npmjs.com/package/@bkper/web-auth) SDK:

```ts
import { Bkper } from 'bkper-js';
import { BkperAuth } from '@bkper/web-auth';

const auth = new BkperAuth({
    onLoginSuccess: () => initializeApp(),
    onLoginRequired: () => showLoginButton(),
});
await auth.init();

Bkper.setConfig({
    oauthTokenProvider: async () => auth.getAccessToken(),
});
```

On the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md), OAuth is pre-configured — no client IDs, redirect URIs, or consent screens to set up. Just use `auth.getAccessToken()` and the platform handles the rest.

See the [@bkper/web-auth API Reference](http://bkper.com/docs/api/bkper-web-auth.md) for the full SDK documentation.

## Google Apps Script

Authentication is handled automatically by the Apps Script runtime. The `bkper-gs` library uses the built-in OAuth token:

```js
function listBooks() {
    var books = BkperApp.getBooks();
    books.forEach(function (book) {
        Logger.log(book.getName());
    });
}
```

No additional authentication setup is needed. See [Apps Script Development](http://bkper.com/docs/build/google-workspace/apps-script.md) for library setup.

## Direct API calls

For any language or platform, send a Bearer token in the Authorization header:

```
Authorization: Bearer YOUR_ACCESS_TOKEN
```

The quickest way to get a token is via the CLI:

```bash
bkper auth token
```

Tokens expire after 1 hour. The CLI and SDKs handle refresh automatically; for direct usage, run the command again.

For custom OAuth 2.0 implementations, see the Google OAuth2 documentation:

- [Server-side Web Apps](https://developers.google.com/identity/protocols/oauth2/web-server)
- [JavaScript Web Apps](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow)
- [Mobile and Desktop Apps](https://developers.google.com/identity/protocols/oauth2/native-app)

## Event handler authentication

When Bkper calls your event handler's webhook URL, it sends:

- `bkper-oauth-token` — An OAuth access token of the user who installed the app. Use this to call the API back on behalf of the user.
- `bkper-agent-id` — The app's agent identifier.

On the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md), these headers are handled automatically. For [self-hosted](http://bkper.com/docs/build/apps/self-hosted.md) setups:

- **Cloud Functions** — The call comes from `bkper-hrd@appspot.gserviceaccount.com` with the user's OAuth token in the header.
- **Generic webhooks** — The call is signed with a JWT token using the [Service to Function](https://cloud.google.com/functions/docs/securing/authenticating#service-to-function) method.

> **Note**
> The development endpoint (`webhookUrlDev`) does **not** receive OAuth tokens. During local development on the Bkper Platform, the auth middleware in `vite.config.ts` handles token refresh for the client, using the CLI's stored credentials. For self-hosted setups, you need to authenticate locally.
## API keys (optional)

API keys are **not required** for authentication. They provide dedicated quota and project-level usage tracking.

If not provided, requests use a shared managed quota via the Bkper API proxy. The default shared quota is **60 requests per minute**.

```ts
Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
    apiKeyProvider: async () => process.env.BKPER_API_KEY,
});
```

See [Direct API Usage](http://bkper.com/docs/build/scripts/rest-api.md#custom-api-key) for API key setup instructions.

---
source: /docs/build/concepts/events.md

# Events

Bkper fires events whenever something changes in a Book — transactions created, accounts updated, collaborators added, and more. These events are the foundation for building automations with [event handlers](http://bkper.com/docs/build/apps/event-handlers.md).

## Subscribing to events

You declare which events your app receives in the `events` list of your [`bkper.yaml`](http://bkper.com/docs/build/apps/configuration.md):

```yaml
events:
    - TRANSACTION_CHECKED
    - TRANSACTION_POSTED
    - ACCOUNT_CREATED
```

When one of these events occurs in a Book where your app is installed, Bkper sends an HTTP POST to your configured webhook URL with the event payload.

## The Event object

When an event handler receives an event, the payload has the following structure:

```ts
{
    /** The id of the Book associated to the Event */
    bookId?: string;

    /** The Book object associated with the Event */
    book?: {
        agentId?: string;
        collection?: Collection;
        createdAt?: string;
        datePattern?: string;
        decimalSeparator?: "DOT" | "COMMA";
        fractionDigits?: number;
        id?: string;
        lastUpdateMs?: string;
        lockDate?: string;
        name?: string;
        ownerName?: string;
        pageSize?: number;
        period?: "MONTH" | "QUARTER" | "YEAR";
        periodStartMonth?: "JANUARY" | "FEBRUARY" | "MARCH" | "APRIL"
            | "MAY" | "JUNE" | "JULY" | "AUGUST" | "SEPTEMBER"
            | "OCTOBER" | "NOVEMBER" | "DECEMBER";
        permission?: "OWNER" | "EDITOR" | "POSTER" | "RECORDER"
            | "VIEWER" | "NONE";
        properties?: { [name: string]: string };
        timeZone?: string;
        timeZoneOffset?: number;
    };

    /** The user in charge of the Event */
    user?: {
        avatarUrl?: string;
        name?: string;
        username?: string;
    };

    /** The Event agent, such as the App, Bot or Bank institution */
    agent?: {
        id?: string;
        logo?: string;
        name?: string;
    };

    /** The creation timestamp, in milliseconds */
    createdAt?: string;

    /** The event data */
    data?: {
        /**
         * The object payload. Depends on the event type.
         * For example, ACCOUNT_CREATED receives an Account payload.
         */
        object?: any;
        /** The object previous attributes when updated */
        previousAttributes?: { [name: string]: string };
    };

    /** The unique id that identifies the Event */
    id?: string;

    /** The resource associated to the Event */
    resource?: string;

    /** The type of the Event */
    type?: EventType;
}
```

The event payload is the same structure exposed by the [REST API](http://bkper.com/docs/build/scripts/rest-api.md). If you use TypeScript, add the [`@bkper/bkper-api-types`](https://www.npmjs.com/package/@bkper/bkper-api-types) package to your project for full type definitions:

```ts
app.post('/', async c => {
    const event: bkper.Event = await c.req.json();
    // handle event...
});
```

## Event types

The list below is the complete current set of event types for API v5.

| Event                   | Description                                                     |
| ----------------------- | --------------------------------------------------------------- |
| `FILE_CREATED`          | A file was attached to the book.                                |
| `FILE_UPDATED`          | An attached file was updated.                                   |
| `TRANSACTION_CREATED`   | A draft transaction was created.                                |
| `TRANSACTION_UPDATED`   | A transaction was updated.                                      |
| `TRANSACTION_DELETED`   | A transaction was deleted.                                      |
| `TRANSACTION_POSTED`    | A draft transaction was posted and now affects balances.        |
| `TRANSACTION_CHECKED`   | A posted transaction was checked (reviewed and locked).         |
| `TRANSACTION_UNCHECKED` | A checked transaction was unchecked and becomes editable again. |
| `TRANSACTION_RESTORED`  | A deleted transaction was restored.                             |
| `ACCOUNT_CREATED`       | An account was created.                                         |
| `ACCOUNT_UPDATED`       | An account was updated.                                         |
| `ACCOUNT_DELETED`       | An account was deleted.                                         |
| `QUERY_CREATED`         | A saved query was created.                                      |
| `QUERY_UPDATED`         | A saved query was updated.                                      |
| `QUERY_DELETED`         | A saved query was deleted.                                      |
| `GROUP_CREATED`         | A group was created.                                            |
| `GROUP_UPDATED`         | A group was updated.                                            |
| `GROUP_DELETED`         | A group was deleted.                                            |
| `COMMENT_CREATED`       | A comment was added.                                            |
| `COMMENT_DELETED`       | A comment was deleted.                                          |
| `COLLABORATOR_ADDED`    | A collaborator was added to the book.                           |
| `COLLABORATOR_UPDATED`  | A collaborator's permissions were updated.                      |
| `COLLABORATOR_REMOVED`  | A collaborator was removed from the book.                       |
| `INTEGRATION_CREATED`   | An integration was created in the book.                         |
| `INTEGRATION_UPDATED`   | An integration was updated.                                     |
| `INTEGRATION_DELETED`   | An integration was deleted.                                     |
| `BOOK_CREATED`          | A book was created.                                             |
| `BOOK_AUDITED`          | A balances audit completed for the book.                        |
| `BOOK_UPDATED`          | Book settings were updated.                                     |
| `BOOK_DELETED`          | The book was deleted.                                           |

## Event data

The `data.object` field contains the resource that was affected. Its shape depends on the event type:

- **Transaction events**: The full Transaction object
- **Account events**: The full Account object
- **Group events**: The full Group object
- **Comment events**: The Comment object
- **Collaborator events**: The Collaborator object

For update events, `data.previousAttributes` contains the fields that changed and their previous values — useful for computing diffs or reacting only to specific field changes.

## The `agent` field

Every event includes information about which agent triggered it. This is important for [preventing loops](http://bkper.com/docs/build/apps/event-handlers.md#preventing-loops) — if your event handler creates a transaction, that will fire a new event, and you need to check `event.agent.id` to avoid reacting to your own actions.

---
source: /docs/build/examples.md

# Examples & Patterns

These are production apps built on Bkper, each demonstrating a different integration pattern. All are open source and available on GitHub.

## Tax Bot

[GitHub](https://github.com/bkper/bkper-tax-bot)

Calculates VAT, GST, and other taxes automatically when transactions are posted. Demonstrates **property-driven configuration** — tax rates and rules are stored in account and group properties, making the bot configurable per-book without code changes.

**What you'll learn:** Using account/group properties to drive behavior, creating related transactions automatically, working with transaction amounts.

## Exchange Bot

[GitHub](https://github.com/bkper/bkper-exchange-bot)

Converts transaction amounts between Books based on updated exchange rates and calculates realized gains and losses. Demonstrates **multi-book synchronization** — when a transaction is checked in one book, the bot creates corresponding entries in connected books.

**What you'll learn:** Mirroring transactions between books, working with exchange rates, gain/loss calculations, cross-book data flow.

## Portfolio Bot

[GitHub](https://github.com/bkper/bkper-portfolio-bot)

Keeps stocks/bonds instruments book in sync with financial books and calculates realized results using FIFO method. Demonstrates **quantity and instrument tracking** — managing financial instruments with both amounts and quantities.

**What you'll learn:** FIFO accounting patterns, tracking instruments with amounts and quantities, synchronized portfolio management.

## Inventory Bot

[GitHub](https://github.com/bkper/bkper-inventory-bot)

Calculates COGS (Cost of Goods Sold) automatically using FIFO method when inventory items are sold. Demonstrates **inventory management patterns** — tracking purchase and sale quantities to compute accurate costs.

**What you'll learn:** Inventory management patterns, purchase/sale quantity tracking, automatic COGS calculation.

## Subledger Bot

[GitHub](https://github.com/bkper/bkper-subledger-bot)

Manages hierarchical relationships between parent and subsidiary books, mapping accounts and groups across ledger levels. Demonstrates **hierarchical ledger relationships** — keeping consolidated and detailed views in sync.

**What you'll learn:** Parent-child book patterns, account/group mapping between books, consolidated reporting.

## Bkper Sheets

[GitHub](https://github.com/bkper/bkper-sheets)

The Google Sheets Add-on — extends Bkper with custom spreadsheet functions, data import/export, and formula-driven reporting. Demonstrates a full **Google Workspace integration**.

**What you'll learn:** Sheets add-on architecture, custom functions, data synchronization between Bkper and Sheets.

---
source: /docs/build/getting-started/agent-security.md

# Agent Security

AI coding agents are powerful but run with your permissions. A misconfigured agent can read credentials, overwrite files, or modify live financial data.

Three layers of protection keep your Bkper development safe:

[Image: Three layers of agent security: sandbox isolation restricts what the agent can reach, credential protection controls how the agent authenticates, and permission scoping limits what the agent can do in Bkper]

## Sandbox isolation

The most effective way to limit an agent is to run it inside a sandbox — a container, micro-VM, or OS-level boundary that restricts what it can reach.

[Image: Sandbox isolation: the agent can only access project files, CLI, and SDK inside the sandbox boundary, while credentials, SSH keys, and other sensitive files on the host machine remain unreachable]

Most agents support a "yolo" or auto-approve mode that skips permission prompts. The per-command approval model sounds safe but creates friction that kills productivity — agents frequently need to run builds, tests, and CLI commands. Pi Agent (and therefore Bkper Agent) runs in yolo mode by default.

Running in a sandbox makes this safe: **full autonomy inside a restricted boundary**.

### Built-in sandboxing

Some agents sandbox themselves without a container. [Claude Code](https://code.claude.com/docs/en/sandboxing) and [Codex](https://developers.openai.com/codex/concepts/sandboxing) both use OS-level primitives (Seatbelt on macOS, bubblewrap on Linux) to enforce filesystem and network isolation. If you use either, enable their sandbox mode before working with real data.

### Container-based sandboxing

For agents without built-in sandboxing — including Pi Agent and Bkper Agent — use a container. We use Docker with [DevContainers](https://containers.dev) and [DevPod](https://devpod.sh) to get reproducible, isolated environments that work the same way locally and in the cloud. Claude Code also publishes a [reference devcontainer](https://code.claude.com/docs/en/devcontainer) designed for autonomous operation with network allowlisting.

Other sandbox tools worth knowing about:

- [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) — microVM-based sandboxes built for coding agents, with host-side credential injection
- [Gondolin](https://earendil-works.github.io/gondolin/) — lightweight micro-VMs with programmable network egress and secret injection
- [Podman](https://podman.io) — rootless, daemonless Docker alternative

## Credential protection

Even inside a sandbox, the agent can access any credentials present in that environment. The Bkper CLI stores OAuth credentials (including a refresh token) at `~/.config/bkper/.bkper-credentials.json`. If the agent can read that file, it can make API calls as you.

### Host-side credential injection

The most secure option. Your credentials never enter the sandbox — an HTTP proxy on the host intercepts outbound API requests and injects authentication headers before forwarding them. [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/security/credentials/) and [Gondolin](https://earendil-works.github.io/gondolin/) implement this pattern.

This works well with the Bkper CLI because only `bkper auth login` starts an interactive browser login. Regular CLI commands can proceed without local credentials and let the proxy add authentication.

### Login inside the sandbox, then logout cleanly

This is the simplest practical workflow for many teams. Authenticate inside the sandbox with a secondary low-permission account:

```bash
bkper auth login
```

When you are done, revoke the refresh token and remove the local credentials:

```bash
bkper auth logout
```

`bkper auth logout` does both:

- revokes the stored refresh token remotely when possible
- clears local credentials from disk

If remote revocation fails, the CLI still clears local credentials and warns that remote cleanup may need manual follow-up.

## Permission scoping

The Bkper CLI authenticates as the user who ran `bkper auth login`. If that user is the book owner, the agent has owner-level access — it can delete accounts, change sharing settings, and modify lock dates.

**Use a secondary account with limited permissions instead.** Log into the CLI with a different Google account (for example, a personal Gmail), then share the target book with that account at the appropriate level:

| Permission | What the agent can do | Good for |
| --- | --- | --- |
| **View Only** | Read accounts, transactions, and balances | Read-only scripts, reporting, analysis |
| **Record Only** | Create and delete drafts | Automated data entry with human review |
| **Record & View** | Record drafts, post transactions, view data | Most development and testing workflows |
| **Editor** | Full data management (accounts, groups, transactions) | Building apps that manage book structure |

Avoid granting **Owner** permission to the agent's account. Owner access allows sharing changes, closing-date modifications, and other irreversible operations that should remain under direct human control.

For the full permissions matrix, see [Book Sharing — Permissions](http://bkper.com/docs/guides/using-bkper/book-sharing.md#permissions).

### Combining layers

A typical secure setup:

1. **Sandbox** — agent runs inside a DevContainer, Docker Sandbox, or micro-VM with only the project directory mounted
2. **Credentials** — either injected from the host, or authenticated inside the sandbox and explicitly revoked with `bkper auth logout` when work is done
3. **Permissions** — CLI authenticated as a secondary account with Record & View access

No single layer is bulletproof, but together they limit exposure to a narrow, time-bound, permission-scoped window.

---
source: /docs/build/getting-started/building-with-ai.md

# Building with AI

AI coding agents are the fastest way to go from idea to working Bkper integration. They can scaffold projects, write SDK code, debug issues, and iterate with you in real time — as long as they have the right context about the platform.

Use whichever agent harness you're most comfortable with. They all work well. But we have a recommended starting point.

> **Caution: Security**
> Coding agents run with your permissions. Before using them with real Bkper data, read [Agent Security](http://bkper.com/docs/build/getting-started/agent-security.md) to understand container isolation, credential protection, and permission scoping.
## Bkper Agent (recommended)

The Bkper CLI ships with a built-in coding agent. It's powered by [Pi Agent](https://pi.dev) with a custom system prompt that gives it deep knowledge of Bkper — core concepts, the SDK, CLI commands, and the accounting model. It's what the Bkper team uses every day, and the choice of Pi as the engine was deliberate.

#### Why Pi

We spent over a year working heavily with coding agents — Claude Code, Cursor, OpenCode, Codex — before settling on Pi as the foundation for Bkper Agent. The reasoning comes down to three things:

- **Hackability** — Pi can be extended while it's running. Custom extensions, hot-reload with `/reload`, shape the agent to fit your project. Bkper Agent is itself an example: a Pi instance that reshaped itself for financial data, with the accounting model, SDK types, and CLI commands baked into its context. That kind of deep customization isn't possible with closed agents.

- **Context management** — `/tree` gives you a branching view of your entire session. When a line of exploration turns into a dead end, you go back to a previous branch instead of paying for that detour in tokens and degraded intelligence. For Bkper projects — where you're working across books, accounts, event handlers, and SDK types — keeping context focused directly affects output quality.

- **Model freedom** — 15+ providers (Anthropic, OpenAI, Google, and more). Pick the model that fits the task instead of being locked into one vendor.

> **Note: We're still early**
> Nobody knows what the ideal AI coding workflow looks like yet. Pi's bet — give developers a minimal, hackable foundation instead of an opinionated black box — matches how we think about developer tools at Bkper. [Lucas Meijer's talk](https://youtu.be/fdbXNWkpPMY?t=796) captures this mindset well:
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; margin-bottom: 1.5rem;">
  <iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 4px solid lightgrey; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);" src="https://www.youtube.com/embed/fdbXNWkpPMY?start=796" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>

Make sure the [CLI is installed and authenticated](http://bkper.com/docs/build/getting-started/setup.md), then start the agent:

```bash
bkper agent
```

> **Tip: Windows users**
> Run the agent inside [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) for the best experience. Terminal agents rely on Linux tooling that works more reliably under WSL than native Windows. Clone your project into the WSL filesystem (e.g. `~/code/`) for best performance — Windows files are also accessible via `/mnt/c/`.
#### Connect a model provider

The agent needs access to a language model. On first launch, type `/login` and select a provider. We recommend **GitHub Copilot** — if you have a GitHub account with Copilot enabled, it gives you access to Claude, GPT, and Gemini models with no extra API keys.

Once logged in, you're in an interactive session. The agent can read your project files, run CLI commands, write code, and iterate with you — all with Bkper context already loaded.

#### Start asking questions

Just talk to it. Good starting prompts:

- `What are the main account types in Bkper?`
- `How do I query transactions using the CLI?`
- `What files are in this project?`
- `Help me create a script that lists all accounts in my book`

The agent reads your project's `AGENTS.md` and nearby files automatically, so it already has project-specific context. Since Bkper Agent wraps [Pi Agent](https://pi.dev), you can pass any Pi flag through — `--model`, `--continue`, `@file` references, and so on.

## Other coding agents

Any coding agent can build effectively with Bkper when given the right context. Here are the ones we've used and recommend:

| Agent | Type | Models | What it is |
| --- | --- | --- | --- |
| [Pi Agent](https://pi.dev) | Terminal | 15+ providers — Anthropic, OpenAI, Google, and more | Minimal, extensible harness — the engine behind Bkper Agent |
| [Claude Code](https://claude.com/product/claude-code) | Terminal, Desktop, IDE | Claude models | Anthropic's full-featured agent across all surfaces |
| [OpenCode](https://opencode.ai) | Terminal, Desktop, IDE | 75+ providers — free models included, works with Copilot and ChatGPT subscriptions | Open-source agent with the largest provider ecosystem |
| [Codex](https://github.com/openai/codex) | Terminal, Desktop, IDE | OpenAI — works with your ChatGPT plan | OpenAI's open-source coding agent |
| [Cursor](https://cursor.com) | Terminal, Desktop, IDE | Multiple providers built in | AI-native code editor with a terminal agent |

Each tool has its own way of loading project context. The next section explains how to provide Bkper knowledge to any of them.

## Giving your agent context

The Bkper Agent has context built in. For other agents, you need to provide it. Bkper makes this straightforward.

### Markdown access to all docs

Every page on bkper.com is available as clean Markdown — append `.md` to any URL:

```
https://bkper.com/docs/core-concepts.md
https://bkper.com/docs/api/bkper-js.md
https://bkper.com/docs/build.md
```

This strips navigation chrome and reduces token usage. See [AI Tooling](http://bkper.com/docs/ai-tooling.md) for all access methods.

### Published context URLs

Three URLs cover most Bkper development needs:

| URL | What it covers |
| --- | --- |
| [`/platform/agents.md`](https://bkper.com/platform/agents.md) | Technical instincts, quality standards, domain sensibilities |
| [`/docs/core-concepts.md`](https://bkper.com/docs/core-concepts.md) | The from-to model, account types, transactions, groups, queries |
| [`/docs/api/bkper-js.md`](https://bkper.com/docs/api/bkper-js.md) | Full bkper-js SDK reference with TypeScript types |

Use whichever combination your project needs. A script that only reads data might need core concepts and the SDK. A full app that handles events would benefit from all three.

### Project-level context files

For project-specific knowledge — which book you're working with, what accounts matter, what tags to use — add it to your agent's context file (`AGENTS.md`, `CLAUDE.md`, or equivalent):

```markdown
## Project context

- Book ID: abc123-def456
- Key accounts: Checking, Sales, Accounts Receivable
- Common tags: #invoice, #payment, #reconciled

## Rules

- All automated transactions must be created as drafts
- Use the #sync tag on all imported transactions
```

This gives the agent project-specific knowledge that no published doc can provide.

## Next steps

- [Your First App](http://bkper.com/docs/build/getting-started/first-app.md) — build and deploy a full Bkper app (a great task to pair with an AI agent)
- [CLI Scripting & Piping](http://bkper.com/docs/build/scripts/cli-pipelines.md) — automate data workflows with CLI pipes
- [Apps Overview](http://bkper.com/docs/build/apps/overview.md) — understand the Bkper Platform architecture

---
source: /docs/build/getting-started/first-app.md

# Your First App

This tutorial walks you through building and deploying a Bkper app from scratch. You'll use the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md) — managed hosting, pre-configured OAuth, and automatic dev tunnels — so you can focus entirely on business logic.

By the end, you'll have a working app live at `https://my-app.bkper.app` that displays account balances and reacts to transaction events.

## Prerequisites

Make sure you've completed [Development Setup](http://bkper.com/docs/build/getting-started/setup.md) — the CLI installed and authenticated.

## Walkthrough

1. **Create from the template**

    ```bash
    bkper app init my-app
    cd my-app
    ```

    This scaffolds a new project from the official template. The structure you get:

    ```
    my-app/
    ├── bkper.yaml                    ← Your entire infrastructure config
    ├── vite.config.ts                ← Client dev server & build config
    ├── env.d.ts                      ← Auto-generated type definitions
    ├── .dev.vars.example             ← Template for local secrets
    └── packages/
        ├── shared/                   ← Shared types and utilities
        ├── web/
        │   ├── client/               ← Frontend UI (Lit + Vite)
        │   └── server/               ← Backend API (Hono on Workers)
        └── events/                   ← Event handlers (webhooks)
    ```

2. **Look at `bkper.yaml`**

    Open `bkper.yaml` — this single file is your entire infrastructure configuration:

    ```yaml
    id: my-app
    name: My App
    description: A Bkper app that does something useful

    # Context menu entry in Bkper
    menuUrl: https://my-app.bkper.app?bookId=${book.id}
    menuUrlDev: http://localhost:8787?bookId=${book.id}

    # Event subscriptions
    webhookUrl: https://my-app.bkper.app/events
    events:
        - TRANSACTION_CHECKED

    # Deployment config
    deployment:
        web:
            main: packages/web/server/src/index.ts
            client: packages/web/client
        events:
            main: packages/events/src/index.ts
        services:
            - KV
        secrets:
            - BKPER_API_KEY
    ```

    This declares hosting, OAuth integration, event subscriptions, KV storage, and secrets — no cloud console required.

    See [App Configuration](http://bkper.com/docs/build/apps/configuration.md) for the full `bkper.yaml` reference.

3. **Start developing**

    ```bash
    npm run dev
    ```

    This runs two processes concurrently:
    - **Vite dev server** — hot module replacement for your UI
    - **Worker runtime** — Miniflare simulates Cloudflare Workers locally, with a Cloudflare tunnel so Bkper can send webhooks to your laptop

    You'll see output like:

    ```
    [vite]   Local: http://localhost:5173/
    [bkper]  Events: https://a1b2c3.trycloudflare.com/events (tunneled)
    ```

4. **See the running app**

    Open [http://localhost:5173](http://localhost:5173).

    You're greeted with a book picker — no login screen, no redirect, no auth setup. The platform handled OAuth automatically. Select a book to see account balances.

    The client code that makes this work is about 30 lines in `packages/web/client/src/components/my-app.ts`:

    ```ts
    private auth = new BkperAuth({
      onLoginSuccess: () => this.loadData(),
      onLoginRequired: () => this.auth.login(),
    });

    private async loadData() {
      this.bkper = new Bkper({
        oauthTokenProvider: async () => this.auth.getAccessToken(),
      });
      this.user = await this.bkper.getUser();
      this.books = await this.bkper.getBooks();
    }
    ```

    That's all the auth code you write. The platform provides the token endpoint, consent screen, and refresh handling.

5. **Trigger an event**

    Go to your Bkper book and check (reconcile) any transaction.

    Back in your terminal, you'll see the event handler fire. The template handler in `packages/events/src/handlers/transaction-checked.ts` creates a 20% draft transaction:

    ```ts
    export async function handleTransactionChecked(
        book: Book,
        event: bkper.Event
    ): Promise {
        if (!event.data) return { result: false };

        const operation = event.data.object as bkper.TransactionOperation;
        const tx = operation.transaction;

        if (!tx) return { result: false };

        // Prevent loops — skip transactions created by this app
        if (event.agent?.id === 'my-app') return { result: false };

        const newAmount = Number(tx.amount) * 0.2;

        const draft = new Transaction(book)
            .setDate(tx.date)
            .setAmount(newAmount)
            .setDescription(`20% of ${tx.description}`)
            .setCreditAccount(tx.creditAccount)
            .setDebitAccount(tx.debitAccount);

        await draft.create();
        return { result: `Created draft: 20% of ${tx.description}` };
    }
    ```

    The event arrived via the Cloudflare tunnel the CLI started — no manual ngrok setup, no URL configuration.

6. **Make a change**

    Edit the handler to use 10% instead of 20%:

    ```ts
    const newAmount = Number(tx.amount) * 0.1; // changed from 0.2
    ```

    Save the file. The Workers runtime reloads automatically. Check another transaction — you'll see the new 10% amount.

7. **Deploy to production**

    ```bash
    npm run build
    bkper app sync
    bkper app deploy
    ```

    This builds both client and workers, syncs metadata from `bkper.yaml`, then deploys code to the platform:

    ```
    ✓ Built web server
    ✓ Built events handler
    ✓ Deployed to https://my-app.bkper.app
    ```

    Your app is now live. Bkper will route production webhook events to `https://my-app.bkper.app/events`.

    The template also includes `npm run deploy` as a convenience shortcut for build + code deploy. When you change `bkper.yaml`, run `bkper app sync` as well.

## What just happened

You built and deployed a full Bkper app. Here's what you wrote versus what the platform handled:

| You wrote                                 | Platform handled                             |
| ----------------------------------------- | -------------------------------------------- |
| ~30 lines: book picker + account list UI  | OAuth client registration and consent screen |
| ~40 lines: event handler (business logic) | Token endpoint and refresh                   |
| `bkper.yaml`: infrastructure as config    | Global edge hosting (`my-app.bkper.app`)     |
|                                           | Webhook routing and delivery                 |
|                                           | Dev tunnel (webhooks on localhost)           |
|                                           | KV storage provisioning                      |
|                                           | SSL certificates                             |
|                                           | `env.d.ts` type generation                   |

The platform handles the infrastructure. You handle the logic.

## Next steps

- [App Architecture](http://bkper.com/docs/build/apps/architecture.md) — Understand the three-package pattern in depth
- [Development Experience](http://bkper.com/docs/build/apps/development.md) — Full reference for `bkper app dev`
- [Building & Deploying](http://bkper.com/docs/build/apps/deploying.md) — Deployment workflow, preview environments, secrets
- [Event Handlers](http://bkper.com/docs/build/apps/event-handlers.md) — All event types and how to handle them
- [App Configuration](http://bkper.com/docs/build/apps/configuration.md) — Full `bkper.yaml` reference

---
source: /docs/build/getting-started/quick-wins.md

# Quick Wins

You've [set up your environment](http://bkper.com/docs/build/getting-started/setup.md). Here are three ways to start building immediately — from a 1-line shell command to a 20-line script.

## CLI piping

Copy all accounts from one book to another in a single line:

```bash
bkper account list -b $SOURCE_BOOK --format json | bkper account create -b $DEST_BOOK
```

The CLI outputs JSON that feeds directly into the next command. No code, no setup beyond the CLI itself.

Add a property to every matching transaction:

```bash
bkper transaction list -b $BOOK -q "account:Expenses" --format json | \
  bkper transaction update -b $BOOK -p "reviewed=true"
```

See [CLI Scripting & Piping](http://bkper.com/docs/build/scripts/cli-pipelines.md) for more patterns.

## Node.js script

A short script that lists all accounts with their current balances:

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const book = await bkper.getBook('your-book-id');
const report = await book.getBalancesReport('');
const containers = report.getBalancesContainers();

for (const container of containers) {
    console.log(`${container.getName()}: ${container.getCumulativeBalance()}`);
}
```

Run it:

```bash
npm install bkper-js bkper
node script.mjs
```

See [Node.js Scripts](http://bkper.com/docs/build/scripts/node-scripts.md) for more examples.

## Direct API call

Call the REST API from any language. Here's a `curl` example:

```bash
# Get your OAuth token (after running bkper auth login)
TOKEN=$(bkper auth token)

# List your books
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.bkper.app/v5/books | jq '.items[].name'
```

See [Direct API Usage](http://bkper.com/docs/build/scripts/rest-api.md) for the full guide.

## What next?

These quick wins are just the beginning. Depending on what you want to build:

- **More automation** — [CLI Scripting & Piping](http://bkper.com/docs/build/scripts/cli-pipelines.md) for shell-based workflows, [Node.js Scripts](http://bkper.com/docs/build/scripts/node-scripts.md) for complex logic
- **A full app** — [Your First App](http://bkper.com/docs/build/getting-started/first-app.md) to build and deploy an app with UI and event handling on the [Bkper Platform](http://bkper.com/docs/build/apps/overview.md)
- **Google Workspace** — [Apps Script Development](http://bkper.com/docs/build/google-workspace/apps-script.md) for Sheets automation and triggers

---
source: /docs/build/getting-started/setup.md

# Development Setup

Everything you build on Bkper starts with the CLI. It handles authentication, provides the `bkper-js` library for programmatic access, and manages the full app lifecycle.

## Prerequisites

- [Node.js](https://nodejs.org/) >= 18

## Install and authenticate

1. **Install the CLI**

   ```bash
   npm i -g bkper
   ```

2. **Authenticate**

   ```bash
   bkper auth login
   ```

   This opens your browser for Google OAuth authentication. Once complete, the CLI stores your credentials locally. All API calls — from the CLI, from scripts, and from `bkper-js` — use this token. To clean up later, run `bkper auth logout`, which revokes the stored refresh token when possible and clears local credentials.

3. **Verify**

   ```bash
   bkper book list
   ```

   You should see a list of your Bkper Books. If you do, you're ready to build.

## What you now have

After setup, you have:

- **CLI commands** — Manage books, accounts, transactions, and apps from the terminal. Run `bkper --help` for the full command list.
- **Auth provider for scripts** — Use `getOAuthToken()` from the `bkper` package in any Node.js script:

  ```ts
  import { Bkper } from 'bkper-js';
  import { getOAuthToken } from 'bkper';

  Bkper.setConfig({
      oauthTokenProvider: async () => getOAuthToken(),
  });
  ```

- **App development tools** — Initialize, develop, and deploy platform apps with `bkper app` commands.

## Optional: API key

For dedicated API quota and project-level usage tracking, you can configure your own API key. This is optional — the default shared quota (60 requests per minute) works for most use cases.

See [Direct API Usage](http://bkper.com/docs/build/scripts/rest-api.md#custom-api-key) for setup instructions.

## Next steps

- [Quick Wins](http://bkper.com/docs/build/getting-started/quick-wins.md) — The fastest ways to create value with Bkper programmatically
- [Your First App](http://bkper.com/docs/build/getting-started/first-app.md) — Build and deploy a platform app
- [CLI reference](http://bkper.com/docs/build/tools/cli.md) — Overview of CLI capabilities

---
source: /docs/build/google-workspace/apps-script.md

# Apps Script Development

[Google Apps Script](https://developers.google.com/apps-script) is Google's serverless platform for extending Google Workspace. With the `bkper-gs` library, you can build Bkper automations that run inside Google's infrastructure — no servers, no deployment pipeline, and native access to Sheets, Drive, Calendar, and Gmail.

## When to use Apps Script

Use Apps Script when your automation lives in the Google Workspace ecosystem:

- Scheduled jobs that read from or write to Google Sheets
- Spreadsheet triggers (on-edit, on-form-submit) that record transactions
- Custom add-ons distributed to a team or domain
- Workflows that combine Bkper with other Google services (Drive, Calendar, Gmail)

If you need real-time event handling, a web UI, or automation that runs outside Google Workspace, use [Node.js scripts](http://bkper.com/docs/build/scripts/node-scripts.md) or a [platform app](http://bkper.com/docs/build/apps/overview.md) instead.

### Add the library

`bkper-gs` is published as an Apps Script library. To add it to your script:

1. Open your script in the [Apps Script editor](https://script.google.com)
2. Click **+** next to **Libraries** in the left-side panel
3. In the "Script ID" field, enter:
   ```
   1hMJszJGSUVZDB3vmsWrUZfRhY1UWbhS0SQ6Lzl06gm1zhBF3ioTM7mpJ
   ```
4. Click **Look up**, choose the latest version, and click **Add**

The `BkperApp` global is now available in your script.

### TypeScript definitions

For TypeScript development with autocomplete, install the type definitions:

```bash
npm i -S @bkper/bkper-gs-types
```

Configure `tsconfig.json`:

```json
{
  "compilerOptions": {
    "typeRoots": ["node_modules/@bkper", "node_modules/@types"]
  }
}
```

See [Develop Apps Script using TypeScript](https://developers.google.com/apps-script/guides/typescript) and use [clasp](https://github.com/google/clasp) to push TypeScript projects to Apps Script.

## The BkperApp entry point

`BkperApp` works the same way as `CalendarApp`, `DocumentApp`, and `SpreadsheetApp` — it's a global entry point that follows familiar Apps Script conventions.

The book ID comes from the URL when you open a book at [bkper.com](https://bkper.com):

```js
// Get a book by its ID (from the URL)
const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');
```

### Get a book

```js
function getBookName() {
  const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');
  Logger.log(book.getName());
}
```

### Record a transaction

```js
function recordTransaction() {
  const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');
  book.record('#gas 63.23');
}
```

Transactions use the same [shorthand syntax](http://bkper.com/docs/guides/using-bkper/record-transactions.md) you'd use in the Bkper UI.

### Batch record transactions

For bulk operations, pass an array. The library sends all records in a single API call — important for avoiding Apps Script execution time limits:

```js
function importExpenses() {
  const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');

  const transactions = [
    '#breakfast 15.40',
    '#lunch 27.45',
    '#dinner 35.86',
  ];

  book.record(transactions);
}
```

### Query transactions

The `getTransactions()` method returns a `TransactionIterator` for handling large datasets without loading everything into memory:

```js
function listTransactions() {
  const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');

  const iterator = book.getTransactions("account:'Bank' after:01/01/2024");

  while (iterator.hasNext()) {
    const transaction = iterator.next();
    Logger.log(transaction.getDescription());
  }
}
```

See [Querying Transactions](http://bkper.com/docs/guides/using-bkper/query-transactions.md) for the full query syntax.

### List accounts with balances

```js
function listAccountBalances() {
  const book = BkperApp.getBook('agtzfmJrcGVyLWhyZHIOCxIGTGVkZ2VyGNKJAgw');

  const accounts = book.getAccounts();
  for (const account of accounts) {
    if (account.isPermanent() && account.isActive()) {
      Logger.log(`${account.getName()}: ${account.getBalance()}`);
    }
  }
}
```

## Building triggers

Apps Script triggers let your automation run on a schedule or respond to spreadsheet events — without any always-on infrastructure.

### Time-based (scheduled)

```js
function setupDailySync() {
  ScriptApp.newTrigger('syncTransactions')
    .timeBased()
    .everyDays(1)
    .atHour(6)
    .create();
}

function syncTransactions() {
  const book = BkperApp.getBook('YOUR_BOOK_ID');
  const sheet = SpreadsheetApp.openById('YOUR_SHEET_ID').getActiveSheet();

  // Read rows from Sheets, record to Bkper
  const rows = sheet.getDataRange().getValues();
  const transactions = rows.slice(1).map(row => `${row[0]} ${row[1]} ${row[2]}`);
  book.record(transactions);
}
```

### Spreadsheet edit trigger

```js
function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== 'Expenses') return;

  const row = e.range.getRow();
  const amount = sheet.getRange(row, 3).getValue();
  const description = sheet.getRange(row, 2).getValue();

  if (amount && description) {
    const book = BkperApp.getBook('YOUR_BOOK_ID');
    book.record(`${description} ${amount}`);
  }
}
```

## TypeScript development workflow

For non-trivial scripts, use [clasp](https://github.com/google/clasp) for local development with TypeScript:

```bash
# Install clasp
npm install -g @google/clasp

# Log in
clasp login

# Clone an existing script
clasp clone <scriptId>

# Push changes
clasp push

# Watch for changes
clasp push --watch
```

With `@bkper/bkper-gs-types` configured, your editor provides full autocomplete for `BkperApp`, `Book`, `Transaction`, `Account`, and all other bkper-gs types.

## API reference

The complete `bkper-gs` reference is at [bkper.com/docs/bkper-gs](https://bkper.com/docs/bkper-gs/).

## Related

- [Building Sheets Integrations](http://bkper.com/docs/build/google-workspace/google-sheets.md) — Custom Sheets automations with bkper-gs
- [Node.js Scripts](http://bkper.com/docs/build/scripts/node-scripts.md) — When you need automation outside Google Workspace
- [Guides → Google Sheets Add-on](http://bkper.com/docs/guides/google-sheets.md) — End-user guide for recording and fetching data with the Bkper add-on

---
source: /docs/build/google-workspace/google-sheets.md

# Building Sheets Integrations

The [Bkper Add-on for Google Sheets](http://bkper.com/docs/guides/google-sheets.md) lets users record transactions and fetch data with built-in functions. This page covers the next level: building *custom* Sheets integrations with `bkper-gs` — automated pipelines, custom menus, scheduled reports, and two-way sync.

See [Apps Script Development](http://bkper.com/docs/build/google-workspace/apps-script.md) first to set up `bkper-gs` and understand the fundamentals.

## The boundary: add-on vs custom integrations

The built-in add-on covers the common cases well. Build a custom integration when:

- You need a **custom menu** tailored to your team's workflow
- You want **automated pipelines** that run on a schedule without user interaction
- You're building a **specialized report** that the standard functions don't cover
- You need **two-way sync** between a spreadsheet and Bkper (data flowing both directions)
- You're distributing a **custom add-on** to your domain or organization

## Custom menu functions

Add a Bkper-powered menu to any Google Sheet. Users can trigger operations directly from the spreadsheet without opening Bkper.

```js
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Bkper')
    .addItem('Import expenses from this sheet', 'importExpenses')
    .addItem('Fetch account balances', 'fetchBalances')
    .addSeparator()
    .addItem('Sync all', 'syncAll')
    .addToUi();
}

function importExpenses() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Expenses');
  const book = BkperApp.getBook(getBookId());

  const rows = sheet.getDataRange().getValues().slice(1); // skip header
  const transactions = rows
    .filter(row => row[0] && row[1] && row[2])     // date, description, amount
    .map(row => `${row[1]} ${row[2]} ${row[0]}`);  // "description amount date"

  book.record(transactions);
  SpreadsheetApp.getUi().alert(`Imported ${transactions.length} transactions.`);
}
```

### Sheets → Bkper (import)

Pull structured data from a spreadsheet and create transactions in bulk. Useful for importing bank exports, expense reports, or any data that lives in Sheets first.

```js
function importFromSheet() {
  const ss = SpreadsheetApp.openById('YOUR_SHEET_ID');
  const sheet = ss.getSheetByName('Transactions');
  const book = BkperApp.getBook('YOUR_BOOK_ID');

  const rows = sheet.getDataRange().getValues();
  const header = rows[0];
  const dateCol = header.indexOf('Date');
  const descCol = header.indexOf('Description');
  const amountCol = header.indexOf('Amount');
  const importedCol = header.indexOf('Imported');

  const toImport = [];

  for (let i = 1; i < rows.length; i++) {
    const row = rows[i];
    if (row[importedCol]) continue; // skip already imported

    const date = Utilities.formatDate(new Date(row[dateCol]), 'UTC', 'dd/MM/yyyy');
    toImport.push({
      row: i + 1,
      tx: `${row[descCol]} ${row[amountCol]} ${date}`,
    });
  }

  if (toImport.length === 0) return;

  book.record(toImport.map(item => item.tx));

  // Mark rows as imported
  for (const item of toImport) {
    sheet.getRange(item.row, importedCol + 1).setValue(true);
  }
}
```

### Bkper → Sheets (export/reporting)

Write Bkper data into a spreadsheet for dashboards, analysis, or sharing with stakeholders who work in Sheets.

```js
function exportBalancesToSheet() {
  const book = BkperApp.getBook('YOUR_BOOK_ID');
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('Balances');

  sheet.clearContents();
  sheet.appendRow(['Account', 'Balance']);

  const accounts = book.getAccounts();
  for (const account of accounts) {
    if (account.isPermanent() && account.isActive()) {
      sheet.appendRow([account.getName(), account.getBalance()]);
    }
  }
}
```

## Scheduled reporting

Use time-based triggers to run reports on a schedule — no user needs to be logged in.

```js
function setupWeeklyReport() {
  // Run every Monday at 8am
  ScriptApp.newTrigger('generateWeeklyReport')
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(8)
    .create();
}

function generateWeeklyReport() {
  const book = BkperApp.getBook('YOUR_BOOK_ID');
  const ss = SpreadsheetApp.openById('YOUR_REPORT_SHEET_ID');
  const sheet = ss.getSheetByName('Weekly') || ss.insertSheet('Weekly');

  const lastWeek = new Date();
  lastWeek.setDate(lastWeek.getDate() - 7);
  const from = Utilities.formatDate(lastWeek, 'UTC', 'MM/dd/yyyy');

  sheet.clearContents();
  sheet.appendRow(['Description', 'Amount', 'Date', 'Account']);

  const iterator = book.getTransactions(`after:${from}`);
  while (iterator.hasNext()) {
    const tx = iterator.next();
    sheet.appendRow([
      tx.getDescription(),
      tx.getAmount(),
      tx.getDateFormatted(),
      tx.getCreditAccount()?.getName(),
    ]);
  }
}
```

## Working with Custom Properties

Custom Properties let you attach metadata to Bkper entities (accounts, transactions). Use them as a sync key between Sheets and Bkper to avoid duplicates and enable updates.

```js
// Store a Sheets row ID on a transaction as a custom property
function recordWithSheetId(book, txString, sheetRowId) {
  const transaction = book.newTransaction()
    .setDate(new Date())
    .setAmount(100)
    .setDescription(txString)
    .setProperty('sheet_row_id', sheetRowId);

  transaction.create();
}

// Later, look up transactions by their sheet row ID
function findBySheetId(book, sheetRowId) {
  const iterator = book.getTransactions(`properties.sheet_row_id:${sheetRowId}`);
  return iterator.hasNext() ? iterator.next() : null;
}
```

This pattern enables idempotent sync: check if a transaction already exists before creating it, and update rather than duplicate.

## When to move beyond Sheets

Google Sheets is powerful, but it has limits. Consider a [platform app](http://bkper.com/docs/build/apps/overview.md) when:

- You need **real-time event handling** — platform apps get webhook events pushed instantly; Sheets triggers have latency and quota limits
- You need **a web UI** outside of Sheets — platform apps get `{appId}.bkper.app` with full auth
- Your automation needs to **run at scale** — Workers have no cold starts and higher execution limits than Apps Script
- You want to **publish to all Bkper users** — platform apps appear in the Bkper app listing; Sheets add-ons have a separate distribution model

## Related

- [Apps Script Development](http://bkper.com/docs/build/google-workspace/apps-script.md) — Setting up `bkper-gs`, BkperApp patterns, triggers
- [The Bkper Platform](http://bkper.com/docs/build/apps/overview.md) — When to build a full platform app
- [Guides → Google Sheets Add-on](http://bkper.com/docs/guides/google-sheets.md) — End-user guide for the built-in add-on

---
source: /docs/build/scripts/cli-pipelines.md

# CLI Scripting & Piping

The Bkper CLI is designed for scripting. Every command supports multiple output formats, and selected write commands accept piped JSON input — making it easy to build data pipelines, batch operations, and automated workflows.

## Output formats

All commands support three output formats via the `--format` global flag:

| Format | Flag                       | Best for                                |
| ------ | -------------------------- | --------------------------------------- |
| Table  | `--format table` (default) | Human reading in the terminal           |
| JSON   | `--format json`            | Programmatic access, single-item detail |
| CSV    | `--format csv`             | Spreadsheets, AI agents, data pipelines |

```bash
# Table output (default)
bkper account list -b abc123

# JSON output
bkper account list -b abc123 --format json

# CSV output -- raw data, no truncation, RFC 4180
bkper account list -b abc123 --format csv
```

**CSV output details:**

- RFC 4180 compliant — proper quoting, CRLF line endings, no truncation
- All metadata included — IDs, properties, hidden properties, URLs, and timestamps
- Raw values — dates in ISO format, numbers unformatted

> **Tip: AI agent guidance**
> When using the CLI from an AI agent or automated script, prefer `--format csv` for list commands, `--format json` for single-item commands (`get`, `create`, `update`), and stdin piping for batch operations.
## Query semantics quick reference

Use these rules in scripts to avoid ambiguous or empty results:

- `on:` supports year, month, and day (`on:2025`, `on:2025-01`, `on:2025-01-31`).
- `after:` is **inclusive** and `before:` is **exclusive**.
- A full-year range uses next-year boundary:
    - `after:2025-01-01 before:2026-01-01`

```bash
# Full year with on:
bkper transaction list -b $BOOK_ID -q "on:2025" --format csv

# Same full year with explicit boundaries
bkper transaction list -b $BOOK_ID -q "after:2025-01-01 before:2026-01-01" --format csv
```

## Batch operations

Write commands (`account create`, `transaction create`, `transaction update`) accept JSON piped via stdin. The input format follows the [Bkper API Types](https://raw.githubusercontent.com/bkper/bkper-api-types/refs/heads/master/index.d.ts) — a single JSON object or an array of objects.

Groups are created explicitly with `bkper group create --name` and optional `--parent`, so hierarchy stays deterministic.

### Creating in batch

```bash
# Create transactions from JSON
echo '[{
  "date": "2025-01-15",
  "amount": "100.50",
  "creditAccount": {"name": "Bank Account"},
  "debitAccount": {"name": "Office Supplies"},
  "description": "Printer paper",
  "properties": {"invoice": "INV-001"}
}]' | bkper transaction create -b abc123

# Create accounts
echo '[{"name":"Cash","type":"ASSET"},{"name":"Revenue","type":"INCOMING"}]' | \
  bkper account create -b abc123

# Create a group explicitly
bkper group create -b abc123 --name "Fixed Costs" --hidden

# Pipe from any script that outputs JSON
python export_bank.py | bkper transaction create -b abc123
```

Batch results are output as a flat JSON array:

```bash
bkper account create -b abc123 < accounts.json
# [{"id":"acc-abc","name":"Cash",...}, {"id":"acc-def","name":"Revenue",...}]
```

### Adding properties via CLI flag

The `--property` flag can add or override properties from the stdin payload:

```bash
echo '[{"name":"Cash","type":"ASSET"}]' | \
  bkper account create -b abc123 -p "region=LATAM"
```

## Piping between commands

All JSON output is designed to feed directly into other commands. This is the most powerful pattern — combining commands into pipelines:

### Copy data between books

```bash
# Copy all accounts from one book to another
bkper account list -b $BOOK_A --format json | bkper account create -b $BOOK_B

# Copy transactions matching a query
bkper transaction list -b $BOOK_A -q "after:2025-01-01" --format json | \
  bkper transaction create -b $BOOK_B
```

Recreate groups explicitly with `bkper group create --name ... --parent ...` before copying accounts that reference them.

### Clone a full chart of accounts

```bash
# Recreate the group hierarchy explicitly
bkper group create -b $DEST --name "Assets"
bkper group create -b $DEST --name "Current Assets" --parent "Assets"

# Then copy accounts and transactions
bkper account list -b $SOURCE --format json | bkper account create -b $DEST
bkper transaction list -b $SOURCE -q "after:2025-01-01" --format json | \
  bkper transaction create -b $DEST
```

### Batch updates with jq

Use [jq](https://jqlang.github.io/jq/) to transform data between commands:

```bash
# List transactions, modify descriptions, pipe back to update
bkper transaction list -b $BOOK -q "after:2025-01-01" --format json | \
  jq '[.[] | .description = "Updated: " + .description]' | \
  bkper transaction update -b $BOOK

# Add a property to all matching transactions
bkper transaction list -b $BOOK -q "account:Expenses" --format json | \
  bkper transaction update -b $BOOK -p "reviewed=true"

# Batch update checked transactions
bkper transaction list -b $BOOK -q "is:checked after:2025-01-01" --format json | \
  bkper transaction update -b $BOOK --update-checked -p "migrated=true"
```

### Daily export

```bash
#!/bin/bash
# Export yesterday's transactions to CSV
DATE=$(date -d "yesterday" +%Y-%m-%d)
bkper transaction list -b $BOOK_ID \
  -q "on:$DATE" \
  --format csv > "export-$DATE.csv"
```

### Bulk categorization

```bash
#!/bin/bash
# Add a property to all uncategorized transactions
bkper transaction list -b $BOOK_ID \
  -q "account:Uncategorized" \
  --format json | \
  bkper transaction update -b $BOOK_ID -p "needs_review=true"
```

## Combining with other tools

The CLI works with standard Unix tools:

```bash
# Count transactions matching a query
bkper transaction list -b $BOOK_ID -q "after:2025-01-01" --format json | jq 'length'

# Extract specific fields with jq
bkper account list -b $BOOK_ID --format json | \
  jq '[.[] | {name, type}]'

# Sort by amount
bkper transaction list -b $BOOK_ID -q "after:1900-01-01" --format json | \
  jq 'sort_by(.amount | tonumber) | reverse'
```

## Full CLI reference

For the complete command reference including all options, see the [bkper-cli app page](http://bkper.com/apps/bkper-cli.md) or run `bkper --help`.

---
source: /docs/build/scripts/node-scripts.md

# Node.js Scripts

For tasks that go beyond CLI piping — complex logic, external API integration, scheduled jobs — write a Node.js script with `bkper-js`.

## Setup

```bash
# Create a script project
mkdir my-bkper-script && cd my-bkper-script
npm init -y
npm install bkper-js bkper
```

Authenticate once via the CLI:

```bash
bkper auth login
```

## The pattern

Every script follows the same structure: authenticate, get a book, do work.

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const book = await bkper.getBook('your-book-id');

// Your logic here
```

### Export balances to JSON

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';
import { writeFileSync } from 'fs';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const book = await bkper.getBook('your-book-id');

const report = await book.getBalancesReport('on:2025-12-31');
const rows = report.getBalancesContainers().map(container => ({
    name: container.getName(),
    balance: container.getCumulativeBalance().toString(),
}));

writeFileSync('balances.json', JSON.stringify(rows, null, 2));
console.log(`Exported ${rows.length} balances`);
```

### Bulk-create accounts from a JSON export

```ts
import { Account, Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';
import { readFileSync } from 'fs';

type AccountInput = {
    name: string;
    type: 'ASSET' | 'LIABILITY' | 'INCOMING' | 'OUTGOING';
    groups?: Array<{ id?: string; name?: string }>;
};

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const book = await bkper.getBook('your-book-id');

const data: AccountInput[] = JSON.parse(readFileSync('accounts.json', 'utf-8'));

const accounts = data.map(acc => {
    const account = new Account(book).setName(acc.name).setType(acc.type);
    if (acc.groups?.length) {
        account.setGroups(acc.groups);
    }
    return account;
});

const created = await book.batchCreateAccounts(accounts);
console.log(`Created ${created.length} accounts`);
```

### Query transactions and generate a report

```ts
import { Amount, Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const book = await bkper.getBook('your-book-id');

const result = await book.listTransactions('account:Expenses after:2025-01-01');

let total = new Amount(0);
for (const tx of result.getItems()) {
    const amount = tx.getAmount();
    if (!amount) continue;

    total = total.plus(amount);
    console.log(`${tx.getDate()} | ${tx.getDescription()} | ${amount.toString()}`);
}
console.log(`\nTotal: ${total.toString()}`);
```

### With cron

```bash
# Run daily at 8am
0 8 * * * cd /path/to/script && node export.mjs
```

### In CI environments

The CLI helper `getOAuthToken()` is designed for local developer machines, where the CLI can store and refresh credentials. In GitHub Actions or other unattended environments, provide your own token source:

```ts
import { Bkper } from 'bkper-js';

const bkper = new Bkper({
    oauthTokenProvider: async () => {
        const token = process.env.BKPER_OAUTH_TOKEN;
        if (!token) {
            throw new Error('BKPER_OAUTH_TOKEN is not set');
        }
        return token;
    },
});
```

Use this pattern only when another system is already issuing and rotating the token for the job. For unattended long-running automation, implement your own OAuth flow instead of relying on the CLI's locally stored credentials.

## Error handling

Wrap API calls with proper error handling:

```ts
import { BkperError } from 'bkper-js';

try {
    const book = await bkper.getBook('your-book-id');
    // ...
} catch (error) {
    if (error instanceof BkperError && error.code === 404) {
        console.error('Book not found');
    } else if (error instanceof BkperError && error.code === 403) {
        console.error('No access to this book');
    } else if (error instanceof Error) {
        console.error('API error:', error.message);
    } else {
        console.error('Unknown error:', String(error));
    }
    process.exit(1);
}
```

## When to use scripts vs apps

| Scenario                                       | Use                                       |
| ---------------------------------------------- | ----------------------------------------- |
| One-off data migration                         | Script                                    |
| Scheduled export/report                        | Script                                    |
| Reacting to book events in real time           | [Platform app](http://bkper.com/docs/build/apps/overview.md) |
| Custom UI for users                            | [Platform app](http://bkper.com/docs/build/apps/overview.md) |
| Complex multi-step workflow with external APIs | Script or app, depending on trigger       |

## Next steps

- [bkper-js API Reference](http://bkper.com/docs/api/bkper-js.md) — Full SDK documentation
- [CLI Scripting & Piping](http://bkper.com/docs/build/scripts/cli-pipelines.md) — For simpler tasks, the CLI may be enough
- [Direct API Usage](http://bkper.com/docs/build/scripts/rest-api.md) — Use the REST API from any language

---
source: /docs/build/scripts/rest-api.md

# Direct API Usage

The Bkper REST API is the universal interface for interacting with Bkper Books. Every library and tool — [bkper-js](http://bkper.com/docs/build/tools/libraries.md#bkper-js), [bkper-gs](http://bkper.com/docs/build/tools/libraries.md#bkper-gs), the [CLI](http://bkper.com/docs/build/tools/cli.md) — is built on top of it. You can use it directly from any language or platform.

## Base URL

```
https://api.bkper.app
```

All API calls use this endpoint:

- `https://api.bkper.app/v5/books` — List books
- `https://api.bkper.app/v5/books/{bookId}` — Get a specific book

## Specifications

The API is built on [OpenAPI](https://swagger.io/resources/open-api/) and [Google API Discovery](https://developers.google.com/discovery) specifications:

- [OpenAPI specification](https://bkper.com/docs/api/rest/openapi.json)
- [Google API Discovery document](https://bkper.com/_ah/api/discovery/v1/apis/bkper/v5/rest)

You can use these specifications to generate client libraries with tools like [OpenAPI generator](https://openapi-generator.tech/) or [Google APIs code generator](https://github.com/google/apis-client-generator) in the language of your choice.

For **TypeScript**, we maintain an updated type definitions package:

- [`@bkper/bkper-api-types`](https://www.npmjs.com/package/@bkper/bkper-api-types)

## Getting started

To use the API, you need a valid OAuth2 access token. No API key is required — the Bkper API proxy provides a managed key with shared quota.

### CLI / Node.js

Use [bkper-js](https://www.npmjs.com/package/bkper-js) with the [CLI](http://bkper.com/docs/build/tools/cli.md) for authentication:

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const books = await bkper.getBooks();
```

First authenticate via CLI: `bkper auth login`

### Web applications

Use [bkper-js](https://www.npmjs.com/package/bkper-js) with [@bkper/web-auth](https://www.npmjs.com/package/@bkper/web-auth) for browser authentication:

```ts
import { Bkper } from 'bkper-js';
import { BkperAuth } from '@bkper/web-auth';

const auth = new BkperAuth({ onLoginSuccess: () => initApp() });
await auth.init();

Bkper.setConfig({
    oauthTokenProvider: async () => auth.getAccessToken(),
});

const bkper = new Bkper();
const books = await bkper.getBooks();
```

### Google Apps Script

Use [bkper-gs](https://github.com/bkper/bkper-gs):

```js
function listBooks() {
    var books = BkperApp.getBooks();
    books.forEach(function (book) {
        Logger.log(book.getName());
    });
}
```

### Direct HTTP calls

For any language, send a Bearer token in the Authorization header:

```
Authorization: Bearer YOUR_ACCESS_TOKEN
```

For details on obtaining tokens, see [Authentication](http://bkper.com/docs/build/concepts/authentication.md).

## API Explorer

The REST API Explorer lets you browse endpoints, inspect payload formats, and try the API live:

[Open API Explorer](https://apis-explorer.appspot.com/apis-explorer/?base=https://bkper-hrd.appspot.com/_ah/api#p/bkper/v5/)

![API Explorer](http://bkper.com/docs/_astro/bkper-rest-api-explorer.CSBhktEB.png)

---

## Custom API key

For dedicated quota and project-level usage tracking, you can optionally configure your own API key:

1. Join [bkper@googlegroups.com](https://groups.google.com/g/bkper) to unlock access to enable the API on your project
2. [Create a new GCP project](https://console.cloud.google.com/projectcreate), or select an existing one
3. [Enable the Bkper API](https://console.cloud.google.com/apis/library/app.bkper.com) in the Google Cloud Console
4. [Create an API key](https://console.cloud.google.com/apis/credentials/key)
   - [Add API Restrictions](https://cloud.google.com/docs/authentication/api-keys#adding_api_restrictions) to `app.bkper.com` API only

Send your API key in the `bkper-api-key` HTTP header:

```
bkper-api-key: YOUR_API_KEY
```

> **Note**
> API keys are for project identification and quota management only, not for authentication. Do not store API keys in your code. See [securing an API key](https://cloud.google.com/docs/authentication/api-keys#securing_an_api_key) best practices.
> **Tip**
> For Google Apps Script, you can store the API key in the [Script Properties](https://developers.google.com/apps-script/reference/properties/properties-service#getScriptProperties()). To store it, open the online editor, *File > Project properties > Script properties*.
### Metrics

With your own API key, you can view detailed [metrics on the GCP Console](https://console.cloud.google.com/apis/api/app.bkper.com/metrics) for your project's API calls:

![REST API Metrics](http://bkper.com/docs/_astro/bkper-rest-api-metrics.DHXQ7CRF.png)

The [metrics dashboard](https://console.cloud.google.com/apis/api/app.bkper.com/metrics) provides information about endpoint calls, latency, and errors — a good overview of your integration's health.

### Quota

The [quotas dashboard](https://console.cloud.google.com/apis/api/app.bkper.com/quotas) provides details of the current default and quota exceeded errors.

The default shared quota is **60 requests per minute**. If you need higher limits with your own API key, please get in touch so we can discuss your case.

---
source: /docs/build/tools/cli.md

# CLI

The Bkper CLI is the command-line interface for everything you build on Bkper. It serves two roles:

- **Data management** — Work with books, accounts, transactions, and balances from the terminal
- **App development** — Initialize, develop, build, and deploy Bkper apps

## Installation

```bash
npm i -g bkper
```

## Authentication

```bash
bkper auth login   # authenticate via Google OAuth
bkper auth logout  # revoke the stored refresh token and clear local credentials
bkper auth token   # print the current access token (requires prior login)
```

`bkper auth login` authenticates via Google OAuth and stores credentials locally. The same credentials are used by:
- All CLI commands
- The `getOAuthToken()` function in scripts
- The `bkper app dev` local development server

`bkper auth token` is useful for direct API calls — pipe the output into a variable:

```bash
TOKEN=$(bkper auth token)
```

### App lifecycle

```bash
# Create a new app from the template
bkper app init my-app

# Start worker runtime (Miniflare + tunnel + file watching)
bkper app dev

# Build worker bundles
bkper app build

# Sync app metadata to Bkper
bkper app sync

# Deploy to the Bkper Platform
bkper app deploy

# Remove app from the Bkper Platform
bkper app undeploy

# Check deployment status
bkper app status
```

> **Note:** The project template composes the full workflow via `npm run dev` (runs Vite + `bkper app dev` concurrently) and `npm run build` (runs `vite build` + `bkper app build`). Use the template scripts for the complete development experience.

### Secrets management

```bash
# Set a secret for production
bkper app secrets put BKPER_API_KEY

# Set a secret for preview environment
bkper app secrets put BKPER_API_KEY --preview

# List secrets
bkper app secrets list

# Delete a secret
bkper app secrets delete BKPER_API_KEY
```

### App installation

```bash
# Install app on a book
bkper app install <appId> -b <bookId>

# Uninstall app from a book
bkper app uninstall <appId> -b <bookId>
```

### Auth provider for scripts

The CLI package exports `getOAuthToken()` for use in Node.js scripts:

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});
```

## Data management commands

The CLI provides full data management capabilities:

```bash
# Books
bkper book list
bkper book get <bookId>
bkper book create --name "My Company"

# Accounts
bkper account list -b <bookId>
bkper account create -b <bookId> --name "Sales" --type INCOMING

# Transactions
bkper transaction list -b <bookId> -q "account:Sales after:2025-01-01"
bkper transaction create -b <bookId> --description "Office supplies 123.78"

# Balances
bkper balance list -b <bookId> -q "on:2025-12-31"
```

All data commands use `-b, --book <bookId>` to specify the book context.

## Query semantics (transactions and balances)

Use the same query language across Bkper web app, CLI, and Google Sheets integrations.

- `on:` supports different granularities:
  - `on:2025` → full year
  - `on:2025-01` → full month
  - `on:2025-01-31` → specific day
- `after:` is **inclusive** and `before:` is **exclusive**.
  - Full year 2025: `after:2025-01-01 before:2026-01-01`
- For point-in-time statements (typically permanent accounts: `ASSET`, `LIABILITY`), prefer `on:` or `before:`.
- For activity statements over a period (typically non-permanent accounts: `INCOMING`, `OUTGOING`), prefer `after:` + `before:`.
- For statement-level analysis, prefer report root groups (for example `group:'Balance Sheet'` or `group:'Profit & Loss'`) over isolated child groups.

```bash
# Transactions in full year 2025
bkper transaction list -b <bookId> -q "on:2025"

# Transactions in January 2025
bkper transaction list -b <bookId> -q "on:2025-01"

# Balance Sheet snapshot (point-in-time)
bkper balance list -b <bookId> -q "group:'Balance Sheet' before:2026-01-01"

# P&L activity over 2025
bkper balance list -b <bookId> -q "group:'Profit & Loss' after:2025-01-01 before:2026-01-01"
```

## Output formats

The CLI supports multiple output formats for scripting and piping:

```bash
# Table (default, human-readable)
bkper book list

# JSON (for programmatic use)
bkper book list --format json

# CSV (for spreadsheets and data tools)
bkper transaction list -b <bookId> --format csv
```

See [CLI Scripting & Piping](http://bkper.com/docs/build/scripts/cli-pipelines.md) for scripting patterns.

## Full reference

Run `bkper --help` or `bkper <command> --help` for built-in documentation on any command.

The complete CLI documentation, including all commands and options, is available on the [bkper-cli app page](http://bkper.com/apps/bkper-cli.md).

---
source: /docs/build/tools/libraries.md

# Libraries & SDKs

Choose the right library for your environment. All libraries are built on the [REST API](http://bkper.com/docs/build/scripts/rest-api.md) and are used by the Bkper team to build our own products.

## bkper-js

**JavaScript/TypeScript SDK for Node.js and browsers.**

The primary client library for programmatic access to Bkper. Use it for [scripts](http://bkper.com/docs/build/scripts/node-scripts.md), [platform apps](http://bkper.com/docs/build/apps/overview.md), and web applications.

```ts
import { Bkper } from 'bkper-js';
import { getOAuthToken } from 'bkper';

Bkper.setConfig({
    oauthTokenProvider: async () => getOAuthToken(),
});

const bkper = new Bkper();
const books = await bkper.getBooks();
```

- [npm package](https://www.npmjs.com/package/bkper-js)
- [API Reference](http://bkper.com/docs/api/bkper-js.md)

## bkper-gs

**Google Apps Script library.**

Access Bkper from Apps Script — Google Sheets automations, triggers, add-ons. Authentication is handled by the Apps Script runtime.

```js
function listBooks() {
    var books = BkperApp.getBooks();
    books.forEach(function (book) {
        Logger.log(book.getName());
    });
}
```

- [GitHub](https://github.com/bkper/bkper-gs)
- [API Reference](http://bkper.com/docs/api/bkper-gs.md)

## @bkper/web-auth

**Web authentication SDK for the Bkper Platform.**

Browser-based OAuth for apps hosted on `*.bkper.app` subdomains. Use with bkper-js when building platform apps.

```ts
import { BkperAuth } from '@bkper/web-auth';

const auth = new BkperAuth({ onLoginSuccess: () => initApp() });
await auth.init();

// Use with bkper-js
const token = await auth.getAccessToken();
```

- [npm package](https://www.npmjs.com/package/@bkper/web-auth)
- [API Reference](http://bkper.com/docs/api/bkper-web-auth.md)

## @bkper/web-design

**CSS design tokens for Bkper web applications.**

Provides typography, spacing, border, and color tokens as CSS custom properties. Includes light/dark theme support and account-type color families. Works standalone or integrates with [Web Awesome](https://www.webawesome.com/) — if Web Awesome is loaded, Bkper tokens automatically inherit its design system values.

```css
@import '@bkper/web-design';
```

Then use the tokens in your styles:

```css
.my-component {
    font-family: var(--bkper-font-family);
    padding: var(--bkper-spacing-medium);
    color: var(--bkper-color-text);
    border: var(--bkper-border);
}
```

- [npm package](https://www.npmjs.com/package/@bkper/web-design)
- [Token Reference](http://bkper.com/docs/api/bkper-web-design.md)

## @bkper/bkper-api-types

**TypeScript type definitions for the REST API.**

Add autocomplete and contextual documentation to any TypeScript project that works with Bkper API payloads.

```bash
npm install @bkper/bkper-api-types
```

Configure `tsconfig.json` to make the `bkper` namespace globally available:

```json
{
    "compilerOptions": {
        "types": ["@bkper/bkper-api-types"]
    }
}
```

Then use the `bkper` namespace directly — no import needed:

```ts
const event: bkper.Event = await c.req.json();
if (!event.book) {
    throw new Error('Missing book in event payload');
}
const book: bkper.Book = event.book;
```

- [npm package](https://www.npmjs.com/package/@bkper/bkper-api-types)
- [API Reference](http://bkper.com/docs/api/bkper-api-types.md)

## Which library to use

| Scenario                                       | Library                                                            |
| ---------------------------------------------- | ------------------------------------------------------------------ |
| Node.js scripts and automations                | bkper-js + CLI                                                     |
| Browser (any domain, with access token)        | [bkper-js via CDN](https://github.com/bkper/bkper-js#cdn--browser) |
| Platform apps (server-side)                    | bkper-js                                                           |
| Platform apps (client-side, \*.bkper.app only) | bkper-js + @bkper/web-auth                                         |
| Platform apps (styling)                        | @bkper/web-design                                                  |
| Google Apps Script                             | bkper-gs                                                           |
| Google Sheets add-ons                          | bkper-gs                                                           |
| Any language (direct HTTP)                     | [REST API](http://bkper.com/docs/build/scripts/rest-api.md) + @bkper/bkper-api-types  |
