Authentication
FoxNose exposes two authentication options when calling the Management API: user-based JWT tokens and environment-scoped API keys. Each request must use exactly one of these methods.
Overview
| Method | Best for | Credentials Needed | Authorization Header |
|---|---|---|---|
| JWT (Bearer) | Human operators working in the dashboard or scripts acting as a specific user | Email + password to obtain {access, refresh} tokens | Authorization: Bearer <access_token> |
| API Key – Secure | Automated services requiring high assurance and replay protection | API key pair (public/private) generated in the dashboard | Authorization: Secure <public_key>:<signature> + Date header |
| API Key – Simple | Trusted internal tooling and staging environments where signing is not available | Same API key pair as Secure mode | Authorization: Simple <public_key>:<private_key> |
Need Flux API details? Refer to the dedicated Flux API authentication docs.
API keys are created in the FoxNose dashboard. Each key belongs to one environment and can optionally carry a role, so the key only has access to endpoints allowed by that role.
User Authentication (JWT)
JWT tokens are issued for human users after entering their email and password. Use them for day-to-day management actions where you want all requests to be performed on behalf of a user.
Token Lifetimes
- Name
access- Type
- string
- Description
Used as
Authorization: Bearer <token>in subsequent API calls. Access tokens expire after 24 hours.
- Name
refresh- Type
- string
- Description
Lets you obtain a new pair of tokens without entering credentials again. Refresh tokens are valid for 7 days and are rotated on every refresh call.
If a refresh token is reused after a successful refresh, the API responds with 401 Unauthorized because the old token has already been rotated out.
Obtain JWT Tokens
Submit credentials to receive the { access, refresh } pair.
Required attributes
- Name
email- Type
- string
- Required
- required
- Description
Registered email address.
- Name
password- Type
- string
- Required
- required
- Description
Account password.
Error codes
- Name
400- Description
The payload is missing required fields or has invalid data types.
- Name
401- Description
Email or password is incorrect, or the user is inactive.
Request
curl https://api.foxnose.net/account/auth/ \
-H "Content-Type: application/json" \
-d '{
"email": "your-email@example.com",
"password": "your-password"
}'
Response
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Refresh JWT Tokens
Exchange a refresh token for a brand-new { access, refresh } pair.
Required attributes
- Name
refresh- Type
- string
- Required
- required
- Description
Refresh token issued by the authentication endpoint or a previous refresh request.
Error codes
- Name
400- Description
Missing
refreshfield or invalid JSON payload.
- Name
401- Description
Refresh token expired, was already rotated, or belongs to a different user.
Request
curl https://api.foxnose.net/account/refresh-token/ \
-H "Content-Type: application/json" \
-d '{
"refresh": "your-refresh-token"
}'
Response
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Client Examples
import axios from 'axios';
const authClient = axios.create({
baseURL: 'https://api.foxnose.net',
headers: { 'Content-Type': 'application/json' },
});
async function login(email, password) {
const { data: tokens } = await authClient.post('/account/auth/', { email, password });
const api = axios.create({
baseURL: 'https://api.foxnose.net',
headers: { Authorization: `Bearer ${tokens.access}` },
});
const folders = await api.get('/v1/7c9h4pwu/folders/');
console.log(folders.data);
const { data: refreshed } = await authClient.post('/account/refresh-token/', {
refresh: tokens.refresh,
});
return refreshed;
}
API Key Authentication
API keys are ideal for backend services or integrations that need consistent access to a single environment. You can generate keys in the dashboard, download the Base64-encoded public/private pair, and optionally assign a role to restrict access.
There are two header formats you can choose from.
Secure Auth Method
Secure mode signs every request, preventing tampering and replay attacks. For each call:
- Build the signing string
data_to_sign = "<path>|<sha256_hex_of_body>|<timestamp>"- path – exact request path, e.g.,
/v1/7c9h4pwu/folders/ - body hash – SHA‑256 hex digest of the raw body (hash an empty string when there is no body)
- timestamp – ISO 8601 UTC string, e.g.,
2024-10-26T20:58:45Z
- path – exact request path, e.g.,
- Sign with your private key using ECDSA (curve
SECP256R1). Encode the signature in Base64. - Send headers
Authorization: Secure <public_key_base64>:<signature_base64>
Date: 2024-10-26T20:58:45Z
import base64
import hashlib
import json
from datetime import datetime, timezone
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
PUBLIC_KEY = "your-public-key"
PRIVATE_KEY = "your-private-key"
URI = "/v1/7c9h4pwu/folders/"
body = json.dumps({"name": "Docs Folder"})
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
body_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
data_to_sign = f"{URI}|{body_hash}|{timestamp}"
private_key = serialization.load_der_private_key(
base64.b64decode(PRIVATE_KEY),
password=None,
)
signature = private_key.sign(
data_to_sign.encode("utf-8"),
ec.ECDSA(hashes.SHA256()),
)
signature_b64 = base64.b64encode(signature).decode("utf-8")
headers = {
"Authorization": f"Secure {PUBLIC_KEY}:{signature_b64}",
"Date": timestamp,
"Content-Type": "application/json",
}
response = requests.post("https://api.foxnose.net" + URI, headers=headers, data=body)
response.raise_for_status()
print(response.json())
Notes
- The server compares the
Dateheader to current time and rejects requests that differ by more than 15 minutes. - Query parameters do not appear in the signing string, but they still affect the final API response.
- If the signature or timestamp check fails, the API responds with a standard
401 Unauthorizederror.
Simple Auth Method
Simple mode is useful for secure staging environments where signing infrastructure is unavailable. It sends both keys directly in the header:
Authorization: Simple <public_key_base64>:<private_key_base64>
No Date header or signature is required. Because the private key is transmitted on every request, restrict Simple mode to trusted networks and rotate keys after use.
import axios from 'axios';
const publicKey = 'YourPublicKeyBase64';
const privateKey = 'YourPrivateKeyBase64';
const uri = '/v1/7c9h4pwu/folders/';
await axios.post(
`https://api.foxnose.net${uri}`,
{ name: 'Docs Folder' },
{
headers: {
Authorization: `Simple ${publicKey}:${privateKey}`,
'Content-Type': 'application/json',
},
},
);
Security Considerations
- Treat private keys and refresh tokens as secrets. Rotate them regularly and revoke compromised credentials immediately.
- Keep client clocks in sync when using Secure mode. A drift larger than 15 minutes causes authentication failures.
- Always send requests over HTTPS, especially when using Simple mode.
- Assign the narrowest possible role when creating API keys so each key only has access to the required endpoints.
- Store refresh tokens securely server-side (for backend services) or in encrypted storage (for native apps); never embed them in public code.