Authentication
The GeoTareas API uses a dual-authentication model to secure every protected endpoint. Each request must include both a JWT token (representing user identity and permissions) and an API Key (representing your company integration). This layered approach ensures that both the user and the integration are independently validated on every call.
Authentication Flow
The following diagram illustrates the end-to-end authentication flow:
- Authenticate — Call
POST /apidev/v1/loginwith your email and password. - Receive a JWT — The server returns a signed token that expires 1 hour after issuance, by default.
- Attach credentials — Include the JWT, your API Key, and the
tenantheader on every subsequent request. - Handle expiration — When you receive a
401, discard the token and re-authenticate. There is no refresh token flow.
Every request — including login — must include the tenant header. The default tenant is geotareas.com; if your company was assigned a dedicated tenant, you must send that assigned value instead. A missing or wrong tenant is rejected with 401.
Login
| Method | POST |
| URL | /apidev/v1/login |
Headers
| Header | Value | Required |
|---|---|---|
tenant | Your assigned tenant domain (default: geotareas.com) — always send your assigned tenant | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The user's email address. |
password | string | Yes | The user's password. |
playerid | string | No | Push-notification player id to register for this user (max 200). |
Code Examples
- cURL
- JavaScript
- Python
- PHP
- C#
curl -X POST https://api.example.com/apidev/v1/login \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"email": "dev@company.com",
"password": "your_password"
}'
const TENANT = "geotareas.com";
const response = await fetch("https://api.example.com/apidev/v1/login", {
method: "POST",
headers: {
"tenant": TENANT,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "dev@company.com",
password: "your_password",
}),
});
const data = await response.json();
const token = data.data.authorization;
import requests
TENANT = "geotareas.com"
response = requests.post(
"https://api.example.com/apidev/v1/login",
headers={
"tenant": TENANT,
"Content-Type": "application/json",
},
json={
"email": "dev@company.com",
"password": "your_password",
},
)
data = response.json()
token = data["data"]["authorization"]
<?php
$tenant = "geotareas.com";
$ch = curl_init("https://api.example.com/apidev/v1/login");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"tenant: {$tenant}",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"email" => "dev@company.com",
"password" => "your_password",
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
$token = $data["data"]["authorization"];
using System.Net.Http;
using System.Text;
using System.Text.Json;
var tenant = "geotareas.com";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("tenant", tenant);
var payload = JsonSerializer.Serialize(new
{
email = "dev@company.com",
password = "your_password"
});
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.example.com/apidev/v1/login", content);
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var token = doc.RootElement
.GetProperty("data")
.GetProperty("authorization")
.GetString();
Success Response
{
"success": true,
"data": {
"authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"meta": {}
}
Error Responses
Invalid credentials
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid email or password."
}
}
Missing tenant header
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "The tenant header is required."
}
}
Expired token
A valid JWT that has passed its 1-hour lifetime returns TOKEN_EXPIRED (distinct from the UNAUTHORIZED code returned for wrong credentials):
{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "The token has expired. Log in again to obtain a new one."
}
}
Suspended company
If the company account is suspended by Logicsat (for example, for administrative reasons), the API returns 403 COMPANY_SUSPENDED on every endpoint, including login — no new tokens are issued and previously issued tokens stop working immediately. Integrations should treat this as a signal to stop retrying and contact Logicsat support:
{
"success": false,
"error": {
"code": "COMPANY_SUSPENDED",
"message": "The company account is suspended. Please contact Logicsat support."
}
}
Using the Token
Once authenticated, include these three headers on every API request:
| Header | Value | Description |
|---|---|---|
tenant | geotareas.com (default) | Your assigned tenant namespace — must be sent on every request. |
Authorization | Bearer eyJhbG... | The JWT obtained from login. |
X-API-Key | gtk_xxxxxxxxxxxx | Your company integration key. |
Example request:
curl -X GET "https://api.example.com/apidev/v1/fleet/devices?limit=25&offset=0" \
-H "tenant: $TENANT" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY"
Token Lifecycle
| Property | Detail |
|---|---|
| Time-to-live (TTL) | 1 hour from issuance. |
| Refresh mechanism | None. Re-authenticate by calling the login endpoint when the token expires. |
| Expiration signal | A 401 TOKEN_EXPIRED response indicates the token expired; a 401 UNAUTHORIZED indicates it is invalid. |
When you receive a 401 response, discard the current token and perform a fresh login to obtain a new one. There is no refresh token flow -- simply call /apidev/v1/login again.
API Key Validation
The API Key is provided to your organization during onboarding. On every request, the server validates the following properties of your key:
| Check | Description |
|---|---|
| Tenant match | The key must belong to the same tenant as the JWT. |
| Scope | The key must have a valid integration scope assigned during onboarding. |
| Status | The key must be in active status. |
| Time window | The current time must fall within the key's valid_from and valid_until range. |
If any of these checks fail, the server responds with 401 UNAUTHORIZED.
Security Best Practices
- Store tokens securely. Keep JWTs in memory or secure storage. Never persist them in local storage on web clients.
- Never expose API keys in client-side code. API keys should only be used in server-to-server communication or secure backend environments.
- Rotate API keys periodically. Contact your account manager to schedule key rotation and minimize the window of exposure.
- Always use HTTPS. All requests to the API must be made over TLS. Plain HTTP requests will be rejected.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 — "Invalid email or password" | Wrong credentials | Verify email and password. Passwords are case-sensitive. |
401 — "The tenant header is required" | Missing or empty tenant header | Add the tenant header with your assigned domain. |
401 after a period of working calls | JWT expired (1 h TTL) | Discard the token and call /apidev/v1/login again. |
401 — API Key rejected | Key expired, inactive, or wrong tenant | Verify the key status and validity window with your account manager. |
429 — Too Many Requests | Brute-force protection triggered (5 failed logins in 30 s) | Wait 60 seconds before retrying. Do not retry in a loop. |