data entry. tax calculations. currency conversions. financial reports. any bookkeeping workflow.

Automate

Customize with Apps and Bots to help you on the hard work.

Overview

This Documentation includes guides, tutorials and references for you to build your own solutions in the shape of Apps and/or Bots to customize and extent your business workflows on Bkper.

Bots are triggered by events in your books giving you the power to greatly reduce the labour of repetive low value and fault sensitive tasks whereas Apps empower collaboration from simple enhancements to sophisticated business understanding.

Under constant evolution, you can keep up with new releases and deprecations on our changelog and check for the platform status at anytime.

Apps apps

Apps can be as simple as opening a url in the book’s context or as complex as a complete Add-on with business logics.

They are interactive solutions that can run independently and integrate to third party solutions.

Listing

All Bkper Apps are listed on the Automations Portal accessed at app.bkper.com > Automations > Apps menu.

Each App has its own page, logo, site and additional details, like bellow:

Bkper Apps Listing

The App listing and settings are managed using the Bkper bkper-node utility.

Context Menu

Apps can add context menus items on the Transactions page More menu in your Books. This allows for you, to open dynamically build urls with reference to expressions of a Book’s context.

Once you install an App with a menu configurations, a new menu item will appear in your Book:

Bkper App Menu Item

The popup that opens carries the particular context of that book at that moment:

Bkper App Menu Popup

On selecting the new option from the more menu the url can be composed dynamically, by replacing expressions ${xxxx} with contextual information of the Book. Example:

menuUrl: https://my.link.com/bookId=${book.id}&query=${query}

Once user click the More menu button, the menu url will become:

https://my.link.com/bookId=xxx&query=yyy

Where xxx is the current Book id and yyy is the current query being executed.

For reference of the accepted expressions see bkperapp.json.

Agents

Each App can define an Agent for acting on behalf of the user, and its activities and transactions are identified by its logo and name on Bkper:

Bkper App Agents

The App agent is identified by the API Key when calling the API.

Creating a new App

Apps are created and updated by the rest API, using the bkper-node utility and library package.

See details bellow.

Bots adb

Bots are a specialized kind of App that react to events from your Books.

Bkper Bots

A Bkper Bot can, for example, calculate taxes on transaction posting, convert currencies on transaction checked or post a message to Slack on comment created.

Bot agents

Bots run on behalf of the user who installed it, and the agents are identified in the transactions and activities list:

Bkper Bot Agents

Bot responses

The bot responses are registered in the activity which started the bot call and can be viewed and replayed by clicking on the response at the bottom of the activity:

Bkper Bot Responses

A Bkper Bot is authenticated via OAuth2, requiring a standard Cloud Platform project

Apps Script Bot

A Bkper Bot running on Google Apps Script infrastructure, makes authenticated calls to remote Google Apps Script functions upon events on the Bkper Books, on behalf of the user who installed the bot.

Apps Script Bots has lower level of development complexity and the execution environment is subjected to constrained Apps Script Quotas. The quota counts against the end user who installed the bot.

Its recommended for scenarios where the Bot event handling thoughput is under 1 event/second/user and the logic can be handled syncronously.

The Apps Script Bots can be composed of context menus, built with Apps Script HTML Service, to provide additional user interaction features.

Good cases are customized import/export bots, such as the Bkper CSV App

Apps Script Bot setup

Apps Script Bot deployment

Once the bot is developed, it should be released to users in a stable version, and make sure this version does not break when working on the code after the bot is already running for users.

To do so, Google Apps Script platform provides a mechanism called Deployments.

Bot installations use the Deployment ID especified in the bkperapp.json.

You can find the Deployment ID in the Apps Script console:

Apps Script Deploy from Manifest

Apps Script Deployment ID

Copy and paste the ID into deploymentId property in the bkperapp.json to specify which Deployment to use.

A same Deployment can run different versions of the script, so:

  • Non breakable changes to code should be released in the same Deployment, rolling out to all user who already installed the Bot.

  • Breakable changes to code should be released in another Deployment to make sure not to break the execution for users who already installed the Bot.

Cloud Functions Bot

A Bkper Bot running on Google Cloud Functions infrastructure, makes authenticated calls to the deployed function, as the bkper-hrd@appspot.gserviceaccount.com service account. In order to authenticate the service, you need to give the service account bkper-hrd@appspot.gserviceaccount.com the Cloud Functions Invoker IAM role (roles/cloudfunctions.invoker)

