License Management
Two paths. Most backend integrations should use the API-key path under
/v1/products/:productId/...— see the Backend Quickstart and API Reference → Management Endpoints. It's simpler (no login, no refresh) and uses scoped API keys.This page documents the JWT-based dashboard path (
/v1/dashboard/orgs/:orgId/...) which is what the GeckoGuard web UI uses. The two paths share the same business logic — pick whichever fits your client. JWT is the right choice if your backend impersonates a real human user (e.g. an internal admin tool tied to SSO); API keys are the right choice for everything else.
Manage licenses programmatically through the dashboard API. All endpoints below live under /v1/dashboard/* and require a JWT access token in Authorization: Bearer … — API keys are not accepted here. Get an access token from POST /v1/auth/login. See the Authentication guide for details.
30-second curl quickstart
The full integrator path, end-to-end. Set these once:
export GG=https://api.geckoguard.net
export EMAIL=you@example.com
export PASSWORD='your-password'
1. Log in and grab an access token.
ACCESS_TOKEN=$(curl -sX POST "$GG/v1/auth/login" \
-H 'Content-Type: application/json' \
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" \
| jq -r '.data.tokens.accessToken')
2. Find your org UUID.
ORG_ID=$(curl -s "$GG/v1/dashboard/orgs" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq -r '.data.orgs[0].id')
echo "$ORG_ID"
3. Find a product UUID.
PRODUCT_ID=$(curl -s "$GG/v1/dashboard/orgs/$ORG_ID/products" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq -r '.data.products[0].id')
echo "$PRODUCT_ID"
4. List existing licenses.
curl -s "$GG/v1/dashboard/orgs/$ORG_ID/licenses?page=1&pageSize=20" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq
5. Create a license.
curl -sX POST "$GG/v1/dashboard/orgs/$ORG_ID/licenses" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: create-$(date +%s)-$RANDOM" \
-d "{\"productId\":\"$PRODUCT_ID\",\"expirationMode\":\"afterActivation\",\"expiresAfterDays\":30}" \
| jq
The Idempotency-Key header is optional but strongly recommended on POST — it lets you safely retry a flaky network without creating duplicates.
6. Revoke a license.
LICENSE_ID="<id from step 5>"
curl -sX POST "$GG/v1/dashboard/licenses/$LICENSE_ID/revoke" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq
7. Add a HWID or IP to the product blacklist.
curl -sX POST "$GG/v1/dashboard/blacklists/products/$PRODUCT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"HWID","value":"abc123…"}' | jq
Sanity check. If any of the above returns 401 UNAUTHORIZED, your access token is missing, expired, or you're hitting the wrong base URL. Refresh it with POST /v1/auth/refresh. If it returns 403 FORBIDDEN, your user doesn't have access to that org/product — check GET /v1/dashboard/me.
Idempotency on writes
Every POST / PATCH / DELETE endpoint on this page accepts an Idempotency-Key header. Without it, a network timeout that retries can create duplicate licenses. With it, the server caches the first successful response for 24 hours and replays it on retry. Use this on every mutation:
Idempotency-Key: <unique key, [A-Za-z0-9_-]{8,128}>
Replays return the cached response with Idempotent-Replayed: true. Reusing the same key with a different body returns 409 IDEMPOTENCY_KEY_REUSE. See Authentication → Idempotency.
Endpoints where this matters most: license create (single + bulk), bulk freeze/unfreeze/revoke/extend/delete, and blacklist add/bulk-add.
Create Licenses
POST /v1/dashboard/orgs/:orgId/licenses
Create one or more licenses for a product. Supports bulk creation up to 500 at a time. Send an Idempotency-Key header — without it, retrying on a timeout will create duplicates.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
productId | string (UUID) | Yes | Product to create licenses for |
count | number | No | Number of licenses to create (1–500, default 1) |
key | string | No | Custom license key (only when count is 1) |
expirationMode | string | No | "never", "fixed", "afterActivation", or "both" |
expiresAt | string (ISO 8601) | No | Fixed expiration date (when mode is fixed or both) |
expiresAfterDays | number | No | Days after first activation (when mode is afterActivation or both) |
policyOverride | object | No | Override default product policy for this license |
metadata | object | No | Custom key-value metadata |
Example: Single License
const res = await fetch(`https://api.geckoguard.net/v1/dashboard/orgs/${orgId}/licenses`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId: 'abc-123',
expirationMode: 'afterActivation',
expiresAfterDays: 30,
}),
});
Response (201)
{
"ok": true,
"data": {
"licenses": [
{
"id": "uuid-here",
"key": "GK-A1B2-C3D4-E5F6",
"productId": "abc-123",
"status": "ACTIVE",
"expiresAt": null,
"expiresAfterDays": 30,
"activatedAt": null,
"createdAt": "2025-01-15T10:30:00.000Z"
}
]
}
}
Example: Bulk Create
body: JSON.stringify({
productId: 'abc-123',
count: 100,
expirationMode: 'fixed',
expiresAt: '2025-12-31T23:59:59.000Z',
})
List Licenses
GET /v1/dashboard/orgs/:orgId/licenses
Query Parameters
| Param | Type | Description |
|---|---|---|
page | number | Page number (default 1) |
pageSize | number | Items per page (default 50, max 200) |
productId | string | Filter by product |
status | string | Filter by status: ACTIVE, EXPIRED, REVOKED, FROZEN |
search | string | Search by license key |
Response (200)
{
"ok": true,
"data": {
"licenses": [ /* array of license objects */ ],
"pagination": {
"page": 1,
"pageSize": 50,
"total": 342,
"totalPages": 7
}
}
}
Get License
GET /v1/dashboard/licenses/:id
Returns full license details including bindings, sessions, and metadata.
Update License
PATCH /v1/dashboard/licenses/:id
Request Body
| Field | Type | Description |
|---|---|---|
status | string | Change status (ACTIVE, FROZEN) |
expiresAt | string | Update fixed expiration |
expiresAfterDays | number | Update relative expiration |
policyOverride | object | Update per-license policy |
metadata | object | Update custom metadata |
Revoke / Restore
POST /v1/dashboard/licenses/:id/revoke
POST /v1/dashboard/licenses/:id/unrevoke
Revoking a license immediately prevents all future authorizations. Restoring sets it back to ACTIVE.
Reset Bindings
POST /v1/dashboard/licenses/:id/reset-hwid
POST /v1/dashboard/licenses/:id/reset-ip
Clears all HWID or IP bindings, allowing the license to bind to new devices/IPs.
Bulk Operations
All bulk endpoints accept an array of license IDs in the request body. Send an Idempotency-Key on every bulk call — bulk freeze/revoke at scale is exactly the operation you don't want to half-apply on a network blip.
| Endpoint | Description |
|---|---|
POST /v1/dashboard/orgs/:orgId/licenses/freeze | Freeze multiple licenses |
POST /v1/dashboard/orgs/:orgId/licenses/unfreeze | Unfreeze multiple licenses |
POST /v1/dashboard/orgs/:orgId/licenses/revoke | Revoke multiple licenses |
POST /v1/dashboard/orgs/:orgId/licenses/unrevoke | Restore multiple licenses |
POST /v1/dashboard/orgs/:orgId/licenses/extend | Extend expiry for multiple licenses |
DELETE /v1/dashboard/orgs/:orgId/licenses | Delete multiple licenses |
Example: Bulk Extend
await fetch(`https://api.geckoguard.net/v1/dashboard/orgs/${orgId}/licenses/extend`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
licenseIds: ['id-1', 'id-2', 'id-3'],
days: 30,
}),
});
Export / Import
Export
GET /v1/dashboard/orgs/:orgId/licenses/export?productId=abc&format=csv
Formats: csv, json
Import
POST /v1/dashboard/orgs/:orgId/licenses/import
Accepts a JSON array of license objects with custom keys.
Analytics
GET /v1/dashboard/orgs/:orgId/licenses/analytics
Returns license counts grouped by status and product.
{
"ok": true,
"data": {
"total": 1500,
"byStatus": {
"ACTIVE": 1200,
"EXPIRED": 250,
"REVOKED": 30,
"FROZEN": 20
},
"byProduct": [
{ "productId": "abc", "productName": "My App", "count": 800 },
{ "productId": "def", "productName": "My Tool", "count": 700 }
]
}
}