Skip to content

Authentication

Certivu uses two separate credential types that serve different purposes:

CredentialUsed forHow to get it
Session JWTDashboard accessReturned on POST /v1/auth/login
API key (ctv_key_…)SDK & programmatic signingCreated in the dashboard → Generators page

These are intentionally separate. Your API key is a long-lived secret for server-side use. Session tokens are short-lived (24h) browser credentials.


Terminal window
curl -X POST https://api.certivu.ai/v1/auth/register \
-H "Content-Type: application/json" \
-d '{ "org_name": "Acme AI", "email": "[email protected]", "password": "••••••••" }'
{
"user": {
"user_id": "usr_abc123",
"email": "[email protected]",
"email_verified": false,
"org_id": "org_xyz789"
}
}

A verification email is sent immediately. Click the link before signing content — the free tier works without verification but all features require it.


The email contains a link to:

https://certivu.ai/verify-email?token=<token>

Or confirm via API:

Terminal window
curl -X POST https://api.certivu.ai/v1/auth/verify-email \
-H "Content-Type: application/json" \
-d '{ "token": "<token-from-email>" }'

Terminal window
curl -X POST https://api.certivu.ai/v1/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "password": "••••••••" }'

Without 2FA — returns a session token immediately:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": { "user_id": "usr_abc123", "email": "[email protected]", "org_id": "org_xyz789" }
}

With 2FA enabled — returns a challenge token instead:

{ "requires_2fa": true, "challenge_token": "ch_7f3kx9mq..." }

Then submit the OTP from your authenticator app:

Terminal window
curl -X POST https://api.certivu.ai/v1/auth/verify-2fa \
-H "Content-Type: application/json" \
-d '{ "challenge_token": "ch_7f3kx9mq...", "otp": "123456" }'
# → { "token": "eyJ...", "user": { ... } }

Challenge tokens are single-use and burned on first submission.

The JWT is valid for 24 hours. Use it as a Bearer token for dashboard API calls:

Terminal window
curl https://api.certivu.ai/v1/auth/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Your ctv_key_… org API key is shown in the dashboard under Settings → API Keys. Store it securely — it is shown once and cannot be recovered.

Generators support two keypair modes. Choose one when registering a generator:

Certivu generates and stores the ML-DSA keypair. You don’t handle private key material. Use POST /v1/sign to sign content — upload the file, get it back watermarked and signed.

In the dashboard: Generators → Add generator → leave the public key field blank. The server keypair is created automatically.

You generate the ML-DSA keypair locally and register only the public key. Private key material never leaves your server. Sign payloads yourself and submit records via POST /v1/records.

import { generateKeypair } from '@certivu/sdk'
const { publicKey, privateKey } = generateKeypair()
// Store privateKey securely — never share it, never upload it
// Register publicKey in the dashboard

In the dashboard: Generators → Add generator → paste your public key.


import { CertivuClient } from '@certivu/sdk'
const certivu = new CertivuClient({
apiKey: process.env.CERTIVU_API_KEY, // ctv_key_…
generatorId: process.env.CERTIVU_GENERATOR_ID,
})
const { token } = await certivu.sign({
content: imageBuffer,
model: 'stable-diffusion-xl',
})

Terminal window
# 1. Request a reset link
curl -X POST https://api.certivu.ai/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]" }'
# 2. Reset with the token from the email
curl -X POST https://api.certivu.ai/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{ "token": "<token-from-email>", "password": "new-password" }'

The reset token is valid for 1 hour and is single-use.


Session-authenticated dashboard calls use the same plan-based limits as API key calls:

PlanRequests / min
Free100
Starter500
Growth1,000
Scale2,000
EnterpriseUnlimited

Verification (POST /v1/verify) is always free and not subject to signing quotas.