A self-hosted integration hub. Run it on your own server and connect as many of your sites as you like through a single local API.
- Nothing runs on anyone else's server. No accounts, no cloud, no phone-home. Your hub issues the keys and your hub verifies them.
- Every site gets its own key.
initmints the admin key (bsc_) that runs the install; registering a site mints a site key (bscs_) for that site alone. A site key can push into a connection and read the result — it cannot create a site, define a connection, or mint another key. So a leaked key names exactly one site, is revoked on its own, and cannot be used to widen the breach. - Keys are entropy, not registry. 512 bits of CSPRNG randomness each, stored only as a SHA-256 hash, shown once, with a checksum that rejects typos offline.
- One engine, your workflows. A connection is a list of steps —
fetch(call any of your sites/APIs),transform(reshape data),when(conditional gate). Push data into a connection and read the result back out.
cd packages/connect
npm install(Or, once published, npm install -g @bruncsoft/connect.)
# 1. Mint the admin key (printed once — store it now)
node bin/connect.js init
# 2. Run the hub (defaults to 127.0.0.1:8787)
node bin/connect.js start --port 8787Management uses the admin key:
KEY=bsc_... # the admin key from `init`
H="Authorization: Bearer $KEY"
# register a site — the response carries THAT SITE'S own key, shown once
curl -X POST localhost:8787/sites -H "$H" -H 'Content-Type: application/json' \
-d '{"name":"My Shop","base_url":"https://shop.example.com"}'
# → { "ok": true, "slug": "my-shop", "key": "bscs_…", "note": "shown once…" }
# define a connection: forward only when the value changed
curl -X POST localhost:8787/connections -H "$H" -H 'Content-Type: application/json' -d '{
"name": "stock sync",
"steps": [
{ "type": "when", "left": "{{input.sku}}", "op": "not_equals", "right": "{{state.last.sku}}" },
{ "type": "fetch", "url": "https://shop.example.com/api/stock", "method": "POST", "body": { "sku": "{{input.sku}}" } },
{ "type": "transform", "output": { "sku": "{{input.sku}}", "synced_at": "{{meta.now}}" } }
]
}'
# push data in, read the latest result out — each site uses ITS OWN key here
SITE=bscs_...
curl -X POST "localhost:8787/c/<slug>/in" -H "Authorization: Bearer $SITE" \
-H 'Content-Type: application/json' -d '{"sku":"A-1"}'
curl "localhost:8787/c/<slug>/out" -H "Authorization: Bearer $SITE"Give each site the key that was issued for it. Never hand a site the admin key: it is the only credential that can register sites, define connections and mint keys.
| Method | Path | Key | Description |
|---|---|---|---|
GET |
/ |
— | Public health check ({ service, version, initialised }). |
GET/POST/DELETE |
/sites · /sites/:slug |
admin | Manage connected sites. POST returns the new site's key, once. |
POST/DELETE |
/sites/:slug/key |
admin | Rotate / revoke one site's key. Rotating kills the old one immediately. |
GET/POST |
/connections · /connections/:slug |
admin | Define / inspect connections. |
POST |
/c/:slug/in |
admin or site | Run the connection over the posted JSON. |
GET |
/c/:slug/out |
admin or site | Latest successful result. |
GET |
/c/:slug/runs |
admin | Recent run history, with pushed_by naming the site. |
A site key used on an admin endpoint gets 403, not 401 — it is a valid key being used beyond what it is for.
fetch—{ "type":"fetch", "url", "method"?, "headers"?, "body"? }→{ status, body }.transform—{ "type":"transform", "output": <any JSON with {{placeholders}}> }.when—{ "type":"when", "left", "op"?, "right" }(op:equals|not_equals|contains, defaultnot_equals). If false the run stops silently — nothing is stored.
{{input.*}} what you pushed · {{prev.*}} previous step output · {{steps.N.*}} step
by index · {{state.last.*}} previous successful result · {{meta.now}} timestamp.
bruncsoft-connect init mint the ADMIN key (once)
bruncsoft-connect start [--port N] [--host H] [--restrict-private]
bruncsoft-connect key:rotate new admin key, retire the old one
bruncsoft-connect sites list sites and their key labels
bruncsoft-connect site:add --name "My Shop" [--url https://shop.example.com]
register a site and issue ITS key
bruncsoft-connect site:key --site my-shop
rotate one site's key
Options: --data DIR (default ./data), --port (8787), --host (127.0.0.1),
--restrict-private (block fetch to private/loopback addresses — off by default,
since a self-hosted hub usually wants to reach your internal sites).
- Every key is 512-bit CSPRNG, stored only as a SHA-256 hash, and carries a checksum that rejects typos offline. The admin key is compared in constant time; a site key is resolved by hash lookup, which reveals nothing to anyone who cannot produce the hash.
- One key per site. Rotate a site with
site:key --site <slug>orPOST /sites/:slug/key; revoke it withDELETE /sites/:slug/key. Deleting a site deletes its key. Rotating the admin key leaves site keys alone, and vice versa. - A site key cannot manage anything, so it cannot escalate: no new sites, no new connections, no new keys, and no reading another site's run history.
data/connect.dbis created0600(owner-only) and holds every key hash.- Bind to
127.0.0.1and put it behind your own HTTPS reverse proxy; if you bind to a public interface, firewall it. Request bodies are capped at 256 KB;fetchresponses at 512 KB with a 10 s timeout andredirect: error. - Run history never stores request headers or tokens.
npm test