Connect every system you run.
Build the missing one in hours.

beetl reads more than 100 systems out of the box. When yours isn't one of them, we write the connector with our open-source SDK: a short TypeScript file, validated before it goes live, and yours to change.

Start from 100+ ready connections.
Ask for the one that isn't there.

The catalogue as it looks in the product, with the systems we read today. Search it, or ask the assistant.

We run Shopify, Business Central and an ERP our team built in 2014. Can you connect all three?
1 tool call
List connection typessuccess— 110 available

Shopify and Business Central are in the catalogue. Your own ERP isn't, so it gets a custom connection type: one TypeScript file that beetl builds and validates before anyone can use it.

Ask anything about your workspace…

Add connection

Choose a connection type to get started.

102 more Browse all

A brand new connection type.
Exactly the way you need it.

A connection type is a TypeScript file with the data contract next to the code. beetl reads that file and builds the setup form, the syncs and the data set from it. Scroll through it.

The schema becomes the data set

Five fields, declared once. Every record is checked against them before it's stored.

erp-orders.ts
const Order = z.object({  id: z.string(),  customer: z.string(),  total: z.number(),  status: z.enum(["open", "shipped", "invoiced"]),  updated_at: z.iso.datetime(),});

Inputs become the setup form

Two fields and a bearer token. beetl draws the form from the schema, so nobody builds UI for a connector.

erp-orders.ts
  connection: {    origin: { type: "input", input: "origin" },    inputs: z.object({      origin: z.url().meta({ title: "ERP address" }),      company: z.string().meta({ title: "Company code" }),    }),    auth: auth.bearer(),

Verify runs before anything is saved

Retries are one line. Verify calls the API before the connection is saved, so a wrong address fails here, not on the first night's sync.

erp-orders.ts
    retry: { maxAttempts: 5, statuses: [429, 500, 502, 503] },    async verify(ctx) {      await ctx.json(`/api/${ctx.config.company}/orders?limit=1`, Page);    },

Merge on id

An updated order replaces its old version. Running the sync again never doubles a row.

erp-orders.ts
  syncs: (sync) => ({    orders: sync({      displayName: "Orders",      mode: "merge",      primaryKey: ["id"],      records: Order,

Each page commits with its checkpoint

If a run stops halfway, the next one starts after the last page that made it in.

erp-orders.ts
      checkpoint: z.object({ since: z.string() }),      async *run(ctx) {        const { company } = ctx.config.connection;        let since = ctx.checkpoint?.since ?? "";        for await (const page of ctx.paginate({          request: { path: `/api/${company}/orders`, query: { since } },          schema: Page,          next: ({ data }) => (data.next ? { path: data.next } : undefined),        })) {          since = page.data.orders.at(-1)?.updated_at ?? since;          yield { records: page.data.orders, checkpoint: { since } };        }      },
EO

ERP orders Custom

erp-orders.tgz · built and validated

Connection verifiedretries 429 · 500 · 502 · 503, up to 5 attempts

Syncs

Ordersmerge on idEvery hour
Last run 14:02 · 1,284 orders · next run resumes from 2026-10-27T14:02:11Z

Data set erp_orders.orders

idstring
customerstring
totalnumber
statusopen · shipped · invoiced
updated_atdatetime

Change any connector to fit.
Same SDK, same checks.

Most of our connectors are written with the same SDK, so each one is a starting point, not a black box. One sales channel out of Shopify, a field your ERP gained last year, a different schedule: a few lines, reviewed and validated like the rest.

S

Orders

Shopify · sync settings

Every 15 minutes · output data set shopify.orders

shopify/integration.ts

@@ -43,3 +43,4 @@ const Inputs = z.strictObject({

}),

+salesChannel: z.string().meta({ title: "Sales channel" }).optional(),

pageSize: z.number().int().min(1).max(100).default(50),

@@ -107,2 +108,3 @@ async *run(ctx) {

const filters = config.query ? [`(${config.query})`] : [];

+if (config.salesChannel) filters.push(`sales_channel:${config.salesChannel}`);

if (definition.root === "orders") {

Two lines in the Shopify connector, and the sync has a new setting.

Fast to write.
Hard to break.

Speed only counts if the connector still works in month six. The SDK owns the parts hand-written integrations usually get wrong: paging, retries, validation, and where to start again after a failure.

  1. Page 1500 orders · committed with its checkpoint
  2. Page 2500 orders · committed
  3. Page 3HTTP 429 · retried after 0.5 s and 1 s · committed
  4. Page 4Worker restarted mid-page · nothing half-written
  5. Next runStarts after page 3 · page 4 committed once

20

connectors in one day, by one engineer.

An agent drafts each one from the vendor's API docs. An engineer reviews it before it ships.

  • Every record is checked against its schema. One that doesn't match is rejected, not quietly stored.
  • A page and its checkpoint commit together, so a restart picks up exactly where the last commit left off.
  • Retries back off on rate limits and server errors, within the limits the connector declares.
  • The connector runs isolated. It gets the source credentials it needs, never the ones for where the data lands.
  • Read-only by default. What the source sent is kept as it came, before any of your mappings run.

No API?
It runs inside your network.

Some systems were never meant to be reached from outside: an ERP database in the server room, a file share, a machine log. A site connector inside your network reads them and opens an outbound tunnel. No inbound firewall rules, no open ports.

  • DatabasesRead-only, over a scoped connection
  • Files and spreadsheetsShares, uploads and object storage
  • WebhooksTyped and schema-checked on the way in
  • Local systemsThrough the site connector

Your network

ERP databaseSite connector
beetl

For wholesale, manufacturing and maritime logistics teams that run on several systems, documents and spreadsheets.

  • SAP
  • Business Central
  • Erpnext
  • Shopify
  • Bigcommerce
  • Wix
  • Hubspot
  • Zoho Crm
  • Adyen
  • Paddle
  • Chargebee
  • Shippo
  • Postgresql
  • Sql Server
  • Bigquery
  • Databricks
  • Microsoft Fabric
  • Amazon S3
  • Azure Blob Storage
  • Google Cloud Storage
  • Airtable
  • Notion
  • Slack
  • Servicenow
  • Zendesk
  • Jira

Open source.
Apache 2.0.

The SDK is public. Your engineers can read exactly what runs, try a connector on a laptop against the real API, and write their own. Upload the package in beetl, or send it to us and we'll review it.

Read the SDK on GitHub ↗
# add the SDK$ npm install --save-dev @beetlio/connect# set up a connection and run a sync on your laptop$ npx beetl-connect configure . orders$ npx beetl-connect sync . orders --output orders.ndjson# package it for beetl$ npx beetl-connect pack .

Tell us what your teamneeds to get done.

A rough description of the task is enough to start. We agree the scope and who looks after it with you.