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 valid for 1 hour.
- 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.
Login
| Method | POST |
| URL | /apidev/v1/login |
Headers
| Header | Value | Required |
|---|---|---|
tenant | Your tenant domain (e.g. geotareas.com) | 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. |
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": "BAD_REQUEST",
"message": "The tenant header is required."
}
}
Using the Token
Once authenticated, include these three headers on every API request:
| Header | Value | Description |
|---|---|---|
tenant | geotareas.com | Identifies the tenant namespace. |
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 UNAUTHORIZED response indicates the token has expired or 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. |