Authentication
Spatial.Properties uses Supabase Auth for authentication. All authenticated endpoints require a JWT access token passed as a Bearer token in the Authorization header.
Authentication Flow
Section titled “Authentication Flow”sequenceDiagram
participant Client
participant Supabase Auth
participant API
Client->>Supabase Auth: Sign in (email + password)
Supabase Auth-->>Client: access_token + refresh_token
Client->>API: Request + Authorization: Bearer access_token
API->>API: Validate JWT signature
API-->>Client: Response (200)
- Sign in via the Supabase Auth API to receive an
access_token(JWT) and arefresh_token. - Include the
access_tokenin every API request as a Bearer token. - The API validates the JWT signature on every request. Invalid or expired tokens return a
401error.
Obtaining a Token
Section titled “Obtaining a Token”Sign in to receive your access token. Replace YOUR_PROJECT with your Supabase project ID and YOUR_SUPABASE_ANON_KEY with your project’s anonymous key.
curl -X POST 'https://YOUR_PROJECT.supabase.co/auth/v1/token?grant_type=password' \ -H 'apikey: YOUR_SUPABASE_ANON_KEY' \ -H 'Content-Type: application/json' \ -d '{ "email": "you@example.com", "password": "your-password" }'import requests
response = requests.post( "https://YOUR_PROJECT.supabase.co/auth/v1/token", params={"grant_type": "password"}, headers={ "apikey": "YOUR_SUPABASE_ANON_KEY", "Content-Type": "application/json", }, json={ "email": "you@example.com", "password": "your-password", },)
data = response.json()access_token = data["access_token"]const response = await fetch( 'https://YOUR_PROJECT.supabase.co/auth/v1/token?grant_type=password', { method: 'POST', headers: { 'apikey': 'YOUR_SUPABASE_ANON_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'you@example.com', password: 'your-password', }), });
const data = await response.json();const accessToken = data.access_token;The response includes:
{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 3600, "refresh_token": "abc123..."}Using the Token
Section titled “Using the Token”Pass the access_token as a Bearer token in the Authorization header on every API request.
curl -X GET 'https://api.spatial.properties/billing/credits/balance' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'response = requests.get( "https://api.spatial.properties/billing/credits/balance", headers={"Authorization": f"Bearer {access_token}"},)
print(response.json())const response = await fetch( 'https://api.spatial.properties/billing/credits/balance', { headers: { 'Authorization': `Bearer ${accessToken}`, }, });
const balance = await response.json();Token Lifecycle
Section titled “Token Lifecycle”- Access tokens expire after approximately 1 hour (
expires_in: 3600). - Refresh tokens have a longer lifespan and are used to obtain new access tokens without re-entering credentials.
When your access token expires, use the refresh token to get a new one:
curl -X POST 'https://YOUR_PROJECT.supabase.co/auth/v1/token?grant_type=refresh_token' \ -H 'apikey: YOUR_SUPABASE_ANON_KEY' \ -H 'Content-Type: application/json' \ -d '{"refresh_token": "YOUR_REFRESH_TOKEN"}'response = requests.post( "https://YOUR_PROJECT.supabase.co/auth/v1/token", params={"grant_type": "refresh_token"}, headers={ "apikey": "YOUR_SUPABASE_ANON_KEY", "Content-Type": "application/json", }, json={"refresh_token": "YOUR_REFRESH_TOKEN"},)
new_token = response.json()["access_token"]const response = await fetch( 'https://YOUR_PROJECT.supabase.co/auth/v1/token?grant_type=refresh_token', { method: 'POST', headers: { 'apikey': 'YOUR_SUPABASE_ANON_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh_token: 'YOUR_REFRESH_TOKEN', }), });
const { access_token: newToken } = await response.json();Public Endpoints
Section titled “Public Endpoints”These endpoints do not require authentication:
| Endpoint | Description |
|---|---|
GET /health | Health check and pack count |
All other endpoints require a valid Bearer token.
Test Your Setup
Section titled “Test Your Setup”Verify your authentication is working with these three steps.
Step 1: Check connectivity. Call the public health endpoint (no token required):
curl -s 'https://api.spatial.properties/health'response = requests.get("https://api.spatial.properties/health")print(response.json())const response = await fetch('https://api.spatial.properties/health');console.log(await response.json());Expected response:
{"status": "ok", "packs_loaded": 1}Step 2: Obtain a token. Use the sign-in call from Obtaining a Token above.
Step 3: Call an authenticated endpoint. Use your token to check your credit balance:
curl -s 'https://api.spatial.properties/billing/credits/balance' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'response = requests.get( "https://api.spatial.properties/billing/credits/balance", headers={"Authorization": f"Bearer {access_token}"},)print(response.json())const response = await fetch( 'https://api.spatial.properties/billing/credits/balance', { headers: { 'Authorization': `Bearer ${accessToken}` }, });console.log(await response.json());Expected success response:
{"balance": "50.00", "currency": "AUD"}If you receive a 401 error instead, check that:
- Your token has not expired (tokens last approximately 1 hour)
- The
Authorizationheader uses the formatBearer <token>with a space after “Bearer” - You are using the
access_tokenvalue, not therefresh_token