Skip to content

Authentication

Every endpoint except GET / and POST /api/connect requires a Bearer token derived from a shared secret. This page is the reference for the scheme; Getting started walks through it with runnable code.

1. Obtain a shared secret (pairing)

Pair once via POST /api/connect (localhost only). Send an application name and the capabilities you need:

{ "application_name": "My Tool", "capabilities": ["project", "upload"] }

The user is shown a consent dialog naming your application and the requested capabilities. On approval you receive a secret:

{ "status": "ok", "secret": "aB3dE7fG9hJ1kL5m" }

Notes:

  • The dialog times out and declines after 30 seconds (403).
  • An empty or unknown capability list is rejected (400) before any dialog.
  • Capabilities are immutable for the life of the secret. To change scope, pair again.
  • Each call produces a distinct secret. You are responsible for persisting your own secret securely.

Clients that run on another machine

POST /api/connect is localhost-only, and returns the secret in its response body over plain HTTP — which is exactly why it isn't reachable from the network. A client running elsewhere therefore cannot pair itself.

Provision it in two steps instead:

  1. Pair once on the machine running LightBurn / MillMage — either from a local tool, or with any HTTP client (curl) against localhost.
  2. In the application, enable Settings → Extensions → Allow API Access From Network, then open Manage API Security from the same page, select the entry, and choose Show Secret…. Transfer it to the other machine over a channel you trust.

The Show Secret… button only appears while network access is enabled. With the API on loopback there is no off-machine client to hand a secret to, so the button is hidden. If you can't see it, check that setting first.

The secret is not machine-bound: once issued it works from anywhere the listener is reachable. Treat it as a password in transit — revoke it from the same dialog if it is ever exposed.

2. Derive a Bearer token

The token is an HMAC-SHA256 of the current Unix minute, keyed by the secret, hex-encoded:

token = hex(HMAC-SHA256(secret, floor(unix_time / 60)))

The server accepts tokens for the current and previous minute, tolerating up to ~1 minute of clock drift. Compute a fresh token per request (cheap) or cache it per minute.

import hmac, hashlib, time

def bearer_token(secret: str) -> str:
    message = str(int(time.time()) // 60).encode()
    return hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()

3. Send it

Authorization: Bearer <token>
flowchart TD
    S[Stored secret] --> T["token = HMAC-SHA256(secret, floor(now/60))"]
    T --> R["Authorization: Bearer <token>"]
    R --> V{Server validates<br/>current or previous minute}
    V -- valid --> OK[200 / 202]
    V -- invalid/missing --> E[401 Unauthorized]

Failure modes

Situation Response What to do
Missing or malformed token 401 Recompute the token; check the clock
Token for an expired minute 401 Recompute (clock drift > 1 min)
Secret revoked / stale 401 Discard the secret and re-pair
Endpoint outside granted scope 401 Re-pair with the needed capability
Request from off-machine while network access is off Connection refused Enable network access in the app, or call from localhost
Secret paired with a different running copy 401 Pair again — see Multiple instances
POST /api/connect from off-machine 403 Pair on the machine running the app
Pairing declined or timed out 403 Prompt the user and retry POST /api/connect

Every 401 above is deliberately indistinguishable from the others. A gated endpoint returns a bare 401 whether the token is absent, expired, revoked, lacking the capability, unknown to the copy that answered, or arriving from a disallowed origin. The API does not tell an unauthenticated caller which it is, so don't branch on the reason — on any 401, recompute the token once, and if it persists, re-pair.

Connection refused is a different signal: nothing is listening on that address. Usually that means the application isn't running, or it is bound to loopback and you called it from another machine. It is not an authorization failure.

Security notes

  • Treat the secret like a password. Store it per-application, not in shared locations.
  • The API is loopback-only by default. Exposing it to a network widens the attack surface — see ../SAFETY.md and ../SECURITY.md.
  • The token is time-based, not a bearer secret in transit beyond its minute window, but it is still sensitive — do not log it.