Authentication
Authenticate with the Novacore API using challenge/verify signing with Ed25519 or ES256 keys.
Novacore uses a challenge/verify authentication flow. You request a challenge, sign it with your private key, and exchange the signature for a JWT token.
Identity Types
An identity in NovaCore represents who something is — a person, a gateway, a device, or a service. Identities are algorithm-agnostic: the authentication algorithm (Ed25519, ES256, etc.) is determined by the identity's auth methods, not its type. An identity can have multiple auth methods simultaneously.
Identities receive opaque IDs (idt-{uuid}) so the identity type cannot be inferred from the ID alone.
| Identity Type | Description |
|---|---|
user | Human user, access scoped to organization membership and role, typically authenticated via Privy |
gateway | ZAP gateway device, proxies telemetry for connected hardware |
device | Smart device with direct MQTT connectivity (e.g. wallbox) |
integration | Third-party service integration |
developer | Dev/test service |
Authentication Flow
1. Request a Challenge
POST https://novacore-mainnet.sourceful.dev/auth/challenge
Content-Type: application/json
{
"public_key": "your-public-key"
}
Response:
{
"challenge": "random-challenge-string",
"expires_at": "2026-03-06T13:00:00Z"
}
2. Sign the Challenge
Sign the challenge string with your private key using the appropriate algorithm (Ed25519 for humans, ES256 for machines).
3. Verify and Get Token
POST https://novacore-mainnet.sourceful.dev/auth/verify
Content-Type: application/json
{
"public_key": "your-public-key",
"signature": "base64-encoded-signature",
"challenge": "the-challenge-string"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"identity_id": "usr-abc123"
}
4. Use the Token
Include the JWT in all subsequent API requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
JWT Structure
Tokens are signed with HS256 and contain:
{
"public_key": "<auth method public key>",
"identity_type": "user",
"identity_id": "usr-abc123",
"auth_method_id": "<uuid>",
"auth_method_type": "ed25519",
"iss": "srcful-api",
"exp": 1709740800
}
Tokens expire after 24 hours. When a token expires, repeat the challenge/verify flow to obtain a new one.
Code Examples
Python (Ed25519)
import requests
from nacl.signing import SigningKey
import base64
# Generate or load your Ed25519 key pair
signing_key = SigningKey.generate()
public_key = base64.b64encode(
signing_key.verify_key.encode()
).decode()
BASE_URL = "https://novacore-mainnet.sourceful.dev"
# Step 1: Request challenge
resp = requests.post(f"{BASE_URL}/auth/challenge", json={
"public_key": public_key
})
challenge = resp.json()["challenge"]
# Step 2: Sign the challenge
signature = base64.b64encode(
signing_key.sign(challenge.encode()).signature
).decode()
# Step 3: Verify and get token
resp = requests.post(f"{BASE_URL}/auth/verify", json={
"public_key": public_key,
"signature": signature,
"challenge": challenge,
})
token = resp.json()["token"]
# Step 4: Use the token
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(f"{BASE_URL}/organizations", headers=headers)
print(resp.json())
JavaScript/TypeScript (Ed25519)
import * as ed from "@noble/ed25519";
const BASE_URL = "https://novacore-mainnet.sourceful.dev";
// Generate or load your Ed25519 key pair
const privateKey = ed.utils.randomPrivateKey();
const publicKey = Buffer.from(
await ed.getPublicKeyAsync(privateKey)
).toString("base64");
// Step 1: Request challenge
const challengeResp = await fetch(`${BASE_URL}/auth/challenge`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ public_key: publicKey }),
});
const { challenge } = await challengeResp.json();
// Step 2: Sign the challenge
const signature = Buffer.from(
await ed.signAsync(
new TextEncoder().encode(challenge),
privateKey
)
).toString("base64");
// Step 3: Verify and get token
const verifyResp = await fetch(`${BASE_URL}/auth/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ public_key: publicKey, signature, challenge }),
});
const { token } = await verifyResp.json();
// Step 4: Use the token
const orgs = await fetch(`${BASE_URL}/organizations`, {
headers: { Authorization: `Bearer ${token}` },
});
console.log(await orgs.json());
Knowledge gap for Johan: The exact request/response schemas for /auth/challenge and /auth/verify need verification. The examples above are based on the Novacore auth documentation but field names and formats may differ slightly in practice. Please verify and update.
Identity Registration
New identities may need to be registered before they can authenticate:
POST https://novacore-mainnet.sourceful.dev/identity/register
Content-Type: application/json
{
"public_key": "your-public-key"
}
Knowledge gap for Johan: Is identity registration required before first auth, or does /auth/verify auto-register new public keys? What fields does the registration endpoint require?
Security Considerations
- 24-hour token expiry: Tokens have a limited lifetime to reduce attack surface
- Private key separation: Private keys never leave your application
- No token refresh: When expired, re-authenticate with a new challenge/verify cycle
- Transport security: Always use HTTPS
Related Documentation
- Environments - Testnet vs Mainnet base URLs
- API Overview - Using tokens with the REST API
- Auth & Identities API - Full endpoint reference