BioStar Air API Integration Quickstart Guide

This guide walks you through how to set up a BioStar Air demo site, connect and register a BioStar Air-compatible readers to it, and begin testing your integration using our cloud APIs.

If you’ve previously worked with BioStar 2, many concepts will feel familiar—but BioStar Air is fully cloud-native.


Getting Started

Pre-Requisites

Ensure the following:

  • You have a BioStar Air compatible devicewith factory-loaded firmware:
    • XP2-AIR, XS2-AIR, BEW3-AIR, BS3-AIR, BLN2-AIR
    • Devices may be purchased through authorized Suprema dealers
  • The device is connected to the internet via Ethernet.
  • Your network allows outbound access on:
    • Port 443 (HTTPS)
    • Port 5671 (MQTT over TLS)

Step 1: Set Up Your Demo Environment & API Key

1. Sign Up and Log In

2. Create a Demo Application

  • Navigate to API → Management
  • Click + Register
  • Choose Demo as the application type
  • Enter a name for your application and click Register
  • Click Download to retrieve an Excel file containing your demo site login credentials (email and password)

3. Issue an API Key

  • Under Application Management click into your registered application
  • Scroll to API Management
  • Click Add
  • Enter a name and confirm. Save the generated key.

4. Log Into the Demo Portal

  • Navigate to the Demo Portal
  • Log in with the email and password from the Excel file
  • Enter the API Key you just created

Step 2: Register the Reader

Use the Airfob Pro Demo App (Android only):

  1. Download the app: https://moca-public-file-share.s3.ap-northeast-2.amazonaws.com/DeviceRegistration.zip
  2. Log in with the credentials from the Excel file
  3. Bring your Android phone near the reader (BLE range), then:
    • Tap All Menu → Devices → + (upper right corner)
    • Locate your device and tap Register
  4. The reader will chime, reboot, and appear under Registered Devices

Note: Readers can only be registered using the mobile app. Web registration is not supported. API-based registration requires a cryptographic certificate not provided to partners.


Step 3: Manage the Reader

Manage the device using either:


API Base URLs

EnvironmentBase URL
Demohttps://demo-afs-api.airfob.com/v1/
Production - Europehttps://e-afs-api.airfob.com/v1/
Production - Koreahttps://a-afs-api.airfob.com/v1/ (for all non-EU customers)

Recommended Integration Scope

When possible limit your integration to:

  • User lifecycle management (create/update/delete)
  • Credential management (mobile, RF card, biometric)

Allow administrators to use the existing BioStar Air web and mobile admin apps for:

  • Access levels
  • Schedules
  • Door and reader settings
  • Site settings

Optional: API Login Flow (Postman or Programmatic)

Step 1 – Login

Endpoint: login

Payload:

{
  "username": "your_email",
  "password": "your_password"
}

Returns a Bearer Token (JWT).

Step 2 – Get Self Accounts

Endpoint: getSelfAccounts

Authorization: Bearer Token

Returns a list of accessible sites/accounts.

Step 3 – Login to an Account

Endpoint: loginAccount

Authorization: Bearer Token

Returns a site-specific token.


How to Get Your Bearer Token (Browser Method)

Chrome/Edge (Windows or Mac)

  1. Press F12 or Ctrl/Cmd + Shift + I
  2. Go to the Network tab
  3. Reload the page
  4. Search for API calls (e.g., groups, login)
  5. Click on the request
  6. Go to the Headers tab
  7. Look for Authorization: Bearer ... in Request Headers
  8. Right-click and copy the token

Safari (Mac)

  1. Enable Develop menu: Safari → Preferences → Advanced → Check Show Develop menu
  2. Open Develop → Show Web Inspector
  3. Go to Network tab
  4. Reload the page
  5. Filter and inspect an API call
  6. Copy the Authorization: Bearer token from the request headers

User Management API Calls

Once logged in, use these endpoints to manage users:

  • getUsers
  • createUser
  • updateUser
  • suspendUsers

Always include the Bearer Token in the Authorization header.

Note: To activate a user, you must assign at least one credential type (e.g., RF Card, Mobile, Linkpass)


Demo vs Production

  • Demo Sites are created via the Developer Portal for testing.
  • Production Sites are created via the Partner Portal and require site ID, user email, and password. Production sites can only be created by authorized dealers or Suprema branch offices.

