Overview
The Garden.gg API supports three authentication methods:
- JWT Tokens — Short-lived access tokens obtained via email/password login, ideal for user-facing applications.
- API Keys — Long-lived keys generated in your account settings, ideal for server-to-server integrations and scripts.
- Device Tokens — Short opaque strings baked into the URL path, for firmware-constrained IoT devices (Ecowitt gateways etc.) that can’t configure headers or query parameters.
JWT + API Keys use the Authorization header with a Bearer token:
Authorization: Bearer <token>
API Keys can alternatively be passed via the X-API-Key header or ?api_key= query parameter.
JWT Authentication
JWT authentication uses an access token (15-minute expiry) and a refresh token (7-day expiry). The access token is used for API requests, and the refresh token is used to obtain a new access token when it expires.
Register
Create a new account.
POST /auth/register
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Valid email address |
password | string | Yes | Minimum 8 characters |
username | string | Yes | 3-30 chars, alphanumeric + underscore |
display_name | string | No | Display name for profile |
Request
{
"email": "[email protected]",
"password": "s3cur3p4ssw0rd",
"username": "green_thumb",
"display_name": "Green Thumb"
}
Response 201 Created
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"user": {
"id": "usr_a1b2c3d4",
"email": "[email protected]",
"username": "green_thumb",
"display_name": "Green Thumb"
}
}
Login
Authenticate with email and password to receive tokens.
POST /auth/login
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Account email |
password | string | Yes | Account password |
Request
{
"email": "[email protected]",
"password": "s3cur3p4ssw0rd"
}
Response 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}
Login Examples
cURL
curl -X POST "https://garden.gg/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "s3cur3p4ssw0rd"
}'
Python
import requests
response = requests.post(
"https://garden.gg/api/v1/auth/login",
json={
"email": "[email protected]",
"password": "s3cur3p4ssw0rd",
},
)
tokens = response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
# Use the access token for subsequent requests
headers = {"Authorization": f"Bearer {access_token}"}
gardens = requests.get("https://garden.gg/api/v1/gardens", headers=headers)
print(gardens.json())
Node.js
// Login and get tokens
const loginResponse = await fetch("https://garden.gg/api/v1/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "[email protected]",
password: "s3cur3p4ssw0rd",
}),
});
const { access_token, refresh_token } = await loginResponse.json();
// Use the access token for subsequent requests
const gardensResponse = await fetch("https://garden.gg/api/v1/gardens", {
headers: { Authorization: `Bearer ${access_token}` },
});
const gardens = await gardensResponse.json();
console.log(gardens);
Refresh Token
Exchange a valid refresh token for a new access token. Use this when your access token expires (after 15 minutes) without requiring the user to log in again.
POST /auth/refresh
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
refresh_token | string | Yes | Valid refresh token |
Request
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
Response 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}
Both the access token and refresh token are rotated on each refresh. The old refresh token is invalidated immediately.
Token Expiry Summary
| Token | Lifetime | Refresh Strategy |
|---|---|---|
| Access Token | 15 minutes | Use refresh token to get a new one |
| Refresh Token | 7 days | Re-authenticate with email/password |
API Key Authentication
API keys provide a simpler authentication method for scripts, CI/CD pipelines, and server-side integrations. They do not expire but can be revoked at any time.
Creating API Keys
Generate API keys from your account settings or via the API:
POST /account/api-keys
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A label for this key (e.g., “CI”) |
Response 201 Created
{
"id": "key_x9y8z7",
"name": "CI",
"key": "gg_live_abc123def456ghi789jkl012mno345",
"created_at": "2026-03-15T10:00:00Z"
}
The full key value is only returned once at creation time. Store it securely.
Using API Keys
Pass the API key as a Bearer token, the same way you would use a JWT access token:
curl -X GET "https://garden.gg/api/v1/gardens" \
-H "Authorization: Bearer gg_live_abc123def456ghi789jkl012mno345"
All API keys are prefixed with gg_live_ so they can be easily identified in logs and secret scanners.
Managing API Keys
GET /account/api-keys — List all API keys
DELETE /account/api-keys/:id — Revoke an API key
See the Users reference for full details.
Best Practices
- Never expose tokens in client-side code. Store JWT tokens in secure, HTTP-only cookies or secure storage on mobile devices. Never include them in URLs or local storage on web.
- Use the refresh flow. Do not store passwords to re-authenticate. Implement proper token refresh logic in your application.
- Rotate API keys regularly. Create a new key, update your integrations, then revoke the old key.
- Use API keys for automation. For CI/CD, scripts, and server-to-server calls, API keys are simpler and avoid the overhead of the JWT refresh flow.
- Monitor key usage. Review your active API keys periodically and revoke any that are no longer in use.
- Scope keys to purpose. Create separate API keys for different integrations so you can revoke one without affecting others.
Error Responses
| Scenario | HTTP Status | Error Code |
|---|---|---|
| Missing Authorization header | 401 | UNAUTHORIZED |
| Invalid or expired token | 401 | UNAUTHORIZED |
| Invalid credentials (login) | 401 | UNAUTHORIZED |
| Expired refresh token | 401 | UNAUTHORIZED |
| Account suspended | 403 | FORBIDDEN |
Device Tokens
Device tokens are short opaque strings (ggd_ + 12 base62 chars, 16 chars total) that authenticate a single IoT device. They’re generated when you connect a device from Profile → Sensor Devices in the web app.
Unlike JWT tokens or API keys, device tokens live in the URL path, not in a header or query string:
POST https://garden.gg/i/e/ggd_AbCdEf123456
Content-Type: application/x-www-form-urlencoded
tempf=68.1&humidity=55&tempf1=72.2&humidity1=62&soilmoisture1=37
This shape exists because Ecowitt gateways (and similar firmware) can only configure a single URL field — no custom headers, no extra query params. A device token keeps the full URL under 40 characters so it fits every gateway.
Scope. A device token can only drive the one device it’s bound to. It can’t read data, can’t list plots, and can’t be used with any endpoint other than the ingest path (POST /i/e/:token). The device’s channel map controls which plots the ingested data lands on.
Rotation. Revoke the device (DELETE /api/v1/devices/:id) and create a new one. There’s no in-place rotation.
See IoT & Devices for the full ingest reference.