API reference
CE requirements API
Continuing education requirements for behavioral health licenses in the 50 US states and DC. Read-only JSON over HTTPS, with every version kept.
Overview
The API returns continuing education requirements for behavioral health licenses in the 50 US states and DC: social work, counseling, psychology, marriage and family therapy, and behavior analysis. Each record is one license in one jurisdiction, with its hours per renewal cycle, cycle length, required topics, limits on self-study and live hours, the board's own notes, and links to where the rule is published.
Every version is kept. Ask what applies now, what applied on a date with as_of, or what changed since a date with /changes.
Informational only. The data is compiled from public licensing-board sources. It is not legal advice; verify current requirements with the relevant board.
curl -s "https://api.verbena.health/v1/requirements/CA/Social%20Work" \
-H "Authorization: Bearer $VERBENA_API_KEY"Authentication
Send your key in the Authorization header on every request: Authorization: Bearer vb_.... A request without a valid key answers 401.
On the Individual plan, create keys on the Account page in the Verbena app. Partners receive a key from us; get in touch.
Keys are read-only. A key is shown once, when it is created, and only a hash is stored, so a lost key is replaced rather than recovered. A key can carry an expiry date, and it can be revoked at any time.
Call the API from your servers. It does not accept cross-origin requests from browsers, and a key does not belong in a web page.
Requests and responses
Every endpoint is a GET under https://api.verbena.health/v1 and answers JSON with a data field. Some responses add fields, such as next_cursor or summary.
- Timestamps are ISO 8601 in UTC. Numbers are JSON numbers.
- Filters that take several values accept a comma list or a repeated parameter:
state=CA,NYorstate=CA&state=NY.licenseis the exception: repeat it, because license labels can contain commas. - Jurisdiction codes and license groups are case-insensitive.
- Verbena serves US jurisdictions only. A code outside the US is
404 unknown_jurisdiction. - Responses can gain fields over time. Ignore fields you do not recognize.
Errors
Errors are JSON with a stable error code to branch on, and usually a detail written for people. Do not parse detail.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_as_of | as_of is not a date. |
| 400 | bad_since | since is missing or not a date. |
| 400 | bad_query | A query value contains a NUL character. |
| 400 | bad_q | q was given more than once. |
| 400 | bad_covered | covered is not true or false. |
| 400 | unknown_region | A reference list was filtered by a region other than US. |
| 400 | unknown_license_group | A reference list was filtered by a group that is not one of the five. |
| 400 | unsupported_filter | license or q was sent to /coverage. |
| 401 | missing_api_key | No Authorization header. |
| 401 | invalid_api_key | The key is unknown, revoked or expired. |
| 403 | plan_required | The key's account plan does not include API access. |
| 404 | unknown_jurisdiction | The code is not a jurisdiction Verbena serves. |
| 404 | unknown_license_group | The path names a group that is not one of the five. |
| 404 | not_covered | Nothing is on record for the jurisdiction and group, at the as_of date if given. |
Pagination
/requirements returns up to limit records, 50 by default and 200 at most, with a next_cursor. Pass it back as cursor until it comes back null. The cursor is a position in a stable order, so a walk stays consistent even if data changes while you page. The reference lists are not paginated.
Caching
Every successful response carries a weak ETag. Send it back as If-None-Match; if nothing changed, the API answers 304 with no body. /meta reports when requirements last changed.
Keeping a copy current
Store records by jurisdiction code, license group and license together; that is a record's identity across versions. Then either pull your scope from /requirements on a schedule, or poll /changes with the time of your last run and apply each row. Doing both, polling changes daily and pulling in full weekly, keeps a copy from drifting.
Requirements
List requirements
GET/v1/requirements
Current requirements by default, one record per license, in a stable order. Pass as_of for the versions in force on a date instead. Filters combine. A filter that matches nothing returns an empty data array. A license_group value that is not one of the five is ignored.
Query parameters
| Parameter | Type | Description |
|---|---|---|
statequery |
array of string | Jurisdiction codes, case-insensitive. Repeat the parameter or use a comma list: state=CA,NY. |
regionquery |
array of string: US |
Regions. US is the only region served; any other value matches nothing. |
license_groupquery |
array of string: Social Work Counseling Psychology MFT BCBA |
License groups, case-insensitive. Repeat the parameter or use a comma list. |
licensequery |
array of string | An exact license label, matched case-insensitively. Repeat the parameter for several; labels can contain commas, so commas do not split. |
confidencequery |
string: high medium low unverified |
One confidence value. |
cpdquery |
boolean | true for regimes with no fixed hours, false for the rest. |
fully_async_okquery |
boolean | true for requirements with no live-session minimum, false for those with one. |
as_ofquery |
string | An ISO 8601 date or date-time. Returns the versions in force at that moment instead of the current ones. |
limitquery |
integer default 50 |
Page size, 1 to 200. |
cursorquery |
string | The next_cursor from the previous page. |
Responses
- 200 A page of requirements. Follow
next_cursoruntil it is null. - 304 Not modified: the
If-None-MatchETag still matches. No body. - 400 A malformed date or query value.
bad_as_ofbad_query - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/requirements?state=CA,NY&license_group=Social%20Work" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"cpd": false,
"total_hours": 36,
"cycle_years": 2,
"live_interactive_required_hours": 0,
"fully_async_ok": true,
"home_study_cap": "The board's statement on self-study limits, as plain text.",
"required_topics": [
{
"topic": "Law and Ethics",
"hours": 6,
"note": "Every renewal."
}
],
"board_nuances": "The board's own notes: exemptions, deadlines, anything that is not a number.",
"source_urls": [
"https://www.bbs.ca.gov/"
],
"confidence": "medium",
"verified_at": null,
"effective_from": "2026-08-12T14:03:11.000Z",
"effective_to": null,
"is_current": true,
"ingested_at": "2026-08-12T14:03:11.000Z"
}
],
"next_cursor": "Q0F8U29jaWFsIFdvcmt8TENTVw"
}Get the requirements for a jurisdiction and license group
GET/v1/requirements/{state}/{license_group}
The records for one jurisdiction and license group, as an array, because a jurisdiction can hold more than one license in a group. Pass as_of for the versions in force on a date.
Path parameters
| Parameter | Type | Description |
|---|---|---|
state requiredpath |
string | A jurisdiction code, case-insensitive. |
license_group requiredpath |
string: Social Work Counseling Psychology MFT BCBA |
A license group, case-insensitive. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
as_ofquery |
string | An ISO 8601 date or date-time. Returns the versions in force at that moment instead of the current ones. |
Responses
- 200 One record per license in the group.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 400 A malformed
as_ofdate.bad_as_of - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required - 404 The jurisdiction is not one Verbena serves, the group is not one of the five, or nothing is on record for the pair (at the
as_ofdate, if given).unknown_jurisdictionunknown_license_groupnot_covered
curl -s "https://api.verbena.health/v1/requirements/CA/Social%20Work" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"cpd": false,
"total_hours": 36,
"cycle_years": 2,
"live_interactive_required_hours": 0,
"fully_async_ok": true,
"home_study_cap": "The board's statement on self-study limits, as plain text.",
"required_topics": [
{
"topic": "Law and Ethics",
"hours": 6,
"note": "Every renewal."
}
],
"board_nuances": "The board's own notes: exemptions, deadlines, anything that is not a number.",
"source_urls": [
"https://www.bbs.ca.gov/"
],
"confidence": "medium",
"verified_at": null,
"effective_from": "2026-08-12T14:03:11.000Z",
"effective_to": null,
"is_current": true,
"ingested_at": "2026-08-12T14:03:11.000Z"
}
]
}Get every version for a jurisdiction and license group
GET/v1/requirements/{state}/{license_group}/history
Every version on record, current and closed, newest first within each license. effective_from and effective_to give each version its window.
Path parameters
| Parameter | Type | Description |
|---|---|---|
state requiredpath |
string | A jurisdiction code, case-insensitive. |
license_group requiredpath |
string: Social Work Counseling Psychology MFT BCBA |
A license group, case-insensitive. |
Query parameters
No parameters.
Responses
- 200 All versions, possibly an empty array.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required - 404 The jurisdiction is not one Verbena serves, or the group is not one of the five.
unknown_jurisdictionunknown_license_group
curl -s "https://api.verbena.health/v1/requirements/CA/Social%20Work/history" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"cpd": false,
"total_hours": 36,
"cycle_years": 2,
"live_interactive_required_hours": 0,
"fully_async_ok": true,
"home_study_cap": "The board's statement on self-study limits, as plain text.",
"required_topics": [
{
"topic": "Law and Ethics",
"hours": 6,
"note": "Every renewal."
}
],
"board_nuances": "The board's own notes: exemptions, deadlines, anything that is not a number.",
"source_urls": [
"https://www.bbs.ca.gov/"
],
"confidence": "medium",
"verified_at": null,
"effective_from": "2026-08-12T14:03:11.000Z",
"effective_to": null,
"is_current": true,
"ingested_at": "2026-08-12T14:03:11.000Z"
}
]
}List changes since a date
GET/v1/changes
One row per version that took effect, or was closed, on or after since, newest first. added has no predecessor, amended carries a per-field diff, and retired is a version closed with no successor. Each row carries the full current record, so no second call is needed.
By default only material rows come back: a rule field or the board notes moved, or a requirement was added or retired. include=all also returns changes to our own metadata: confidence, the verification date and the source list. Overlapping windows are safe; identify a row by its jurisdiction, license group, license and event_at.
Query parameters
| Parameter | Type | Description |
|---|---|---|
since requiredquery |
string | An ISO 8601 date or date-time. Inclusive. |
statequery |
array of string | Jurisdiction codes, case-insensitive. Repeat the parameter or use a comma list: state=CA,NY. |
regionquery |
array of string: US |
Regions. US is the only region served; any other value matches nothing. |
license_groupquery |
array of string: Social Work Counseling Psychology MFT BCBA |
License groups, case-insensitive. Repeat the parameter or use a comma list. |
includequery |
string: all |
all adds changes to our own metadata; the default returns material changes only. |
Responses
- 200 Change rows, with the window and mode that answered.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 400
sinceis missing or not a date.bad_since - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/changes?since=2026-09-01&state=CA" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": "CA",
"jurisdiction_name": "California",
"region": "US",
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"change_type": "amended",
"material": true,
"event_at": "2026-09-03T13:20:00.000Z",
"diff": {
"total_hours": {
"from": 36,
"to": 40
},
"live_hours": {
"from": 0,
"to": 6
}
},
"current": {
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"cpd": false,
"total_hours": 40,
"cycle_years": 2,
"live_interactive_required_hours": 6,
"fully_async_ok": false,
"home_study_cap": "The board's statement on self-study limits, as plain text.",
"required_topics": [
{
"topic": "Law and Ethics",
"hours": 6,
"note": "Every renewal."
}
],
"board_nuances": "The board's own notes: exemptions, deadlines, anything that is not a number.",
"source_urls": [
"https://www.bbs.ca.gov/"
],
"confidence": "medium",
"verified_at": null,
"effective_from": "2026-09-03T13:20:00.000Z",
"effective_to": null,
"is_current": true,
"ingested_at": "2026-08-12T14:03:11.000Z"
}
}
],
"since": "2026-09-01T00:00:00.000Z",
"include": "material"
}Reference lists
List jurisdictions
GET/v1/jurisdictions
Every jurisdiction Verbena serves, the 50 states and DC, with the license groups each currently has data for.
Query parameters
| Parameter | Type | Description |
|---|---|---|
regionquery |
array of string: US |
Regions. US is the only region served; any other value matches nothing. |
Responses
- 200 Jurisdictions in name order.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/jurisdictions" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"code": "AL",
"name": "Alabama",
"region": "US",
"board": "Alabama licensing board",
"license_groups": [
"BCBA",
"Counseling",
"MFT",
"Psychology",
"Social Work"
]
}
]
}List licenses
GET/v1/licenses
One row per license with a current requirement, with its jurisdiction and license group. Use it to fill a picker, or to find every state that has a given license with q. Unpaginated, because the list is bounded by the dataset.
Query parameters
| Parameter | Type | Description |
|---|---|---|
statequery |
array of string | Jurisdiction codes, case-insensitive. Repeat the parameter or use a comma list. |
regionquery |
array of string: US |
Regions, case-insensitive. US is the only one; anything else is 400 unknown_region. |
license_groupquery |
array of string: Social Work Counseling Psychology MFT BCBA |
License groups, case-insensitive. A value that is not one of the five is 400 unknown_license_group. |
licensequery |
array of string | An exact license label, matched case-insensitively. Repeat the parameter for several; labels can contain commas, so commas do not split. |
qquery |
string | A case-insensitive substring of the license label, for example LCSW. Give it once. |
Responses
- 200 Licenses in jurisdiction, group and license order.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 400 A filter the endpoint cannot apply.
unknown_regionunknown_license_groupbad_qbad_query - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/licenses?q=LCSW" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"license": "LCSW (Licensed Clinical Social Worker)",
"last_changed_at": "2026-08-12T14:03:11.000Z"
}
]
}List coverage
GET/v1/coverage
Every combination of jurisdiction and license group, covered or not, with its licenses and when anything in it last changed. The summary counts the rows returned. license and q belong to /licenses and are refused here.
Query parameters
| Parameter | Type | Description |
|---|---|---|
statequery |
array of string | Jurisdiction codes, case-insensitive. Repeat the parameter or use a comma list. |
regionquery |
array of string: US |
Regions, case-insensitive. US is the only one; anything else is 400 unknown_region. |
license_groupquery |
array of string: Social Work Counseling Psychology MFT BCBA |
License groups, case-insensitive. A value that is not one of the five is 400 unknown_license_group. |
coveredquery |
boolean | true or false keeps one side; anything else is 400 bad_covered. |
Responses
- 200 Coverage rows and their summary.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 400 A filter the endpoint cannot apply.
unknown_regionunknown_license_groupbad_coveredunsupported_filter - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/coverage?state=CA" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"jurisdiction": {
"code": "CA",
"name": "California",
"region": "US",
"board": "Board of Behavioral Sciences"
},
"license_group": "Social Work",
"covered": true,
"licenses": [
"LCSW (Licensed Clinical Social Worker)"
],
"last_changed_at": "2026-08-12T14:03:11.000Z"
}
],
"summary": {
"combinations": 255,
"covered": 255,
"not_covered": 0
}
}List license groups
GET/v1/license-groups
The five license groups, with how many jurisdictions and licenses each covers now.
Query parameters
| Parameter | Type | Description |
|---|---|---|
regionquery |
array of string: US |
Regions, case-insensitive. US is the only one; anything else is 400 unknown_region. |
Responses
- 200 The five groups in alphabetical order.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 400 An unknown region.
unknown_region - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/license-groups" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"license_group": "Social Work",
"jurisdictions_covered": 51,
"licenses": 51
}
]
}List topics
GET/v1/topics
The canonical topic vocabulary. Boards name one subject many ways, so each topic has a key, a label and the alternate wordings that mean it. To resolve a record's required_topics[].topic, match it against each topic's label and aliases together, ignoring case and repeated spaces. Most wordings resolve; a few have no key yet.
Query parameters
No parameters.
Responses
- 200 Topics in curated order.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/topics" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": [
{
"key": "ethics",
"label": "Ethics",
"aliases": [
"Ethics and Jurisprudence",
"Ethical Practice of Psychology"
]
}
]
}Get the dataset summary
GET/v1/meta
Counts, when requirements last changed, and the closed value sets a client can validate against. dataset.last_changed_at moves when a requirement version is added, amended or retired. It does not move when a jurisdiction name or board is edited, so keep a scheduled full pull as well.
Query parameters
No parameters.
Responses
- 200 The summary.
- 304 Not modified: the
If-None-MatchETag still matches. No body. - 401 The key is missing, unknown, revoked or expired.
missing_api_keyinvalid_api_key - 403 The key belongs to an account whose plan does not include API access.
plan_required
curl -s "https://api.verbena.health/v1/meta" \
-H "Authorization: Bearer $VERBENA_API_KEY"{
"data": {
"api_version": "v1",
"dataset": {
"last_changed_at": "2026-08-12T14:03:11.000Z",
"jurisdictions": 51,
"jurisdictions_covered": 51,
"license_groups": 5,
"combinations": 255,
"combinations_covered": 255,
"current_records": 255,
"record_versions": 255
},
"values": {
"regions": [
"US"
],
"license_groups": [
"BCBA",
"Counseling",
"MFT",
"Psychology",
"Social Work"
],
"confidence": [
"high",
"medium",
"low",
"unverified"
],
"change_types": [
"added",
"amended",
"retired"
]
}
}
}Objects
Requirement
The continuing education rule for one license in one jurisdiction, as one version.
| Field | Type | Description |
|---|---|---|
jurisdiction |
Jurisdiction | |
license_group |
string: Social Work Counseling Psychology MFT BCBA |
One of five license groups. |
license |
string | The license label, usually the abbreviation then the full name. With the jurisdiction code and license group, it identifies the record across versions. |
cpd |
boolean | Derived: true when total_hours is null, a regime with no fixed hours. |
total_hours |
number or null | Hours per renewal cycle. Null means no fixed hours, not missing data. |
cycle_years |
number or null | Length of the renewal cycle in years. |
live_interactive_required_hours |
number | The minimum hours that must be live or interactive. |
fully_async_ok |
boolean | Derived: true when there is no live minimum. |
home_study_cap |
string or null | The board's statement on self-study limits, as plain text. |
required_topics |
array of RequiredTopic | |
board_nuances |
string or null | The board's own notes: exemptions, deadlines and anything that does not fit a number. |
source_urls |
array of string | Where the rule is published. Every record has at least one. |
confidence |
string: high medium low unverified |
high: every core value was checked against a primary source and dated. medium or low: published with less certainty. unverified: not yet checked against its source. |
verified_at |
string or null | When the values were last checked against their source. |
effective_from |
string | When this version was recorded. Not the board's effective date; that is in the notes where the board states one. |
effective_to |
string or null | When this version was closed; null while it is in force. |
is_current |
boolean | |
ingested_at |
string |
RequiredTopic
| Field | Type | Description |
|---|---|---|
topic |
string | The board's wording. Resolve it with /topics. |
hours optional |
number or null | Hours required for the topic, where the board states a number. |
note optional |
string | When it applies, for example every renewal or the first renewal only. |
Change
| Field | Type | Description |
|---|---|---|
jurisdiction |
string | Jurisdiction code. |
jurisdiction_name |
string | |
region |
string: US |
|
license_group |
string: Social Work Counseling Psychology MFT BCBA |
One of five license groups. |
license |
string | |
change_type |
string: added amended retired |
|
material |
boolean | True when a requirement was added or retired, or a rule field or the board notes moved. |
event_at |
string | effective_from for added and amended rows, effective_to for retired ones. |
diff |
object or null | For amended rows, each changed field as {from, to}. Keys use storage names: live_hours is live_interactive_required_hours on the record. |
current |
Requirement | The continuing education rule for one license in one jurisdiction, as one version. |
License
| Field | Type | Description |
|---|---|---|
jurisdiction |
Jurisdiction | |
license_group |
string: Social Work Counseling Psychology MFT BCBA |
One of five license groups. |
license |
string | |
last_changed_at |
string | When this license's current version took effect. |
CoverageRow
| Field | Type | Description |
|---|---|---|
jurisdiction |
Jurisdiction | |
license_group |
string: Social Work Counseling Psychology MFT BCBA |
One of five license groups. |
covered |
boolean | |
licenses |
array of string | Current licenses in the combination, sorted; empty when not covered. |
last_changed_at |
string or null | The latest addition, amendment or retirement in the combination; null if it never had a record. |
CoverageSummary
| Field | Type | Description |
|---|---|---|
combinations |
integer | |
covered |
integer | |
not_covered |
integer |
LicenseGroupSummary
| Field | Type | Description |
|---|---|---|
license_group |
string: Social Work Counseling Psychology MFT BCBA |
One of five license groups. |
jurisdictions_covered |
integer | |
licenses |
integer |
Topic
| Field | Type | Description |
|---|---|---|
key |
string | |
label |
string | |
aliases |
array of string | Other wordings boards use for the topic. |
Meta
| Field | Type | Description |
|---|---|---|
api_version |
string: v1 |
|
dataset |
object | |
values |
object |
Jurisdiction
| Field | Type | Description |
|---|---|---|
code |
string | Jurisdiction code, for example CA. |
name |
string | |
region |
string: US |
|
board |
string or null | The licensing board, where known. |
Error
| Field | Type | Description |
|---|---|---|
error |
string | A stable code to branch on. |
detail optional |
string | A human-readable explanation. Do not parse it. |
jurisdiction optional |
string | On unknown_jurisdiction, the code that was not found. |