📘 Introduction
The ICONIC Board Credential Verification API is a REST API for verifying the credential status of holistic health practitioners registered with the ICONIC Board of Holistic Health.
Use it to verify credentials in real time at time of booking, claim submission, hiring, or patient registration. All responses are JSON and return within 80ms (median).
Base URL
API Version
The current version is v1. All endpoint paths are prefixed with /api/v1/. Breaking changes will be introduced under a new version prefix (/api/v2/) with at least 90 days' notice.
Free tier available: Register a free API key at /api-access to get 100 requests/day with no credit card required.
🚀 Quick Start
You can make your first API call in under 60 seconds. Here's how:
1. Get an API key
Register at /api-access. Your key is created immediately — no approval required for the free tier.
2. Make your first request
-H "X-API-Key: IBC_your_api_key_here"
3. Parse the response
"success": true,
"result": {
"found": true,
"credential_number": "IBC-12345",
"status": "active",
"tier": "Certified Holistic Health Practitioner",
"tier_code": "CHHP",
"practitioner": { "name": "Jane Doe", "state": "AZ", "country": "US" },
"issued_at": "2024-03-15T00:00:00.000Z",
"expires_at": "2026-03-15T00:00:00.000Z",
"endorsements": [{ "name": "Functional Nutrition" }],
"verified_at": "2025-01-22T18:30:00.000Z",
"source": "ICONIC Board of Holistic Health"
}
}
🔑 Authentication
Authenticate by including your API key in every request. You can pass it via HTTP header (recommended) or query parameter.
Header (recommended)
Query parameter (alternative)
Security note: Prefer the X-API-Key header over query parameters to avoid leaking your key in server logs and browser history.
Anonymous access
You may make requests without an API key. Anonymous requests are rate-limited to 10 requests per day per IP address. Register a free API key for 100 requests/day.
API Tiers
| Tier | Req/Day | Req/Month | Batch Max | Webhooks | Price |
|---|---|---|---|---|---|
| Anonymous | 10 | — | — | ✕ | — |
| free | 100 | 3,000 | 10 | ✕ | $0 |
| standard | 5,000 | 150,000 | 50 | ✕ | $49/mo |
| enterprise | Unlimited | Unlimited | 100 | ✓ | $199/mo |
⏱ Rate Limits
Rate limits are enforced per API key (or per IP for anonymous requests). Limits reset at midnight UTC each day.
When you exceed your daily limit, the API returns a 429 Too Many Requests response with details:
"success": false,
"error": "RATE_LIMIT_EXCEEDED",
"message": "Daily limit of 100 reached. Upgrade at .../developers",
"limit": 100,
"resets": "midnight UTC"
}
📋 Endpoints
| Parameter | Type | Required | Description |
|---|---|---|---|
| credential_number | string | Optional* | ICONIC credential number (e.g. IBC-12345). Case-insensitive. Returns a single result. |
| name | string | Optional* | Practitioner display name. Partial match. Returns up to 10 results. |
| state | string | Optional | Two-letter US state code (e.g. AZ). Only used with name. |
| api_key | string | Optional | API key as query param. Prefer X-API-Key header instead. |
* At least one of credential_number or name is required.
curl "https://iconic-board.polsia.app/api/v1/verify?credential_number=IBC-12345" \
-H "X-API-Key: IBC_your_key"
# Name + state search
curl "https://iconic-board.polsia.app/api/v1/verify?name=Jane+Doe&state=AZ" \
-H "X-API-Key: IBC_your_key"
// By credential number
const res = await fetch(
'https://iconic-board.polsia.app/api/v1/verify?credential_number=IBC-12345',
{ headers }
);
const { result } = await res.json();
console.log(result.status); // 'active'
// Name search
const search = await fetch(
'https://iconic-board.polsia.app/api/v1/verify?name=Jane+Doe&state=AZ',
{ headers }
).then(r => r.json());
console.log(search.results); // array
headers = {"X-API-Key": "IBC_your_key"}
BASE = "https://iconic-board.polsia.app/api/v1"
# By credential number
r = requests.get(BASE + "/verify", headers=headers,
params={"credential_number": "IBC-12345"})
result = r.json()["result"]
print(result["status"]) # active
# Name search
r2 = requests.get(BASE + "/verify", headers=headers,
params={"name": "Jane Doe", "state": "AZ"})
results = r2.json()["results"]
200 Single credential found
"status": "active", "tier": "Certified Holistic Health Practitioner",
"tier_code": "CHHP", "practitioner": { "name": "Jane Doe", "state": "AZ", "country": "US" },
"issued_at": "2024-03-15T00:00:00.000Z", "expires_at": "2026-03-15T00:00:00.000Z",
"endorsements": [{ "name": "Functional Nutrition" }],
"verified_at": "2025-01-22T18:30:00.000Z" } }
404 Credential not found
"message": "Credential not found in ICONIC Board registry." }
| Field | Type | Required | Description |
|---|---|---|---|
| credentials | string[] | Required | Array of credential numbers. Max: 10 (free), 50 (standard), 100 (enterprise). |
-H "X-API-Key: IBC_your_key" \
-H "Content-Type: application/json" \
-d '{"credentials":["IBC-001","IBC-002","IBC-003"]}'
'https://iconic-board.polsia.app/api/v1/verify/batch',
{
method: 'POST',
headers: {
'X-API-Key': 'IBC_your_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
credentials: ['IBC-001', 'IBC-002', 'IBC-003']
})
}
);
const { results, found } = await response.json();
console.log(`${found} of ${results.length} found`);
response = requests.post(
"https://iconic-board.polsia.app/api/v1/verify/batch",
headers={"X-API-Key": "IBC_your_key"},
json={"credentials": ["IBC-001", "IBC-002", "IBC-003"]}
)
data = response.json()
print(f"{data['found']} of {data['count']} credentials found")
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://iconic-board.polsia.app/api/v1/verify/batch',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'credentials' => ['IBC-001', 'IBC-002']
]),
CURLOPT_HTTPHEADER => [
'X-API-Key: IBC_your_key',
'Content-Type: application/json'
]
]);
$result = json_decode(curl_exec($ch), true);
echo $result['found'] . ' found';
200 Batch results
"success": true,
"count": 3,
"found": 2,
"results": [
{ "found": true, "credential_number": "IBC-001", "status": "active", ... },
{ "found": true, "credential_number": "IBC-002", "status": "expired", ... },
{ "found": false, "credential_number": "IBC-003" }
],
"verified_at": "2025-01-22T18:30:00.000Z"
}
{
"status": "operational",
"version": "1.0",
"name": "ICONIC Board Credential Verification API",
"docs": "https://iconic-board.polsia.app/api-docs",
"timestamp": "2025-01-22T18:30:00.000Z"
}
📦 Response Schema
All responses include a top-level success boolean. Successful credential lookups return a result object (single) or results array (batch/name search).
Credential object fields
| Field | Type | Description |
|---|---|---|
| found | boolean | Always present. true if credential exists in registry. |
| credential_number | string | ICONIC credential number (e.g. IBC-12345). |
| status | string | One of: active, expired, revoked, suspended. |
| tier | string | null | Full credential type name. |
| tier_code | string | null | Short credential type code (e.g. CHHP). |
| practitioner | object | Object with name, state, country. |
| issued_at | ISO 8601 | null | Date credential was issued. |
| expires_at | ISO 8601 | null | Date credential expires (or expired). |
| endorsements | array | Array of issued endorsement objects with name. |
| verified_at | ISO 8601 | Timestamp of this API response. |
| source | string | Always "ICONIC Board of Holistic Health". |
🔴 Credential Statuses
The status field can be one of four values:
| Status | Meaning | Recommended Action |
|---|---|---|
| active | Credential is current and in good standing | Allow booking / claim / hire |
| expired | Credential has passed its expiration date | Block or warn; request renewal |
| revoked | Credential revoked by ICONIC Board | Block immediately; do not accept |
| suspended | Credential temporarily suspended pending review | Block temporarily; check again later |
❌ Error Codes
All errors follow the same JSON structure with a machine-readable error code:
🔔 Webhooks Enterprise only
Enterprise API key holders can subscribe to real-time credential status change events. When a credential status changes, ICONIC Board sends an HTTP POST to your registered endpoint URL.
Enterprise tier required. Webhooks are available on the $199/month enterprise plan. Upgrade in your developer dashboard →
Webhook payload format
"event": "credential_revoked",
"data": {
"credential_number": "IBC-12345",
"old_status": "active",
"new_status": "revoked",
"reason": "CE requirements not met"
},
"timestamp": "2025-01-22T18:30:00.000Z",
"source": "ICONIC Board of Holistic Health"
}
Delivery headers
X-ICONIC-Signature: sha256=abc123...
X-ICONIC-Event: webhook
User-Agent: ICONIC-Board-Webhooks/1.0
Delivery semantics
- ICONIC Board attempts delivery up to 3 times with exponential backoff (2s, 4s)
- Delivery is successful when your endpoint returns a 2xx HTTP status
- Delivery history is visible in your developer dashboard
- Timeout per attempt: 10 seconds
🛡 Signature Verification
Every webhook delivery includes an X-ICONIC-Signature header. Verify it to confirm the request originated from ICONIC Board.
The signature is computed as HMAC-SHA256(signing_secret, raw_request_body) and formatted as sha256=<hex_digest>.
function verifySignature(signingSecret, rawBody, signatureHeader) {
const expected = 'sha256=' +
crypto.createHmac('sha256', signingSecret).update(rawBody).digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signatureHeader)
);
}
// In your Express webhook handler:
app.post('/webhooks/iconic', express.raw({type: '*/*'}), (req, res) => {
const sig = req.headers['x-iconic-signature'];
if (!verifySignature(process.env.ICONIC_SIGNING_SECRET, req.body, sig)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Handle event.event (e.g. 'credential_revoked')
res.status(200).end();
});
from flask import Flask, request, abort
app = Flask(__name__)
SIGNING_SECRET = os.environ["ICONIC_SIGNING_SECRET"]
def verify_signature(raw_body, sig_header):
expected = "sha256=" + hmac.new(
SIGNING_SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig_header)
@app.route("/webhooks/iconic", methods=["POST"])
def webhook():
sig = request.headers.get("X-ICONIC-Signature", "")
if not verify_signature(request.get_data(), sig):
abort(401)
event = request.json
# Handle event['event']
return "", 200
$signingSecret = getenv('ICONIC_SIGNING_SECRET');
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_ICONIC_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $signingSecret);
if (!hash_equals($expected, $sigHeader)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Handle $event['event']
http_response_code(200);
Your signing secret is displayed once when you create a webhook subscription in your developer dashboard. Store it securely as an environment variable — it cannot be retrieved later.
📡 Webhook Event Types
| Event | Trigger |
|---|---|
| credential_renewed | Credential successfully renewed for another term |
| credential_revoked | Credential permanently revoked by ICONIC Board |
| credential_suspended | Credential temporarily suspended pending review |
| credential_expired | Credential automatically expired past its end date |
| credential_tier_changed | Practitioner upgraded or changed their credential tier |
| credential_status_changed | Generic status change event (covers all status transitions) |
💻 Code Examples
JavaScript — Verification helper
constructor(apiKey) {
this.apiKey = apiKey;
this.base = 'https://iconic-board.polsia.app/api/v1';
}
headers() {
return { 'X-API-Key': this.apiKey };
}
async verify(credentialNumber) {
const url = `${this.base}/verify?credential_number=${credentialNumber}`;
const res = await fetch(url, { headers: this.headers() });
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
return data.result;
}
async isActive(credentialNumber) {
const result = await this.verify(credentialNumber);
return result?.found && result.status === 'active';
}
async batchVerify(credentialNumbers) {
const res = await fetch(`${this.base}/verify/batch`, {
method: 'POST',
headers: { ...this.headers(), 'Content-Type': 'application/json' },
body: JSON.stringify({ credentials: credentialNumbers })
});
const data = await res.json();
return data.results;
}
}
// Usage:
const iconic = new IconicVerifier('IBC_your_key');
const active = await iconic.isActive('IBC-12345'); // true/false
Python — requests wrapper
class IconicVerifier:
BASE = "https://iconic-board.polsia.app/api/v1"
def __init__(self, api_key: str):
self.session = requests.Session()
self.session.headers["X-API-Key"] = api_key
def verify(self, credential_number: str) -> dict:
r = self.session.get(f"{self.BASE}/verify",
params={"credential_number": credential_number})
r.raise_for_status()
return r.json().get("result")
def is_active(self, credential_number: str) -> bool:
result = self.verify(credential_number)
return bool(result) and result["found"] and result["status"] == "active"
def batch_verify(self, credential_numbers: list) -> list:
r = self.session.post(f"{self.BASE}/verify/batch",
json={"credentials": credential_numbers})
r.raise_for_status()
return r.json()["results"]
# Usage:
client = IconicVerifier("IBC_your_key")
print(client.is_active("IBC-12345")) # True/False
roster = client.batch_verify(["IBC-001", "IBC-002"])
PHP
function iconic_verify(string $apiKey, string $credentialNumber): array {
$url = 'https://iconic-board.polsia.app/api/v1/verify?'
. http_build_query(['credential_number' => $credentialNumber]);
$response = file_get_contents($url, false, stream_context_create([
'http' => [
'header' => "X-API-Key: $apiKey\r\n"
]
]));
return json_decode($response, true)['result'] ?? [];
}
function iconic_is_active(string $apiKey, string $credentialNumber): bool {
$result = iconic_verify($apiKey, $credentialNumber);
return !empty($result['found']) && $result['status'] === 'active';
}
// Usage:
$active = iconic_is_active('IBC_your_key', 'IBC-12345');
if ($active) { echo 'Credential verified ✓'; }
cURL — all endpoints
API_KEY="IBC_your_api_key"
BASE="https://iconic-board.polsia.app/api/v1"
# 1. Single credential lookup
curl "${BASE}/verify?credential_number=IBC-12345" \
-H "X-API-Key: ${API_KEY}"
# 2. Name + state search
curl "${BASE}/verify?name=Jane+Doe&state=AZ" \
-H "X-API-Key: ${API_KEY}"
# 3. Batch verify
curl "${BASE}/verify/batch" \
-H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"credentials":["IBC-001","IBC-002","IBC-003"]}'
# 4. API status (no auth needed)
curl "${BASE}/status"
# 5. Anonymous (no key, 10 req/day limit)
curl "${BASE}/verify?credential_number=IBC-12345"
Ready to start verifying?
Get your free API key — no credit card required, no waiting.
Get Free API Key →