Developers

Realtime and the SDK

The SDK keeps one live connection to Bekom. Calls go over it, and Bekom pushes what changes back over it, so there is nothing to poll.

Install and connect

npm install @bekom/sdk

import bekom from '@bekom/sdk';

// 'production' takes bkm_live_ keys; 'testing' takes bkm_test_ keys.
const client = bekom.connect('production', { apiKey: process.env.BEKOM_API_KEY });

const me = await client.describe();   // who this key is, its scopes and routes

For buyers: your notices and the bids you receive

const notices = await client.notices.list();

const bids = await client.notices.bids(noticeId);
if (bids.sealed) {
  console.log(`${bids.count} sealed bids; they open at ${bids.closingAt}`);
} else {
  const bid = await client.bids.get(bids.rows[0].id);   // sections, files, versions, signature
  await client.bids.review(fileId, 'resubmit', 'Please send the S3 data sheet.');
}

For suppliers: the board, and preparing a bid

const { items } = await client.tenders.list({ closing: 'open', province: 'Gauteng' });
const fits = await client.tenders.fit({ bucket: 'qualify' });   // Qualify plan

// Prepare a bid on a notice published on Bekom. A person signs it in the dashboard.
await client.submissions.addFile(noticeId, {
  file: { data: fs.readFileSync('tax.pdf'), name: 'tax.pdf', type: 'application/pdf' },
  section: 'tax',
  label: 'SARS tax compliance status',
  description: 'PIN valid to March 2027',
});

Listen first, then send

Bekom never answers through a callback. Every request x is answered by an xSuccess or an xError event. Register your listeners for both first, then send. A listener stays until you stop it, so it also hears results and errors caused by any other part of your program.

const stop = client.listen('listMyNotices', {
  success: (notices) => render(notices),
  error: (e) => console.error(e.code, e.message),
});
client.send('listMyNotices');   // the answer reaches the listener above

stop();   // when you no longer want them

The helpers (await client.notices.list() and the rest) do the same thing for one request: listen, send, and settle on whichever answer comes. Calls to the same event are queued, so two that overlap each get their own answer, never each other's.

The connection itself

client.on('disconnected', (reason) => showOffline(reason));   // calls in flight fail now, code 'disconnected'
client.on('reconnected', () => client.send('listMyNotices'));   // fetch again what you show
client.on('connectionError', (e) => console.error(e.code));     // e.g. a revoked key

Live pushes

client.on('notification', (n) => console.log(n.title, n.href));
client.on('tenderIngested', () => refreshBoard());
  • notification: Something your organisation should act on: a bid received, a file reviewed, a comment.
  • tenderIngested: The board changed: new or updated tenders are available.

Errors

try {
  await client.tenders.fit();
} catch (e) {
  if (e.code === 'plan_required') console.log('Needs the', e.needs, 'plan');
  if (e.code === 'scope_required') console.log('Create a key with the right scope');
  if (e.code === 'disconnected') console.log('The connection dropped; send it again');
}

Without the SDK

The live connection is Socket.IO on the /v1 namespace, with the key in auth.apiKey. Every call is an event named as in the REST reference; Bekom answers with <event>Success or <event>Error. The SDK is the supported way; this is here so nothing is hidden.