Introduction
REST API v1 for the Bitaqa NFC business-card platform: provision cards, sync HR employees, read analytics and manage touchpoints and webhooks.
All endpoints live under `/api/v1` and are tenant-scoped to the organization that owns the API token
(super admin tokens select an organization with the `X-Organization: {org-slug}` header).
Each token carries a set of abilities; endpoints reject tokens missing the required ability with a
`403` response. Available abilities: `cards:read`, `cards:write`, `analytics:read`, `employees:write`,
`touchpoints:read`, `touchpoints:write`, `webhooks:manage`.
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_TOKEN}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Authenticate with a Sanctum personal access token sent as a bearer token: Authorization: Bearer {token}. An organization admin can issue tokens (with the abilities listed above) from the admin panel's API tokens screen. Requests without a valid token receive a 401; tokens missing the required ability receive a 403.
Analytics
Aggregated tap/click analytics. All endpoints accept an optional
from/to date range (defaults to the last 30 days, maximum span
366 days) and require the analytics:read token ability.
Card analytics
requires authentication
Summary, per-day timeseries and top link channels for one card.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/cards/k7m2p9qr/analytics?from=2026-06-01&to=2026-06-30" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/cards/k7m2p9qr/analytics"
);
const params = {
"from": "2026-06-01",
"to": "2026-06-30",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"from": "2026-06-01",
"to": "2026-06-30",
"summary": {
"views": 58,
"unique_visitors": 41,
"link_clicks": 23,
"vcard_downloads": 6,
"shares": 2,
"qr_scans": 4,
"journey_submits": 0
},
"timeseries": [
{
"date": "2026-06-01",
"views": 3,
"link_clicks": 1,
"vcard_downloads": 0
}
],
"channels": [
{
"channel": "whatsapp",
"count": 12
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Organization summary
requires authentication
Org-wide totals, top link channels and a per-branch breakdown.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/analytics/summary?from=2026-06-01&to=2026-06-30" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/analytics/summary"
);
const params = {
"from": "2026-06-01",
"to": "2026-06-30",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"from": "2026-06-01",
"to": "2026-06-30",
"summary": {
"views": 412,
"unique_visitors": 267,
"link_clicks": 158,
"vcard_downloads": 41,
"shares": 12,
"qr_scans": 33,
"journey_submits": 9
},
"channels": [
{
"channel": "whatsapp",
"count": 74
}
],
"by_branch": [
{
"branch_id": 1,
"name_en": "Ruwi",
"name_ar": "روي",
"views": 210,
"link_clicks": 80,
"vcard_downloads": 22
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Branches
List branches
requires authentication
Branch directory with coordinates, opening hours and services.
Requires the cards:read token ability.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/branches?search=ruwi&per_page=15" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"search\": \"b\"
}"
const url = new URL(
"http://bitaqa.test/api/v1/branches"
);
const params = {
"search": "ruwi",
"per_page": "15",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"search": "b"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Cards
Provision and manage the organization's digital business cards. Card endpoints are tenant-scoped: slugs belonging to another organization always return 404.
List cards
requires authentication
Requires the cards:read token ability.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/cards?status=active&branch_id=1&department_id=1&search=sara&per_page=15&page=1" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"status\": \"architecto\",
\"branch_id\": 16,
\"department_id\": 16,
\"search\": \"n\"
}"
const url = new URL(
"http://bitaqa.test/api/v1/cards"
);
const params = {
"status": "active",
"branch_id": "1",
"department_id": "1",
"search": "sara",
"per_page": "15",
"page": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"status": "architecto",
"branch_id": 16,
"department_id": 16,
"search": "n"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Provision a card
requires authentication
Creates a card for the given employee email — reusing the matching
organization user, or provisioning a new user (random password,
local source) when none exists. Requires the cards:write ability.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/cards" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"email\": \"sara@alameen.test\",
\"display_name_en\": \"Sara Al Busaidi\",
\"display_name_ar\": \"سارة البوسعيدية\",
\"title_en\": \"Relationship Manager\",
\"title_ar\": \"مدير علاقات العملاء\",
\"phone\": \"+96891234567\",
\"whatsapp\": \"+96891234567\",
\"website\": \"https:\\/\\/alameen.test\",
\"branch_id\": 1,
\"department_id\": 1,
\"template_key\": \"classic\",
\"status\": \"pending\",
\"links\": [
{
\"type\": \"custom\",
\"label_en\": \"b\",
\"label_ar\": \"n\",
\"value\": \"g\"
}
]
}"
const url = new URL(
"http://bitaqa.test/api/v1/cards"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "sara@alameen.test",
"display_name_en": "Sara Al Busaidi",
"display_name_ar": "سارة البوسعيدية",
"title_en": "Relationship Manager",
"title_ar": "مدير علاقات العملاء",
"phone": "+96891234567",
"whatsapp": "+96891234567",
"website": "https:\/\/alameen.test",
"branch_id": 1,
"department_id": 1,
"template_key": "classic",
"status": "pending",
"links": [
{
"type": "custom",
"label_en": "b",
"label_ar": "n",
"value": "g"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"slug": "k7m2p9qr",
"status": "pending",
"display_name_en": "Sara Al Busaidi",
"links": [],
"public_url": "http://bitaqa.test/alameen/k7m2p9qr"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a card
requires authentication
Requires the cards:read token ability.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/cards/k7m2p9qr" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/cards/k7m2p9qr"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a card
requires authentication
Partial update. When links is present the existing links are
replaced with the given set. Requires the cards:write ability.
Example request:
curl --request PATCH \
"http://bitaqa.test/api/v1/cards/k7m2p9qr" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"display_name_en\": \"Sara Al Busaidi\",
\"display_name_ar\": \"b\",
\"title_en\": \"n\",
\"title_ar\": \"g\",
\"bio_en\": \"z\",
\"bio_ar\": \"m\",
\"email\": \"gulgowski.asia@example.com\",
\"phone\": \"j\",
\"whatsapp\": \"n\",
\"website\": \"i\",
\"branch_id\": 16,
\"department_id\": 16,
\"template_key\": \"modern\",
\"links\": [
{
\"type\": \"linkedin\",
\"label_en\": \"n\",
\"label_ar\": \"g\",
\"value\": \"z\"
}
]
}"
const url = new URL(
"http://bitaqa.test/api/v1/cards/k7m2p9qr"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"display_name_en": "Sara Al Busaidi",
"display_name_ar": "b",
"title_en": "n",
"title_ar": "g",
"bio_en": "z",
"bio_ar": "m",
"email": "gulgowski.asia@example.com",
"phone": "j",
"whatsapp": "n",
"website": "i",
"branch_id": 16,
"department_id": 16,
"template_key": "modern",
"links": [
{
"type": "linkedin",
"label_en": "n",
"label_ar": "g",
"value": "z"
}
]
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Deactivate a card
requires authentication
Instant status flip (no approval step) — the public page stops
serving on the very next tap. Requires the cards:write ability.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/cards/k7m2p9qr/deactivate" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/cards/k7m2p9qr/deactivate"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Activate a card
requires authentication
Instant status flip to active. Requires the cards:write ability.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/cards/k7m2p9qr/activate" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/cards/k7m2p9qr/activate"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Employees
Push HR directory changes into Bitaqa.
Sync employees
requires authentication
Batch upsert (max 500 rows) matched by employee_ref, falling back
to email. New employees are created with an auto-provisioned
PENDING card. Rows with active: false deactivate the user and flip
their cards to inactive immediately; re-activated users keep their
cards inactive until re-approved. Requires the employees:write
token ability.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/employees/sync" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
const url = new URL(
"http://bitaqa.test/api/v1/employees/sync"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200):
{
"created": 2,
"updated": 1,
"deactivated": 1,
"reactivated": 0,
"failures": [
{
"index": 4,
"errors": {
"email": [
"The email field is required."
]
}
}
],
"note": "Re-activated employees keep their cards inactive until re-approved."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Touchpoints
Physical NFC/QR touchpoints (branch counters, marketing material) that open a journey when tapped.
List touchpoints
requires authentication
Requires the touchpoints:read token ability.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/touchpoints?type=branch_counter&journey=feedback&branch_id=1&search=lobby&per_page=15" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"type\": \"architecto\",
\"journey\": \"architecto\",
\"branch_id\": 16,
\"search\": \"n\"
}"
const url = new URL(
"http://bitaqa.test/api/v1/touchpoints"
);
const params = {
"type": "branch_counter",
"journey": "feedback",
"branch_id": "1",
"search": "lobby",
"per_page": "15",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "architecto",
"journey": "architecto",
"branch_id": 16,
"search": "n"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a touchpoint
requires authentication
The public slug is generated server-side. Requires the
touchpoints:write token ability.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/touchpoints" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"type\": \"branch_counter\",
\"journey\": \"feedback\",
\"name\": \"Qurum branch counter\",
\"branch_id\": 1,
\"is_active\": true
}"
const url = new URL(
"http://bitaqa.test/api/v1/touchpoints"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "branch_counter",
"journey": "feedback",
"name": "Qurum branch counter",
"branch_id": 1,
"is_active": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"slug": "w3n8k2mq",
"type": "branch_counter",
"journey": "feedback",
"name": "Qurum branch counter",
"branch_id": 1,
"is_active": true,
"public_url": "http://bitaqa.test/alameen/w3n8k2mq"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a touchpoint
requires authentication
Requires the touchpoints:write token ability.
Example request:
curl --request PATCH \
"http://bitaqa.test/api/v1/touchpoints/w3n8k2mq" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"type\": \"branch_counter\",
\"journey\": \"feedback\",
\"name\": \"Lobby stand — renamed\",
\"branch_id\": 16,
\"is_active\": false
}"
const url = new URL(
"http://bitaqa.test/api/v1/touchpoints/w3n8k2mq"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "branch_counter",
"journey": "feedback",
"name": "Lobby stand — renamed",
"branch_id": 16,
"is_active": false
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a touchpoint
requires authentication
Requires the touchpoints:write token ability.
Example request:
curl --request DELETE \
"http://bitaqa.test/api/v1/touchpoints/w3n8k2mq" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/touchpoints/w3n8k2mq"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Webhooks
Webhook endpoints receiving signed event deliveries
(X-Bitaqa-Signature: t=<ts>,v1=hmac_sha256(secret, ts.'.'.body)).
All endpoints require the webhooks:manage token ability.
List webhook endpoints
requires authentication
Secrets are masked — they are only revealed once, on creation.
Example request:
curl --request GET \
--get "http://bitaqa.test/api/v1/webhooks" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/webhooks"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a webhook endpoint
requires authentication
The signing secret (provided or generated) is returned ONCE in this
response as secret — store it now; it is stored encrypted and
never shown again.
Example request:
curl --request POST \
"http://bitaqa.test/api/v1/webhooks" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/example.com\\/hooks\\/bitaqa\",
\"events\": [
\"appointment.created\"
],
\"secret\": \"b\"
}"
const url = new URL(
"http://bitaqa.test/api/v1/webhooks"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com\/hooks\/bitaqa",
"events": [
"appointment.created"
],
"secret": "b"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"id": 7,
"url": "https://example.com/hooks/bitaqa",
"events": [
"card.created"
],
"secret": "********a1b2",
"is_active": true
},
"secret": "whsec_9fK2..."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a webhook endpoint
requires authentication
Example request:
curl --request DELETE \
"http://bitaqa.test/api/v1/webhooks/7" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://bitaqa.test/api/v1/webhooks/7"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.