@trillboards/api-client
Official TypeScript SDK for the Trillboards DOOH Partner API.
Installation
npm install @trillboards/api-client
Requires Node.js 18+. REST uses native fetch; media buying uses the official MCP client and streaming uses its shared SSE parser. Keep buying credentials in your server application.
Quick Start
import { TrillboardsClient } from '@trillboards/api-client';
const client = new TrillboardsClient({
apiKey: 'trb_partner_xxxxx',
});
// List screens
const { data: screens } = await client.screens.list({ limit: 10 });
// Get analytics
const { data: analytics } = await client.analytics.get({ period: 'week' });
// Get earnings
const { data: earnings } = await client.earnings.get({ period: 'month' });
Resources
| Resource | Methods |
|---|---|
client.screens | list, get, create, delete, getDisplaySettings, patchDisplaySettings, updateDisplaySettings, batchUpdateDisplaySettings |
client.devices | list, get, create, createBatch, delete, heartbeat |
client.impressions | create, createBatch |
client.analytics | get |
client.earnings | get |
client.webhooks | list, create, update, delete, test |
client.fleet | pushCommand, listCommands |
client.audience | getLive, getHeatmap, getStats |
client.data | getVenueIntelligence, getUsage, listScreens, getHistory, getAudience, query, stream |
client.errors | list, get |
client.billing | setup, createCreditCheckout, purchaseCredits, getUsageSummary, get, getStripeStatus, requestPayout |
client.mediaBuys | getProducts, discoverInventory, listAccounts, syncAccounts, create, list, update, syncCreatives, getDelivery, close |
client.creativeExperiments | create, get, configureBrand, generate, importCreative, review, registerVariants, updatePolicy, getRewards, stop |
Fund and run a creative feedback loop
Use the Creative AI Feedback Loop for complete buying, creative review, serving and correction-aware reward examples. The same partner key works with REST and native MCP buying tools.
Create a credit checkout with a stable key for this exact purchase, then open the returned Stripe URL:
const checkout = await client.billing.createCreditCheckout(
{ tier: 'tier_500' },
{ idempotencyKey: 'my-first-credit-checkout-v1' },
);
console.log(checkout.checkout_url);
After completing payment, read the committed balance:
const balance = await client.billing.getUsageSummary();
console.log(balance.credit_balance_cents, balance.credit_reserved_cents);
Balances and metered amounts retain their exact decimal strings. A saved payment method alone does not fund delivery. Media uses its accepted quote; API usage uses the published API tariff.
Direct purchaseCredits can return requires_action: true with a client_secret. Complete that payment's Stripe authentication, then read the committed balance; retain its operation_id and stable key. The SDK returns these fields unchanged. Stripe Checkout handles authentication in the returned browser flow.
Discover your account and the live product catalog before creating a buy:
try {
const accounts = await client.mediaBuys.listAccounts();
const products = await client.mediaBuys.getProducts({
brief: 'Transit screens for a creative attention test',
pagination: { max_results: 10 },
});
console.log(accounts.accounts, products.products, products.pagination);
} finally {
await client.mediaBuys.close();
}
client.mediaBuys.create takes the native AdCP request, including the account, flight, product/pricing-option identifiers, budget and stable idempotency_key. list accepts media_buy_ids and pagination; update accepts the native revision. Preserve media_buy_id and packages[].package_id for creativeExperiments.create. The MCP task's status describes task execution; the returned media-buy lifecycle, funding and moderation describe delivery readiness. Always release the MCP connection with mediaBuys.close() when finished.
Historical and live data
The Data API resources require the scopes declared by their endpoints: read:live, read:historical or stream:live. Screen-list pagination is returned unchanged as has_more and next_offset. Bounded history reads preserve unmeasured values as null.
const history = await client.data.getHistory('YOUR_SCREEN_ID', {
since: '2026-10-01T00:00:00Z',
until: '2026-10-02T00:00:00Z',
limit: 100,
});
console.log(history.metrics);
const controller = new AbortController();
try {
for await (const event of client.data.stream({}, { signal: controller.signal })) {
console.log(event.event, event.data);
if (event.event === 'audience') break;
}
} finally {
controller.abort();
}
The stream forwards connected, audience and in-band error events. Breaking iteration or aborting closes the connection. It is a live tail: reconnect explicitly and use bounded history reads to recover historical data. Creative reward corrections use the guide's canonical-key/revision scan protocol.
MCP tool failures throw McpToolError with the exact tool and original result, including structured error codes and receipts. Protocol/HTTP errors from the official MCP transport remain intact. REST failures use TrillboardsError.
Content fit
await client.screens.patchDisplaySettings('SCREEN_ID', {
content_fit: {
advertisements: 'fill',
default_streams: 'fill',
self_promo: 'fit',
},
});
await client.devices.createBatch({
defaults: {
display_settings: {
content_fit: { advertisements: 'fill', self_promo: 'fill' },
},
},
devices: [
{ device_id: 'screen-001' },
{
device_id: 'screen-002',
display_settings: { content_fit: { self_promo: 'fit' } },
},
],
});
fit preserves the full creative and may letterbox; fill covers the screen
and may crop its edges. Omit content_fit to leave existing behavior unchanged.
Error Handling
import { TrillboardsClient, TrillboardsError } from '@trillboards/api-client';
try {
await client.screens.get('nonexistent');
} catch (err) {
if (err instanceof TrillboardsError) {
console.log(err.code); // 'screen_not_found'
console.log(err.statusCode); // 404
console.log(err.docsUrl); // 'https://api.trillboards.com/docs/errors#screen_not_found'
if (err.isRateLimited) {
const retryAfter = err.retryAfter; // seconds from Retry-After header
}
}
}
Webhook Verification
Trillboards webhooks carry a Stripe-style compound signature header
X-Trillboards-Signature: t=<timestamp>,v0=<hex>,v1=<hex> and a separate
X-Trillboards-Timestamp: <unix_seconds> for redundancy.
The verifier accepts either format and rejects payloads outside a 300-second replay window by default.
import { Webhooks } from '@trillboards/api-client';
// Use express.raw for the webhook route so req.body is the exact signed bytes
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = Webhooks.constructEvent(
req.body, // raw Buffer or string (NOT JSON.parsed)
req.headers, // IncomingHttpHeaders — case-insensitive lookup
process.env.WEBHOOK_SECRET!,
);
console.log('Event:', event.event, event.data);
res.sendStatus(200);
} catch (err) {
// TrillboardsError with code "webhook_signature_invalid"
res.sendStatus(401);
}
});
Override the replay-window tolerance (default 300s):
const event = Webhooks.constructEvent(req.body, req.headers, secret, {
tolerance: 600, // 10 minutes
});
Upgrading from 0.2.0: the 0.2.0 call shape
Webhooks.constructEvent(payload, signatureString, secret)produced verification failures against the production server because the server signs${timestamp}.${payload}while the 0.2.0 verifier signedpayloadonly. The 0.3.0 verifier handles both formats AND adds replay protection. Migrate to the newWebhooks.constructEvent(payload, req.headers, secret)call shape. The 0.2.0 shape is preserved asWebhooks.constructEventLegacyfor one minor version (logs a deprecation warning).
Full webhook contract: https://api.trillboards.com/docs/integrations/webhook-signature-verification.md
Configuration
const client = new TrillboardsClient({
apiKey: 'trb_partner_xxxxx', // Required
baseUrl: 'https://api.trillboards.com', // Optional, defaults to production
version: '2024-01-15', // Optional, sent as Trillboards-Version header
});
License
MIT