Pryv API Platform

Keys, credits, and private AI docs in one dashboard.

Use wallet-signed management endpoints to create keys and add credits, then call the public API with Bearer keys. Everything below matches current backend contracts.

Quickstart

Purchase credits, mint a key, ship a request.

  1. 1Set NEXT_PUBLIC_API_BASE_URL and NEXT_PUBLIC_PLATFORM_URL.
  2. 2Create an API key (shown once).
  3. 3Buy credits with usdAmount and wait for balance to update.
  4. 4Call /v1/chat/completions with Bearer auth.
  5. 5Handle 402 insufficient_credits and 429 rate limits.

Hello world request

const apiKey = process.env.PRYV_API_KEY

const res = await fetch('https://api.pryv.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'gpt-5.2',
    messages: [{ role: 'user', content: 'Hello Pryv!' }],
    stream: false
  })
})

const data = await res.json()
console.log(data.choices[0].message.content)

Topology

Keep the dashboard and API on separate hosts for security and CORS control.

Dashboard

<your-platform-url>

Public developer API

https://api.pryv.ai

Env config

NEXT_PUBLIC_PLATFORM_URL=<your-platform-url>
NEXT_PUBLIC_API_BASE_URL=https://api.pryv.ai

Environments

Sandbox

Stripe test mode, separate webhook secret, sandbox base URL.

Production

Live Stripe, production base URL, separate WAF and rate limits.

Personas

Developer

Create, label, rotate keys; purchase credits; view usage; run quickstarts.

Admin (support)

View account state, assist failed payments, revoke compromised keys, monitor abuse.

Jobs to be done

  • Purchase credits -> create key -> call /v1/chat/completions -> ship.
  • Rotate keys, restrict usage, respond to insufficient credits.
  • Inspect usage, balances, and request_id for debugging.

Platform IA

Docs, API Keys, Usage, Billing, Settings.
Settings can include API Keys, Billing and Credits, Usage, Webhooks, Security.

Roles are single-owner today. The wallet signing management requests is treated as the owner.

Auth model

Public API

Authorization: Bearer pryv_...

Management

walletAddress + message + signature (anti-replay)

Wallet auth fields can be passed in query params (GET) or JSON body (POST or DELETE).

Wallet signature payload

const message = buildWalletAuthMessage({ action: 'create' })
const { walletAddress, signature } = await signWalletMessage(message)

const body = {
  walletAddress,
  message,
  signature,
  name: 'Production key'
}

Key concepts

userId

Salted hash of a wallet address.

Credits unit

Balance stored in microusd (1e-6 USD). $10.00 = 10_000_000.

Preview cost

PII preview charges $0.05 (50_000 microusd).

Pricing

Debits follow model prices per 1M tokens with a margin multiplier (default 1.25).

Typed fetch helper

export type ApiError = {
  error: { message: string; type?: string; code?: string; details?: unknown }
}

export async function fetchJson<T>(
  baseUrl: string,
  path: string,
  init?: RequestInit
): Promise<T> {
  const res = await fetch(`${baseUrl}${path}`, {
    ...init,
    headers: {
      'Content-Type': 'application/json',
      ...(init?.headers || {})
    }
  })

  const text = await res.text()
  const body = text ? (JSON.parse(text) as unknown) : null

  if (!res.ok) {
    const apiErr = body as ApiError | null
    const msg = apiErr?.error?.message || `Request failed: ${res.status}`
    const err = new Error(msg)
    ;(err as any).status = res.status
    ;(err as any).code = apiErr?.error?.code
    ;(err as any).details = apiErr?.error?.details
    throw err
  }

  return body as T
}

Wallet signing helper

import { BrowserProvider } from 'ethers'

export async function signWalletMessage(message: string) {
  const provider = new BrowserProvider((window as any).ethereum)
  const signer = await provider.getSigner()
  const walletAddress = await signer.getAddress()
  const signature = await signer.signMessage(message)
  return { walletAddress, signature }
}

export function buildWalletAuthMessage(params: {
  action: 'create' | 'list' | 'revoke' | 'checkout' | 'balance' | 'ledger'
  keyId?: string
}) {
  const nonce = crypto.randomUUID()
  const ts = new Date().toISOString()
  const keyId = params.keyId ?? ''
  return `Pryv API key mgmt | action=${params.action} | keyId=${keyId} | nonce=${nonce} | ts=${ts}`
}

Endpoint catalog

Public and wallet-auth routes

GET/v1/modelsBearerList available models
GET/v1/models/{id}BearerModel details
POST/v1/chat/completionsBearerChat completions (SSE when stream=true)
POST/v1/pii/previewBearerPaid preview ($0.05)
GET/api/v1/billing/credit-packsPublicList credit packs
GET/api/v1/billing/balanceWalletCredits balance
GET/api/v1/billing/ledgerWalletUsage ledger
GET/api/v1/billing/successBrowserCheckout success redirect
GET/api/v1/billing/cancelBrowserCheckout cancel redirect

Chat completions

POST /v1/chat/completions

OpenAI-compatible chat completions with optional tools, URLs, PDFs, and PII controls.

