Getting started
Quickstart
From nothing to a live licensure check. If you already have a key, skip to step three.
1. Mint an API key
In the Payna dashboard, open Settings, then API Keys, and create a key with a label describing the system that will use it. You need to be an admin on the account.
2. Put it in the environment
# Store the key in the environment rather than in source
export PAYNA_API_KEY="pk_live_..."A key grants read access to your whole compliance portfolio, so treat it like a database credential. It belongs on a server. Never ship one in browser code, a mobile binary, or a public repository.
3. Call the summary endpoint
The summary is the cheapest call in the API and the best way to confirm a key works.
curl -s https://app.payna.com/api/v1/licenses/summary \
-H "Authorization: Bearer $PAYNA_API_KEY"You should get back aggregate counts across the portfolio.
{
"data": {
"total": 37,
"by_status": {
"active": 13,
"deficient": 2,
"pending": 3,
"applying": 10,
"not_licensed": 9
},
"renewing_soon": 3,
"renewing_30_days": 1,
"expired": 0,
"verified": 0
},
"meta": { "total": 37, "synced_at": null }
}A 401 here means the key is wrong, malformed, or revoked. A 403 means the key is genuine but is not entitled to this resource. Both are covered on the errors page.
4. Ask a real question
Aggregates are for dashboards. The call that tends to matter is whether a specific state is good right now.
# The question most integrations actually ask:
# can we operate in this state today?
curl -s https://app.payna.com/api/v1/licenses/CA \
-H "Authorization: Bearer $PAYNA_API_KEY"5. Wrap it in a client
A minimal client with the two things every integration needs: rate limit handling and a real error path.
const BASE = 'https://app.payna.com/api/v1'
async function payna(path) {
const res = await fetch(BASE + path, {
headers: { Authorization: `Bearer ${process.env.PAYNA_API_KEY}` },
})
if (res.status === 429) {
// Retry-After is in seconds and is always present on a 429.
const wait = Number(res.headers.get('Retry-After') ?? 30)
throw new Error(`Rate limited, retry in ${wait}s`)
}
const body = await res.json()
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`)
return body
}
// Gate on the derived descriptor, not the raw status string.
const { data } = await payna('/licenses/CA')
const canOperate = data.status_detail.category === 'active'Next
The interactive reference accepts your key and runs requests against your own data, which is usually faster than reading schemas.
BOOK A DEMO
See how Payna runs licensing.
Tell us a little about the company and we’ll walk through how Payna handles licensing and compliance in every US state.