API
Send logs and events with a key. Everything else runs on your signed-in session, so there is no admin token to leak.
Ingest: POST /i
POST /v1/event is the same endpoint.
curl -X POST https://upcontrol.io/i \-H "X-Upcontrol-Key: $UPCONTROL_API_KEY" \-d '{ "msg": "payment_failed", "level": "warn", "provider": "stripe" }'# 200 {"accepted":1}
X-Upcontrol-Key, Authorization: Bearer, ?key= or a top-level key field. The first one found wins.413 over 16 MiB. 503 spool_full with Retry-After: 30 under overload. A line is never refused for what it contains.Management: /v1
PATCH /v1/monitors/{id}{ "interval": "1m" } # from a Free project# 402 {"error":{"code":"plan_limit_exceeded","message":"…","upgrade":{"reason":"…"}}}
uc_session cookie).{ "error": { "code", "message" } }. 400 for bad input; 402 for a plan limit, with error.upgrade.reason.429.Keys
Add, revoke and rotate them on the Connect page.
uc_live_…. Lives in .env on your server. Sends logs and events, and reads, writes and adds dashboards (npx upcontrol board). It can't rename or delete one, read your logs, or change checks, alerts or billing.uc_pub_…. Safe in a browser bundle. Sends named events only, and only from the origins you list, matched exactly.From a browser
The first way in is one script tag. Paste it into the <head> of every page. The key comes from Sources, Add it to a website, and works only from the addresses listed on it.
With a coding agent it is one command: npx upcontrol web https://your-site.com http://localhost:3000 mints the key with the project key already in .env and prints the tag, and the agent puts it where your framework keeps the <head>. Name every address the site answers on, local dev included: a rerun never widens the key.
<script defer src="https://upcontrol.io/uc.js" data-key="uc_pub_…"></script>
How to add it on your platform
header.php.theme.liquid, before </head>.<Script src="https://upcontrol.io/uc.js" data-key="uc_pub_…" strategy="afterInteractive" /> in the root layout.</head>.Sending your own events
track() there. No key in the bundle, ad blockers do not cut a request to your own domain, and the visitor comes from your session.VITE_ or NEXT_PUBLIC_ variable ships its value to every visitor.Through your own server:
const ALLOWED = new Set(['page_view', 'buy_clicked']); // the server names the eventsapp.post('/api/track', (req, res) => {res.sendStatus(204); // answer first: telemetry never holds a pageconst name = req.body?.name;if (!ALLOWED.has(name)) return;track(name, { 'uc.actor': 'u_' + req.session.userId }); // prefixed: a bare 13-19 digit id is scrubbed as a card});
With a public key, from a static site. Get one on the Connect page, under Add it to a website.
const line = JSON.stringify({ msg: 'page_view', 'uc.event': true, 'uc.actor': visitorId }) + '\n';fetch('https://upcontrol.io/i?key=' + PUBLIC_KEY, {method: 'POST',body: line,keepalive: true, // survives the page closingheaders: { 'Content-Type': 'application/x-ndjson' },});
Public endpoints
host is required. A page the host already has, claimed or not, is answered as it is (created: false). A new host gets an unclaimed page with one check, the host itself (created: true), if it answers: otherwise 400 unreachable carries what the probe found, and nothing is created. Signed in or not, the answer is the same: a page becomes yours when you claim it. targets counts only with email, which also creates an account and mails a sign-in code.