The production Cloud Function trigger is set at the webhookUrl property and the development endpoint at the webhookUrlDev, in the bkperapp.json file.

For the end user, in the webhookUrl production endpoint, we send the OAuth Access Token of the user who installed the bot, and the Bot API Key, in the bkper-oauth-token and bkper-api-key http headers, respectivelly, on each event, so the Bot can use both tokens to call the API back on behalf of the user. The webhookUrlDev development endpoint does NOT receive those tokens, so the developer needs to perform the login locally and send the access_token accordingly. This can be simplified by the bkper-node utility library.

The function response payload must be in the format:

{result?: any, error?: any}

The result object is recorded on each book activity that generated thee event as the bot response.

If the function returns a {result: false}, the bot response is suppresed and not recorded at the activity.

Any error returned, like {error: "This is an error"}, will show up as a Bot Response error, explained in the Development session.

To show up the full error stack trace, you can do like bellow:

  try {
    ...
  } catch (err) {
    res.send(response({error: err.stack ? err.stack.split("\n") : err}))
  }

That way the stacktrace will be pritty printed in the Bot Response.

The events throughput to the bot can sometimes be high, specially when processing large batches. You should set the max instance limit - usually 1-2 is enough - and the function will return a response code of 429 Too Many Requests, then the bot call will be automatically retried, with an incremental backoff logic, until receives an http 200.

Cloud Function Bots has a higher level of development complexity and the execution environment is subjected to Cloud Function Quotas. The quota counts agains the developer account, not the end user.

Its recommended for scenarios where the Bot event handling thoughput is even higher than 1 event/second/user and the logic can be handled asyncronously.

The Cloud Function Bots can be composed of context menus, built with Apps Script HTML Service, or any other infrastructure, to provide additional user interaction features.

Good cases are high throughput event handlers, like:

  • The Tax Bot that provide tax calculations for transactions, supporting large batch imports.
  • The Exchange Bot that handle big loads in paralell for calculating exchanges in multiple books, also providing a context menu UI for updating gains/losses due exchange variation.
  • The Stock Bot that handle large amout of stock purchase/selling orders, also providing a context menu UI for calculating Realized Results using FIFO method.

Generic Webhook Bot

Cloud Function Bots are actually a recommended and proven way of handling book events in a high scallable and secure infrastructure, although you can build and host your bot in any other cloud infrastrucure, as well as on-premise.

To do so, you can use the same webhookUrl property and the development endpoint at the webhookUrlDev, in the bkperapp.json file.

The calls to the production webhook url is signed with a JWT token using Service to Function method. You can use this token to assert the identity of the Bkper service to ensure hight level of security. This is automatically done by the Cloud Functions infrastructure, so, we strongly recommend it.

The events throughput to the bot can sometimes be high, specially when processing large batches. If the bot infrastructure gets overloaded, you can return an http status 429 and the bot call will be automatically retried, with an incremental backoff logic, until receives an http 200.

Google Cloud Platform setup

Bot development

The Bot runs in Development Mode when executed by the developer or owner of the App.

Any succesfull result or error will be shown as the Bot response:

Bkper Bots Errors

You can click the bot response to replay failed executions.

If you return an html snippet, with a link for example, it will be rendered in the response popup.

If you want to avoid recording the reponse, simply return false from your trigger function.

The functions are bound to events by it names in camel-case, starting with “on”. Example:

For the TRANSACTION_POSTED event, the function called will be onTransactionPosted(event).

Check out Import/Export CSV example to learn more.

The Event object

