📘 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

https://iconic-board.polsia.app

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

curl
curl "https://iconic-board.polsia.app/api/v1/verify?credential_number=IBC-12345" \
  -H "X-API-Key: IBC_your_api_key_here"

3. Parse the response

json 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)

http header
X-API-Key: IBC_your_api_key_here

Query parameter (alternative)

url
GET /api/v1/verify?credential_number=IBC-12345&api_key=IBC_your_key
⚠️

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:

json — 429 response
{
  "success": false,
  "error": "RATE_LIMIT_EXCEEDED",
  "message": "Daily limit of 100 reached. Upgrade at .../developers",
  "limit": 100,
  "resets": "midnight UTC"
}

📋 Endpoints

GET
/api/v1/verify
Verify a single credential by number, or search by practitioner name and state.
Parameters
Example
Response
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
JavaScript
Python
# By credential number
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"
const headers = { '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
import requests

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

{ "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" } }

404  Credential not found

{ "success": false, "found": false, "credential_number": "IBC-99999",
  "message": "Credential not found in ICONIC Board registry." }
POST
/api/v1/verify/batch
Verify multiple credentials in a single request. Requires an API key. Batch size limit depends on tier.
Request Body
Example
Response
FieldTypeRequiredDescription
credentials string[] Required Array of credential numbers. Max: 10 (free), 50 (standard), 100 (enterprise).
cURL
JavaScript
Python
PHP
curl https://iconic-board.polsia.app/api/v1/verify/batch \
  -H "X-API-Key: IBC_your_key" \
  -H "Content-Type: application/json" \
  -d '{"credentials":["IBC-001","IBC-002","IBC-003"]}'
const response = await fetch(
  '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`);
import requests

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")
<?php
$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"
}
GET
/api/v1/status
API health check. No authentication required. Use to verify connectivity.
// Response
{
  "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

FieldTypeDescription
foundbooleanAlways present. true if credential exists in registry.
credential_numberstringICONIC credential number (e.g. IBC-12345).
statusstringOne of: active, expired, revoked, suspended.
tierstring | nullFull credential type name.
tier_codestring | nullShort credential type code (e.g. CHHP).
practitionerobjectObject with name, state, country.
issued_atISO 8601 | nullDate credential was issued.
expires_atISO 8601 | nullDate credential expires (or expired).
endorsementsarrayArray of issued endorsement objects with name.
verified_atISO 8601Timestamp of this API response.
sourcestringAlways "ICONIC Board of Holistic Health".

🔴 Credential Statuses

The status field can be one of four values:

StatusMeaningRecommended 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:

{ "success": false, "error": "ERROR_CODE", "message": "Human-readable description" }
400
MISSING_PARAMS
Neither credential_number nor name was provided.
400
BATCH_LIMIT_EXCEEDED
Batch size exceeds your tier's maximum. Upgrade for larger batches.
401
INVALID_API_KEY
The API key provided does not exist.
403
KEY_INACTIVE / KEY_REVOKED
API key exists but is not active or has been revoked.
429
RATE_LIMIT_EXCEEDED
Daily request limit reached. Resets at midnight UTC.
500
SERVER_ERROR
Unexpected server error. Contact api@iconicboard.org if this persists.

🔔 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

json — webhook payload
{
  "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

http headers
Content-Type: application/json
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>.

Node.js
Python
PHP
const crypto = require('crypto');

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();
});
import hmac, hashlib
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
<?php
$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

EventTrigger
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

javascript
class IconicVerifier {
  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

python
import requests

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

php
<?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

bash
# Set your key once
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 →