Important Notes

  • User ID and Account ID are different — do not confuse them.
  • Store passwords securely if managing multiple sites.
  • Always use the most recent Bearer Token.
  • Bearer Tokens copied from the browser can be reused in Postman.

Need Help?

Open a ticket on the Suprema Technical Support Portal: https://support.supremainc.com

Sample Application

Need an example? The following is sample code for a Python app that lets you bulk suspend users by uploading a CSV file via the API.


import requests
import csv
import getpass
from pathlib import Path

def select_server():
    servers = {
        "1": ("Demo", "https://demo-afs-api.airfob.com/v1"),
        "2": ("Global", "https://a-afs-api.airfob.com/v1"),
        "3": ("EU", "https://e-afs-api.airfob.com/v1")
    }

    print("Please select a server:")
    for key, (name, _) in servers.items():
        print(f"{key}: {name}")

    choice = input("Enter server number: ").strip()
    return servers.get(choice, (None, None))[1]

def login(base_url, username, password):
    url = f"{base_url}/login"
    payload = {"username": username, "password": password}
    response = requests.post(url, json=payload)
    response.raise_for_status()
    data = response.json()
    token = data.get("access_token")
    if not token:
        raise ValueError("Login succeeded but no access_token returned.")
    print("✅ Login successful.")
    return token

def login_to_account(base_url, token, account_id):
    url = f"{base_url}/login/{account_id}"
    headers = {"Authorization": f"Bearer {token}"}
    response = requests.post(url, headers=headers)
    response.raise_for_status()
    new_token = response.json().get("access_token")
    if new_token:
        print(f"✅ Switched to account ID: {account_id}")
        return new_token
    return token

def get_accounts(base_url, token):
    url = f"{base_url}/accounts/self"
    headers = {"Authorization": f"Bearer {token}"}
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    accounts = response.json().get("accounts", [])
    return [{"id": acc["id"], "name": acc["site"]["name"]} for acc in accounts]

def suspend_users_from_csv(base_url, csv_path, token):
    if not Path(csv_path).exists():
        print(f"❌ File not found: {csv_path}")
        return

    headers = {"Authorization": f"Bearer {token}"}

    with open(csv_path, newline='', encoding='utf-8-sig') as f:
        reader = csv.DictReader(f)

        # Normalize headers
        reader.fieldnames = [field.strip().lower() for field in reader.fieldnames]
        if 'email' not in reader.fieldnames:
            print("❌ Missing required 'email' column in CSV.")
            return

        for row in reader:
            email = row.get("email")
            if not email:
                print("⚠️ Skipping row with missing email.")
                continue

            # Search user
            search_url = f"{base_url}/users/search"
            payload = {"filters": [{"field": "email", "equals": email}]}
            search_resp = requests.post(search_url, headers=headers, json=payload)
            if search_resp.status_code != 200:
                print(f"❌ Failed to search user {email}: {search_resp.text}")
                continue

            users = search_resp.json().get("users", [])
            if not users:
                print(f"❌ No user found with email: {email}")
                continue

            user_id = users[0]["id"]

            # Suspend user
            suspend_url = f"{base_url}/users/suspend"
            suspend_payload = {
                "ids": [user_id],
                "certify_by": "none",
                "use_site_template": True
            }
            suspend_resp = requests.post(suspend_url, headers=headers, json=suspend_payload)
            if suspend_resp.status_code == 200:
                print(f"✅ Suspended user: {email}")
            else:
                print(f"❌ Failed to suspend user {email}: {suspend_resp.text}")

def main():
    base_url = select_server()
    if not base_url:
        print("❌ Invalid selection. Exiting.")
        return

    print("\n? BioStar Air Login")
    username = input("Email: ")
    password = getpass.getpass("Password: ")

    token = login(base_url, username, password)
    accounts = get_accounts(base_url, token)

    print("\n? Available Sites:")
    for i, acc in enumerate(accounts):
        print(f"{i}: {acc['name']} (ID: {acc['id']})")

    selected = int(input("\nSelect site number to log into: "))
    account_id = accounts[selected]["id"]
    token = login_to_account(base_url, token, account_id)

    csv_path = input("Enter path to CSV file with user emails: ").strip()
    suspend_users_from_csv(base_url, csv_path, token)

if __name__ == "__main__":
    main()