All bot triggers function, such as onTransactionChecked(event), receive an Event object in the following format:

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

        /** The Book object associated with the Event */
        book?: {
            /**
             * The id of agent that created the resource
             */
            agentId?: string;
            
            collection?: Collection;
            /**
             * The creation timestamp, in milliseconds
             */
            createdAt?: string;
            /**
             * The date pattern of the Book. Example: dd/MM/yyyy
             */
            datePattern?: string;
            /**
             * The decimal separator of the Book
             */
            decimalSeparator?: "DOT" | "COMMA";
            /**
             * The number of fraction digits (decimal places) of the Book
             */
            fractionDigits?: number; // int32
            /**
             * The unique id that identifies the Book in the system. Found at bookId url param
             */
            id?: string;
            /**
             * The last update date of the Book, in in milliseconds
             */
            lastUpdateMs?: string;
            /**
             * The book lock date
             */
            lockDate?: string;
            /**
             * The name of the Book
             */
            name?: string;
            /**
             * The Book owner username
             */
            ownerName?: string;
            /**
             * The transactions pagination page size
             */
            pageSize?: number; // int32
            /**
             * The period slice for balances visualization
             */
            period?: "MONTH" | "QUARTER" | "YEAR";
            /**
             * The start month when YEAR period set
             */
            periodStartMonth?: "JANUARY" | "FEBRUARY" | "MARCH" | "APRIL" | "MAY" | "JUNE" | "JULY" | "AUGUST" | "SEPTEMBER" | "OCTOBER" | "NOVEMBER" | "DECEMBER";
            /**
             * The Permission the current user has in the Book
             */
            permission?: "OWNER" | "EDITOR" | "POSTER" | "RECORDER" | "VIEWER" | "NONE";
            /**
             * The key/value custom properties of the Book
             */
            properties?: {
                [name: string]: string;
            };
            /**
             * The time zone of the Book
             */
            timeZone?: string;
            /**
             * The time zone offset of the Book, in minutes
             */
            timeZoneOffset?: number; // int32
        };
              
        /** The user in charge of the Event */ 
        user?: {

            /** The user public avatar url */
            avatarUrl?: string;

            /** The user display name */
            name?: string;

            /** The Bkper username of the user */
            username?: string;
        };

        /** The Event agent, such as the App, Bot or Bank institution */ 
        agent?: {

            /** The agent id */
            id?: string;

            /** The agent logo. Public url or Base64 encoded */
            logo?: string;

            /** The agent name */
            name?: string;
        };

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

        /** The event data */
        data?:{
            
            /** 
             * The object payload. It will depend on the event type. 
             * 
             * For example, an ACCOUNT_CREATED type will receive 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?: "FILE_CREATED" | "TRANSACTION_CREATED" | "TRANSACTION_UPDATED" | "TRANSACTION_DELETED" | "TRANSACTION_POSTED" | "TRANSACTION_CHECKED" | "TRANSACTION_UNCHECKED" | "TRANSACTION_RESTORED" | "ACCOUNT_CREATED" | "ACCOUNT_UPDATED" | "ACCOUNT_DELETED" | "QUERY_CREATED" | "QUERY_UPDATED" | "QUERY_DELETED" | "GROUP_CREATED" | "GROUP_UPDATED" | "GROUP_DELETED" | "COMMENT_CREATED" | "COMMENT_DELETED" | "COLLABORATOR_ADDED" | "COLLABORATOR_UPDATED" | "COLLABORATOR_REMOVED" | "BOOK_UPDATED" | "BOOK_DELETED";
    }

The Event payload is the same exposed in the REST API and, if your are using Typescript, you can add the bkper-api-types package to your project and set the bkper.Event type on your trigger functions, like bellow:

function onTransactionChecked(event: bkper.Event)

bkper-node build

The bkper-node package helps you create and manage Apps and Bots configurations as well as provide a simple interface for accessing the Bkper REST API.

bkper-node installation

1) Add the package:

npm i -S bkper

or

yarn add bkper

API

The bkper-node offers a complete programming interface to help streamline the development workflow.

Checkout the API Reference

Commands

  • login - Logs the user in. Saves the client credentials to a ~/.bkper-credentials.json file.
  • logout - Logs out the user by deleting client credentials.
  • app -c - Create a new App based on ./bkperapp.json file
  • app -u - Update an existing App based on ./bkperapp.json file
Environment Variables

The app command uses the following environment variables in order to perform App create/update operations:

BKPER_API_KEY=XXXX
BKPER_CLIENT_SECRET=YYYY
BKPER_USER_EMAILS="someone@gmail.com anotherone@altrostat.com"
BKPER_DEVELOPER_EMAIL=somedeveloer@mycompany.com

You can add a .env file at the root of your project with those variables and bkper will automatically load from it.

WARNING: Never upload variables to the source code repository.

bkperapp.json

The bkperapp.json file stays in the root of the App project and contains the App configuration information.

Reference:

{
  "id": "The App agent id. It can NOT be changed after the App created.",
  "name": "The name of the App or Bot.",
  "logoUrl": "Set your logo url from public host. Best fit 200x200 px. Use https://",
  "menuUrl": "The menu url to open in the popup window. See reference bellow.",
  "menuUrlDev": "The menu url to open in the popup window, when opened by the developer user.",
  "menuText": "The context menu call to action.",
  "menuPopupWidth": "500 //width in pixels. Default to 80% of screen width.",
  "menuPopupHeight": "300 //height in pixels. Default to 90% of screen height.",
  "clientId": "The Client ID from GCP project Web Application OAuth Credential",
  "scopes": [
    "The Google OAuth scopes used. E.g.",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/script.external_request"
  ],


  "scriptId": "The Google Apps Script ID",
  "deploymentId": "The Google Apps Script API Deployment ID",

  "webhookUrl": "The production webhook url",
  "webhookUrlDev": "The development webhook url",

  "events": [
    "TRANSACTION_POSTED",
    "TRANSACTION_CHECKED",
    "TRANSACTION_UNCHECKED",
    "TRANSACTION_UPDATED",
    "TRANSACTION_DELETED",
    "TRANSACTION_RESTORED",
    "ACCOUNT_CREATED",
    "ACCOUNT_UPDATED",
    "ACCOUNT_DELETED",
    "GROUP_CREATED",
    "GROUP_UPDATED",
    "GROUP_DELETED",
    "FILE_CREATED",
    "BOOK_UPDATED"
  ],
  "filePatterns": [
    "The file patterns the Bot is capable of process. It accepts wildcard. E.g.",
    "radiusbank*.ofx",
    "-*.qif"
  ],
  "propertiesSchema": {
    "book": {
      "keys": [
        "key1",
        "key2"
      ],
      "values": [
        "value2",
        "value2"
      ]
    },
    "group": {
      "keys": [
        "key1",
        "key2"
      ],
      "values": [
        "value2",
        "value2"
      ]
    },
    "account": {
      "keys": [
        "key1",
        "key2"
      ],
      "values": [
        "value2",
        "value2"
      ]
    },
    "transaction": {
      "keys": [
        "key1",
        "key2"
      ],
      "values": [
        "value2",
        "value2"
      ]
    }
  }
}

Accepted expressions in menuUrl property:

  • ${book.id} - the current book id
  • ${book.properties.xxxxx} - any property value from the current book
  • ${transactions.query} - the current query being executed on transactions list
  • ${transactions.ids} - the ids of selected transactions, splitted by comma
  • ${account.id} - the current account being filterd
  • ${account.properties.xxxxx} - any property value from the current account being filtered
  • ${group.id} - the current group being filterd
  • ${group.properties.xxxxx} - any property value from the current group being filtered

Example:

"menuUrl": "https://app.bkper.com/b/#transactions:bookId=${book.id}"

REST API settings_ethernet

The Bkper REST API is the interface for Apps and Bots to interact with the Bkper Books having users sercurely authenticated under OAuth2 protocol.

Note: The REST API is the underlying connection interface behind the BkperApp library.

The API is built on Swagger OpenAPI and Google API Discovery specifications:

You can use these specification documents to generate client libraries using open source tools such as OpenAPI generator or Google APIs code generator, in the language of your choice.

To call the API directly from the browser, you can use the discovery document with the gapi.

If you are using Typescript, we keep an updated type definitions package on npm you can easily add to your projects for autocomplete and contextual documentation:

API Explorer

We provide a REST API Explorer at apis-explorer.appspot.com so you can check out the endpoints paths and payload formats, and try the API live:

API Explorer

Enabling the REST API

A Google Cloud Project project is required in order to use the Bkper REST API. Follow the steps to get started with the Bkper REST API:

NOTE: Do not store the API Key on your code to avoid leaks, potentialy leading to quota theft. See securing an API key best practices.

For Google Apps Script, you can use the Script Properties. To store it, open the online editor, File > Project properties > Script properties.

Authentication

The Bkper REST API uses OAuth 2.0 protocol with the email scope to authenticate users.

You should send a valid OAuth2 Bearer token in the Authentication header of the API http requests.

Server side Google Apps Script example

For Google Apps Script you can use the built in ScriptApp.getOAuthToken() in your code to access the OAuth2 access token the script has acquired and pass it in the Authorization header of a UrlFetchApp.fetch() call, like bellow:

function listBooks() {
  var options = {
    headers: {
       Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
     }
  };
  Session.getActiveUser().getEmail() // Ensure email scope used
  var url = 'https://app.bkper.com/_ah/api/bkper/v4/books?key=' + PropertiesService.getScriptProperties().getProperty('API_KEY'); // Store the key on File > Project properties > Script properties
  var content = UrlFetchApp.fetch(url, options).getContentText();
  Logger.log(content)
}

The ScriptApp.getOAuthToken() should work on most cases, but, if you work on some restricted environment, such as Google Sheets custom functions, you can manage the tokens with the OAuth2 for Apps Script library. See the bkper-sheets Authorizer for a detailed example.

Client side javascript example

For client side javascript, you should configure the OAuth consent screen and use the google-api-javascript-client initializing with our discovery document, your API key, Client ID and the email scope, like bellow:

<html>
  <head>
    <script src="https://apis.google.com/js/api.js"></script>
    <script>
      function start() {
        // Initializes the client with the API key and the Bkper API.
        gapi.client.init({
          discoveryDocs: ['https://app.bkper.com/_ah/api/discovery/v1/apis/bkper/v4/rest'],
          apiKey: 'YOUR_API_KEY',
          clientId: 'YOUR_CLIENT_ID',
          scope:'email'
        }).then(function() {
          // Executes an API request, and returns a Promise.
          // The method name `bkper.books.list` comes from the API discovery.
          return gapi.client.bkper.books.list();
        }).then(function(response) {
          var books = response.result.items;
          document.getElementById('results').innerText = JSON.stringify(books, null, 4);
        }, function(reason) {
          console.log('Error: ' + reason.result);
        });
      };
      // Loads the JavaScript client library and invokes `start` afterwards.
      gapi.load('client', start);
    </script>
  </head>
  <body>
    <div id="results"></div>
  </body>
</html>

NOTE: Don’t forguet to restrict your key only to http referrers your project will run

Metrics

Once you enable and start using the API, you can check out detailed metrics on the GCP Console, of the API calls for your project:

REST API Deeveloper portal

The metrics dashboard provides you with information about endpoint calls, latency and errors, giving a good overview of the project integration health.

Quota

The quotas dashboard provides details of the current default and quota exceeded errors.

The current default quota 180 requests per minute, per project. If you need to increase the quota, please get in touch so we discuss your case.

Examples

Bkper CSV App - Import/Export transactions in CSV format.

Tax Bot - Bot to calculate VAT, GST and taxes based on the transaction amount.

Exchange Bot - Bot to convert transaction amounts between Books based on updated exchange rates and calculate gains & losses.

Stock Bot - Bot to keep stocks/bonds instruments book in sync with financial books a calculate realized results in FIFO method.

Bkper Add-on for Google Sheets - Google Sheets Add-on to extend Bkper features, import/export data and run custom formulas from within your Spreadsheets.

Altough you can work on the Online editor really quickly, we strongly recommend clasp to develop locally with Typescript on VS Code editor, which is really powerfull and free, so you get:

  • Code Autocomplete
  • Contextual documentation
  • Compile time error checking
  • Code navigation - really helpful!
  • Calling hierarchy searching
  • Use of new javascript features such as classes, interfaces, arrow functions etc
  • Easier code redability
  • Automatic refactoring

Libraries build_circle

Those are some libraries we use to build our own Apps and Bots. It’s a set of well tested and documented libraries you can use on your own projects:

  • bkper-node - The Node.js client library we use to help manage Apps and Bots, as well as simplify the access to the Bkper REST API in the Node.js environment. Reference.

  • BkperApp - The Google Apps Script library to access the Bkper REST API in a secure and simple way. Reference.

  • HttpRequestApp - Fluent interface for Google Apps Script Url Fetch Service, to simplify HttpRequest building and 3rd party API integrations.

  • ExchangeApp - Google Apps Script library to exchange currencies based on updated rates.

Publishing

By default, the App or Bot you create is visible only for you or your company, in case of Google Workspace domains.

If you are interested in publishing your App or Bot to all users, please contact us at support@bkper.com