Skip to Content

Auth API - Authenticated Requests

June 2, 2026 by
Auth API - Authenticated Requests
渥屋科技股份有限公司, 系統管理者
Authentication API Reference

Authentication API Reference

This guide walks an external developer through the steps needed to obtain credentials and call a WoowTech instance on a user's behalf. The flow is built on the OAuth 2 framework together with the IndieAuth client-discovery extension, so no app pre-registration is required — your public URL is your registration.

Identifying Your Application

Because of IndieAuth, you do not request a client secret or sign up anywhere. Instead, the public address where your app lives doubles as its identifier. Two values matter:

  • Client identifier — the canonical URL of your service, e.g. https://app.brightpanel.example.
  • Callback (redirect) target — where WoowTech sends the user back to. It has to live on the exact same scheme, hostname, and port as the identifier, e.g. https://app.brightpanel.example/woow/return.

Mobile and Desktop Callbacks

Native clients usually can't receive a browser redirect to an https:// page, so they rely on a custom URI scheme such as brightpanel://oauth. Since that scheme won't match your https client identifier, you have to advertise the allowed callback. Publish a discovery tag in the markup of your client-identifier page:

<link rel="redirect_uri" href="brightpanel://oauth">

When validating, WoowTech fetches your page and inspects only the leading 10 kilobytes for these redirect_uri link tags, so place them high in the document.

Step 1 — Send the User to Authorize

Open the user's browser at the /auth/authorize route of their server, passing your identifier and callback as URL-encoded query parameters:

https://demo.local:8123/auth/authorize?client_id=https%3A%2F%2Fapp.brightpanel.example&redirect_uri=https%3A%2F%2Fapp.brightpanel.example%2Fwoow%2Freturn

A common pattern is to attach a state value so you remember which instance the person was logging into. Round-tripping the base URL works well:

https://demo.local:8123/auth/authorize?client_id=https%3A%2F%2Fapp.brightpanel.example&redirect_uri=https%3A%2F%2Fapp.brightpanel.example%2Fwoow%2Freturn&state=https%3A%2F%2Fdemo.local%3A8123

Once the user signs in and grants access, WoowTech bounces them back to your callback with a one-time code (and the state you supplied, untouched):

https://app.brightpanel.example/woow/return?code=7f3a9b21&state=https%3A%2F%2Fdemo.local%3A8123

Step 2 — Trade the Code for Tokens

All token operations are POST requests sent to /auth/token with a application/x-www-form-urlencoded body.

Grant type: authorization_code

Convert the short-lived code into a token pair:

grant_type=authorization_code&code=7f3a9b21&client_id=https%3A%2F%2Fapp.brightpanel.example

A successful exchange yields JSON like this:

{
    "access_token": "ya29.brightAccess01",
    "expires_in": 1800,
    "refresh_token": "rt.brightRefresh99",
    "token_type": "Bearer"
}

The access_token lasts only 30 minutes (expires_in is in seconds). Hold onto the refresh_token — it's how you stay logged in afterward. A malformed body comes back as HTTP 400; a request for a user who has been disabled returns HTTP 403.

Grant type: refresh_token

When the access token ages out, mint a fresh one without bothering the user:

grant_type=refresh_token&refresh_token=rt.brightRefresh99&client_id=https%3A%2F%2Fapp.brightpanel.example

Note there is no new refresh token in the reply — keep reusing the original:

{
    "access_token": "ya29.brightAccess02",
    "expires_in": 1800,
    "token_type": "Bearer"
}

Tearing Down a Session

To log the user out everywhere, revoke the refresh token, which also kills every access token derived from it. The client_id field is unnecessary here:

token=rt.brightRefresh99&action=revoke

The endpoint always answers HTTP 200 with no payload, whether or not the token actually existed.

Long-Lived Access Tokens

For unattended integrations — background daemons, webhook receivers, scripts — there is a token variety that stays valid for roughly a decade. A user can mint one from the bottom of their WoowTech profile screen, or a frontend can request one over the WebSocket connection:

{
    "id": 27,
    "type": "auth/long_lived_access_token",
    "client_name": "Greenhouse Telemetry",
    "client_icon": null,
    "lifespan": 730
}

The server replies with the raw token string in result (you only see it once):

{
    "id": 27,
    "type": "result",
    "success": true,
    "result": "llat.greenhouse.deadbeef"
}

Calling the REST API With a Token

Attach whichever token you hold as a bearer credential in the Authorization header:

Authorization: Bearer ya29.brightAccess01

Shell (cURL)

curl -X GET \
  'https://home.brightpanel.example/api/config' \
  -H 'Authorization: Bearer ya29.brightAccess01'

Python (requests)

import requests

endpoint = "https://home.brightpanel.example/api/config"
auth = {"Authorization": "Bearer ya29.brightAccess01"}

reply = requests.get(endpoint, headers=auth)
print(reply.json())

Browser / Node (fetch)

const token = "ya29.brightAccess01";

fetch("https://home.brightpanel.example/api/config", {
  headers: { Authorization: `Bearer ${token}` },
})
  .then((res) => {
    if (!res.ok) throw new Error(`Request failed: ${res.status}`);
    return res.json();
  })
  .then((data) => console.log(data))
  .catch((err) => console.error(err));

If a call comes back as HTTP 401, the access token has lapsed. Run the refresh flow; should that fail too, walk the user back through the authorization step.

Signed Paths

Sometimes you need a plain GET URL — say, an <img> source or a file a browser downloads directly — that carries its own authentication and therefore needs no header. WoowTech can hand you a temporary, signature-bearing URL for exactly this. By default the signature is good for 30 seconds.

From an integration

Inside Python integration code, pull in the helper:

from homeassistant.components.http.auth import async_sign_path

From the frontend

Over the WebSocket API, ask for a signed version of a path:

{
  "type": "auth/sign_path",
  "path": "/api/camera_proxy/camera.front_door",
  "expires": 45
}

The response appends an authSig query argument:

{
  "path": "/api/camera_proxy/camera.front_door?authSig=signed.token.value"
}

Things to know about signed URLs

  • The signature dies if the originating refresh token is removed.
  • Deleting the owning user also voids it.
  • A WoowTech restart invalidates outstanding signatures.
  • Validity is checked the moment the request arrives, so an already-streaming long download keeps going even after the expiry passes.

Start writing here...

Share this post