Operation tokens for your mobile app
Let users of your own app open and close products over Bluetooth, even where the site has no network. Your backend creates a short-lived operation token for one product; your app hands it to the product through the Parklio SDK. This guide covers the backend side in full, and the app side as a flow.
Prerequisites
- An API token, on your backend only; see Authentication. Never ship it in the app.
- Products paired with your account. A Gateway isn't needed for this path.
- Access to the Parklio SDK for your app (Android, iOS): it's shared under contract. Contact info@parklio.com. See SDK.
The flow
- A user asks your app for access to a product, for example when their booking starts.
- Your app asks your backend, which checks the user is allowed.
- Your backend creates an operation token for that product and a short window, and returns it to the app.
- The app, through the SDK, sends the token's open or close command to the product over Bluetooth. The product checks the signature and the window itself; no network is needed at the product.
- When the window ends, the token stops working.
1. Create a token, just in time
Call POST /v2/operation-tokens with the
product and a window. Times are ISO-8601 in UTC.
- curl
- Node.js
- Python
curl -s -X POST https://api.parklio.com/v2/operation-tokens \
-H "Authorization: Bearer $PARKLIO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_id": 123,
"start_time": "2026-03-02T08:00:00.000Z",
"end_time": "2026-03-02T10:00:00.000Z"
}'
export async function createOperationToken(productId: number, minutes: number) {
const start = new Date();
const end = new Date(start.getTime() + minutes * 60_000);
const res = await fetch('https://api.parklio.com/v2/operation-tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PARKLIO_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_id: productId,
start_time: start.toISOString(),
end_time: end.toISOString(),
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.errors[0].code}: ${body.errors[0].message}`);
return body.data;
}
import os
from datetime import datetime, timedelta, timezone
import httpx
def create_operation_token(product_id, minutes):
start = datetime.now(timezone.utc)
end = start + timedelta(minutes=minutes)
body = httpx.post(
"https://api.parklio.com/v2/operation-tokens",
headers={"Authorization": f"Bearer {os.environ['PARKLIO_API_TOKEN']}"},
json={
"product_id": product_id,
"start_time": start.isoformat(),
"end_time": end.isoformat(),
},
).json()
if not body["success"]:
error = body["errors"][0]
raise RuntimeError(f"{error['code']}: {error['message']}")
return body["data"]
The response (HTTP 200):
{
"success": true,
"data": {
"id": 9001,
"product_id": 123,
"start_time": "2026-03-02T08:00:00.000Z",
"end_time": "2026-03-02T10:00:00.000Z",
"status": "active",
"used_count": 0,
"is_unlimited": 0,
"token_open": { "token": "<hex>", "status_key": "<hex>", "challenge_key": "<hex>" },
"token_close": { "token": "<hex>", "status_key": "<hex>", "challenge_key": "<hex>" },
"token_status": { "token": "<hex>", "status_key": "<hex>", "challenge_key": "<hex>" },
"product": { "id": 123, "device_id": "B1234567" },
"created_at": "2026-03-02T07:59:58.000Z",
"updated_at": "2026-03-02T07:59:58.000Z"
}
}
Return token_open, token_close and token_status, unchanged, plus the product's
device_id and end_time, to your app. The SDK documentation explains how the app uses
them.
2. Keep windows short
Revoking a token doesn't reach the device. The window is signed into the token, and
the product checks it on its own. Blocking a token
(PATCH /v2/operation-tokens/{id} with
{"status": "blocked"}) or deleting it only changes Parklio's records; a copy the app
already holds keeps working until end_time.
So the window is your revocation:
- Create tokens when the user needs access, not in advance.
- Make the window as short as your use case allows: the length of a booking, or a few minutes for a single "open now".
- Create a new token to extend access; a token can't be changed.
- Don't use
"is_unlimited": true. It setsend_timeto 2038-01-01, and such a token can never be revoked on the device. start_timemay be in the past;end_timemust be in the future.
3. Store nothing long-lived
- Don't keep tokens in your database once delivered, and don't let the app cache them
beyond
end_time. Create a new one each time. - Keep the API token on your backend. The app should only ever see operation tokens.
- Use HTTPS between your app and backend, and authenticate the user before you create a token for them: whoever holds a token can operate the product until it expires.
4. Check the device clock
Time windows need the product's clock to be set. If a product reports
product_error.rtcc_invalid = 1 (see Monitor devices),
its clock isn't set and time-limited tokens fail until it is. Parklio sets the clock
when a Gateway sees the product; on a site with no Gateway, contact
support.
What the API tracks, and what it doesn't
statusbecomesexpired(checked hourly) afterend_time,blockedafter you block it, andinvalidwhen the product is removed from its lot or paired again.used_countdoesn't count uses over Bluetooth: the product doesn't report them to Parklio. Log opens in your app if you need them.GET /v2/operation-tokenslists tokens, filterable byproduct_idandstatus.- Operation tokens need no plan feature and no Gateway.
Verify it worked
POST /v2/operation-tokensreturnsstatus: "active"and the three commands.- Your app, near the product, sends
token_open: the product opens (on a barrier, the arm lowers). - After
end_time, the same command is refused by the product.
Common errors
| HTTP | Code | Cause | Fix |
|---|---|---|---|
| 403 | 402 | Neither end_time nor is_unlimited. | Send an end_time. |
| 403 | 403 | end_time is in the past. | Use a future end_time. |
| 403 | 405 | end_time is before start_time. | Fix the window. |
| 403 | 404 | A time after 2038. | Use an earlier time. |
| 403 | 206 | The product isn't paired. | Pair it with the Connect app. |
| 404 | 6 | No such product, or your token can't see it. | Check the id and the product's lot. |
| — | — | The product refuses the token. | Check the window, the device clock (rtcc_invalid), and that the product wasn't paired again since the token was created. |
Next
- SDK
- Access and keys: the other ways to share access.