본문으로 건너뛰기

API 통합 시작하기

개요

이 문서에서는 BioStar Air 데모 사이트 설정, 호환 BioStar Air 리더기 연결 및 등록, 클라우드 API를 사용한 통합 테스트 방법을 안내합니다.

이전에 BioStar 2에서 작업한 적이 있다면 많은 개념이 친숙하게 느껴질 것입니다. 그러나 BioStar Air는 완전히 클라우드 기반입니다.

시작하기

사전 요구 사항

아래 사항을 확인하세요.

  • 공장 초기화된 펌웨어를 설치한 BioStar Air 호환 장치를 준비하세요.

    • XP2-AIR, XS2-AIR, BEW3-AIR, BS3-AIR, BLN2-AIR

    • 장치는 공인 슈프리마 대리점을 통해 구매할 수 있습니다.

  • 장치는 이더넷을 통해 인터넷에 연결됩니다.

  • 네트워크에서 아웃바운드 액세스를 허용하세요.

    • Port 443 (HTTPS)

    • Port 5671 (MQTT over TLS)

Step 1: 데모 환경과 API 키 설정

회원 가입 및 로그인

  • BioStar Air Developer로 이동하세요.

  • 회원 가입을 클릭하고 양식을 작성한 후 승인을 기다리세요.

  • 승인이 완료되면 크리덴셜을 사용해 로그인하세요.

데모 애플리케이션 생성

  • APIManagement로 이동하세요.

  • + Register를 클릭하세요.

  • Demo를 애플리케이션 유형으로 선택하세요.

  • 애플리케이션 이름을 입력하고 Register를 클릭하세요.

  • 데모 사이트 로그인 크리덴셜(이메일 및 비밀번호)이 포함된 엑셀 파일을 다운로드하려면 Download를 클릭하세요.

API 키 발급

  • Application Management에서 등록한 애플리케이션을 클릭하세요.

  • API Management로 스크롤하세요.

  • Add를 클릭하세요.

  • 이름을 입력하고 확인하세요. 생성된 키를 저장하세요.

데모 포털 로그인

  • Demo Portal로 이동하세요.

  • 엑셀 파일의 이메일 및 비밀번호로 로그인하세요.

  • 방금 생성한 API 키를 입력하세요.

Step 2: 리더 등록

BioStar Air Demo App (안드로이드 전용)을 사용하세요:

  1. 앱을 다운로드하세요. DeviceRegistration.zip

  2. 엑셀 파일의 크리덴셜로 로그인하세요.

  3. 안드로이드 모바일 기기를 리더기 근처(BLE 범위)로 가져가세요.

    • All MenuDevices+ (오른쪽 상단 모서리)를 탭하세요.

    • 장치를 찾고 Register를 탭하세요.

  4. 리더기에서 소리가 나고 재부팅되며 등록된 장치 목록에 나타납니다.

리더기는 모바일 앱을 통해서만 등록할 수 있습니다. 웹 포털에서는 등록할 수 없습니다. API 기반 등록에는 파트너에게 제공되지 않는 암호화 인증서가 필요합니다.

Step 3: 리더기 관리

장치를 관리하려면 다음 중 하나를 사용하세요.

API Base URLs

환경Base URL
Demohttps://demo-afs-api.airfob.com/v1/
Production - Europehttps://e-afs-api.airfob.com/v1/
Production - Koreahttps://a-afs-api.airfob.com/v1/ (EU가 아닌 모든 고객 전용)

API 통합은 가능한 한 아래의 범위로 제한하세요.

  • 사용자 생애 주기 관리 (생성/업데이트/삭제)

  • 자격 증명 관리 (모바일, RF 카드, 생체 인식)

관리자가 기존 BioStar Air 웹 및 모바일 관리 앱을 사용할 수 있도록 허용합니다.

  • 출입 등급

  • 일정

  • 출입문 및 리더기 설정

  • 사이트 설정

Optional: API 로그인 절차 (Postman 또는 Programmatic)

로그인

Endpoint: 로그인

Payload:

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

Bearer 토큰(JWT)을 반환합니다.

Get Self Accounts

Endpoint: getSelfAccounts

Authorization: Bearer Token

접근 가능한 사이트 및 계정 목록을 반환합니다.

계정에 로그인

Endpoint: loginAccount

Authorization: Bearer Token

특정 사이트 토큰을 반환합니다.

웹 브라우저에서 Bearer 토큰 얻는 방법

Chrome/Edge (Windows 또는 Mac)

  1. 웹 브라우저에서 F12 또는 Ctrl/Cmd + Shift + I를 누르세요.

  2. Network 탭으로 이동하세요.

  3. 페이지를 새로 고침하세요.

  4. API 호출을 검색하세요.

    예, groups, login

  5. 요청을 클릭하세요.

  6. Headers 탭으로 이동하세요.

  7. 요청 헤더에서 Authorization: Bearer ...를 찾으세요.

  8. 오른쪽 마우스를 클릭하고 토큰을 복사하세요.

Safari (Mac)

  1. 개발자 메뉴를 확성화하세요. Safari → Preferences → Advanced → Show Develop menu를 체크하세요.

  2. DevelopShow Web Inspector으로 이동하세요.

  3. Network 탭으로 이동하세요.

  4. 페이지를 새로 고침하세요.

  5. API 호출을 필터링하고 검사하세요.

  6. 요청 헤더에서 Authorization: Bearer 토큰을 복사하세요.

사용자 관리 API 호출

로그인 후 다음 엔드포인트를 사용하여 사용자를 관리하세요.

  • getUsers

  • createUser

  • updateUser

  • suspendUsers

항상 Authorization 헤더에 Bearer 토큰을 포함하세요.

사용자를 활성화하려면 최소한 하나의 크리덴셜 유형을 할당해야 합니다.

예, RF 카드, 모바일, LinkPass

Demo vs Production

  • Demo Sites는 테스트를 위해 개발자 포털을 통해 생성됩니다.

  • Production Sites는 파트너 포털을 통해 생성되며 사이트 ID, 사용자 이메일 및 비밀번호가 필요합니다. Production 사이트는 승인된 판매자 또는 슈프리마 지사만 생성할 수 있습니다.

주요 참고사항

  • 사용자 ID와 계정 ID는 다릅니다. 혼동하지 마세요.

  • 여러 사이트를 관리한다면 비밀번호를 안전하게 저장하세요.

  • 항상 최신 Bearer 토큰을 사용하세요.

  • 브라우저에서 복사한 Bearer 토큰은 Postman에서 재사용할 수 있습니다.

도움이 필요하세요?

슈프리마 기술 지원 포털에서 티켓을 여세요. https://support.supremainc.com

애플리케이션 예제

다음은 API를 통해 CSV 파일을 업로드하고 사용자를 일괄 중단할 수 있는 Python 앱의 샘플 코드입니다.

Python
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()