FieldTypeReqNotes
modelstringyesModel id from GET /v1/models.
messagesarrayyesOpenAI-style messages; last message must include text.
streambooleannoWhen true, returns SSE stream.
max_tokensnumbernoUsed for rate-limit estimation.
toolsarraynoOpenAI-style tool definitions.
urlsarraynoURL list to fetch and include.
pdfsarraynoPDFs as { base64, file_name }.
piiEntitiesarraynoList of PII entity strings.
piiConfigobjectnoPII config for image or pdf thresholds.
skip_pii_redactionbool/objectnotrue or { text, image, pdf }.
  • messages[].content can be a string or an array of parts (type: "text" or "image_url").
  • Aliases accepted: pii_entities, pii_config, skipPiiRedaction, no_pii_redaction, noPiiRedaction.
  • Optional header X-Request-Id is echoed into ledger entries for correlation.

Streaming SSE

Set stream: true to receive text/event-stream chunks. Streaming ends with [DONE].

export async function streamChatCompletion(
  apiBaseUrl: string,
  apiKey: string,
  req: any,
  onDelta: (text: string) => void
) {
  const res = await fetch(`${apiBaseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ ...req, stream: true })
  })

  if (!res.ok || !res.body) {
    const text = await res.text()
    throw new Error(`stream failed: ${res.status} ${text}`)
  }

  const reader = res.body.getReader()
  const decoder = new TextDecoder()
  let buffer = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    buffer += decoder.decode(value, { stream: true })

    const frames = buffer.split('\n\n')
    buffer = frames.pop() || ''

    for (const frame of frames) {
      const line = frame.trim()
      if (!line.startsWith('data:')) continue
      const data = line.slice('data:'.length).trim()
      if (data === '[DONE]') return
      const evt = JSON.parse(data)
      const delta = evt?.choices?.[0]?.delta?.content
      if (typeof delta === 'string' && delta) onDelta(delta)
    }
  }
}

PII controls

Privacy tuning

Redact emails only

piiEntities: ["EMAIL_ADDRESS"]

Disable image redaction

skip_pii_redaction: { image: true }

Blur faces strongly

piiConfig.image.faces = { blur: "strong" }

PII config fields

  • image.faces.* (face blur controls)
  • image.scoreThreshold (OCR and PII strictness)
  • pdf.scoreThreshold, pdf.maxPages

Aliases accepted: pii_entities, pii_config, skipPiiRedaction, no_pii_redaction, noPiiRedaction.

POST /v1/pii/preview

export async function piiPreview(
  apiBaseUrl: string,
  apiKey: string,
  req: any
) {
  return fetchJson(apiBaseUrl, '/v1/pii/preview', {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
    body: JSON.stringify(req)
  })
}

Models

GET /v1/models returns the models available to your API key. Cache for hours.

Open model catalog

Use GET /v1/models/{id} for a single model details response.

List models

curl https://api.pryv.ai/v1/models \
  -H "Authorization: Bearer pryv_..."

Pricing and credits

Debits are based on model prices (USD per 1M tokens) with a margin multiplier. Preview charges $0.05 per call.

Balance is stored in microusd (1e-6 USD).

Error handling

Handle auth, credits, and limits

401missing_api_key, invalid_api_key, inactive_api_key, missing_wallet_auth.
403Wallet auth failures such as ACTION_MISMATCH, NONCE_REPLAY, SIGNATURE_INVALID.
402insufficient_credits - prompt user to add credits.
429rate_limit_exceeded_rpm or rate_limit_exceeded_tpm. Respect Retry-After.
400invalid_model, invalid_params, validation errors.
500internal_error or billing_error.

Error envelope

{
  "error": {
    "message": "Missing or invalid API key",
    "type": "authentication_error",
    "code": "invalid_api_key",
    "details": { "optional": "context" }
  }
}

Wallet error codes: ACTION_MISMATCH, KEY_ID_MISMATCH, MISSING_NONCE_OR_TS, INVALID_TS, TS_EXPIRED, NONCE_REPLAY, SIGNATURE_INVALID, SIGNATURE_VERIFY_ERROR.

Rate limits

Public API responses include rate limit headers and Retry-After on 429.

  • X-RateLimit-Limit-RPM
  • X-RateLimit-Remaining-RPM
  • X-RateLimit-Reset-RPM
  • X-RateLimit-Limit-TPM
  • X-RateLimit-Remaining-TPM
  • X-RateLimit-Reset-TPM
  • Retry-After
export function parseRetryAfterSeconds(res: Response): number | null {
  const ra = res.headers.get('Retry-After')
  if (!ra) return null
  const n = Number(ra)
  return Number.isFinite(n) ? n : null
}

Security

  • API key is shown once and cannot be recovered.
  • Never commit keys; avoid localStorage by default.
  • Frontend never asserts entitlement; backend is source of truth.
  • Prefer POST for wallet-auth to reduce URL leakage in logs.

Operations

Caching

  • Cache GET /v1/models for hours.
  • Cache keys and balance for 5 to 15 seconds with manual refresh.

Retries

  • Retry idempotent GETs with exponential backoff.
  • Do not auto-retry key creation or checkout creation without user action.

Telemetry events

  • api_keys_viewed
  • api_key_created
  • api_key_revoked
  • billing_checkout_started
  • billing_checkout_completed
  • credits_balance_viewed
  • insufficient_credits_error_shown
  • rate_limit_error_shown

Launch checklist

Minimum QA cases

  • Create key -> show once -> copy -> close -> key not retrievable.
  • Revoke key -> bearer calls fail with 401.
  • Checkout with usdAmount min and max boundaries.
  • Credits applied after webhook; polling balance updates.
  • Chat completions streaming works.
  • PII preview charges $0.05 and returns redacted content.
  • Insufficient credits returns 402 and UI reacts correctly.
  • Rate limit 429 handling respects Retry-After.