Software Guides

Pre-Authorization API for Nigerian HMOs: Approve or Deny PA Requests With a PA Code (2027)

How an HMO's developers read hospital pre-authorization requests and approve with a PA code or deny with a reason through the ClinikEHR API.

Back to Intelligence
Share This Dispatch

A hospital has sent your HMO (health maintenance organisation) a pre-authorization request, and someone on your team has to answer it: approve with a code, or deny with a reason. This guide shows how a Nigerian HMO's developers can do that with the ClinikEHR API (api.clinikehr.com), the API of the ClinikEHR app. It is not ClinikAPI (clinikapi.com), a separate product with its own site, keys and licence, built around the FHIR health-data standard. You will read requests, decide each one once and follow two events.

Quick Answer

To decide a pre-authorization request through the ClinikEHR API, read the request the hospital sent, then post one decision: approved with a PA code, or denied with a reason.

  • Get access first. Two Enterprise requirements apply: your HMO's workspace is on the Enterprise contract plan, and the hospital is on the Enterprise plan with API access on. You need a live key too, and an organization (your HMO's workspace in the developer portal) that a ClinikEHR reviewer has approved. The hospital must link you to its record of your HMO.
  • List the requests the hospital has sent to you, then read one for its lines, diagnoses and clinical reasons.
  • Post the decision once, with an Idempotency-Key (a unique value that makes a retry safe). An approval needs a pa_code. A denial needs a reason.
  • Keep approved_amount at or below the hospital's estimate. A second decision on the same request answers 409.
  • Listen for the events insurance.pa.requested and insurance.pa.decided, which are short notes that something changed. They carry no PA code, so read the request.

What Is a Pre-Authorization, in Plain Words

A pre-authorization (PA) asks an HMO to agree, in advance, that it will pay for something. The hospital lists the diagnoses and the items, such as a procedure or a medicine. The HMO approves against that list. If it agrees, it gives an authorization code, called a PA code. The hospital later puts that code on its claim, which is the bill it sends the HMO.

Not every HMO plan needs one. A hospital can flag an HMO plan as "Pre-authorization required", so its team knows to raise a request first. A request can name the enrollee, the member the HMO covers, and the HMO plan.

An API (application programming interface) lets one system exchange data with another. Here, your own software reads and answers the hospital's requests. This guide describes the product only. It does not describe how HMOs or the National Health Insurance Authority (NHIA) handle prior approval in Nigeria, because it relies on the product's own documentation. ClinikEHR does not connect to NHIA.

What You Need Before the First Request

Reading a hospital's requests takes more than an API key. Two Enterprise requirements apply, and they sit on different sides. First, your HMO's own developer workspace must be on the Enterprise contract plan. ClinikEHR sets that plan on request, and there is no self-serve checkout. Second, the hospital must be on the Enterprise plan with API access switched on, because an HMO's organization key reaches the hospital's insurance data (requests, claims and enrollees) only then.

Neither is a condition for HMO billing inside the ClinikEHR app. A hospital bills its HMOs in the app on any plan once its owner sets Insurance to Outside the US, under Settings, General, Insurance. It is a setting, not a country. An API call about a hospital on the US setting is refused with a 409 and the code insurance_region_unsupported.

The routes and their documentation are published at docs.clinikehr.com. This guide follows that documentation as read on 14 October 2026. A ClinikEHR reviewer decides on access, the documentation states no timeline, and each hospital chooses whether to link you. A hospital cannot simply invite an HMO, because each HMO needs its own workspace and approval first.

Use this table as a checklist. The rows run in the order they usually happen.

WhoWhat has to be trueWhere
Your Admin or OwnerWorkspace verified: a domain check and a business profileDeveloper portal
Your Admin or OwnerAPI Terms of Use accepted (no live key until then)Developer portal, Agreements
Your HMO and ClinikEHRWorkspace on the Enterprise contract plan, set on requestContact us to go live
ClinikEHR reviewerYour organization approved for Insurer claims, authorizations and enrolleesDeveloper portal, Settings, Verification
Your OwnerData-protection addendum accepted, version 2026-10-09Developer portal, Agreements
Hospital ownerInsurance set to Outside the USSettings, General
HospitalEnterprise plan, with Allow API access onSettings, API access
Hospital ownerHands you a one-time connection code, then approves your request with the insurance permissions (scopes) and a recorded legal basisSettings, API access
Hospital owner or managerLinks your organization to its record of your HMOSettings, API access, Insurer links
Hospital owner or managerOptional: picks ClinikEHR API as your HMO's channel, so sent requests reach your organizationThe hospital's channel setting for your HMO
Your developerCreates a live key and checks it with GET /v1/connectionsDeveloper portal

The data kind in the fourth row covers eight permissions. This guide mainly uses two of them, insurance.pa:read and insurance.pa:decide. It also names insurance.enrollees:read in Step 3 and insurance.pa:submit in the developer section. The addendum is between Bettar Platforms, Inc. and your organization. It has Nigeria Data Protection Act terms for an organization established in Nigeria or processing the information of people in Nigeria. Read it at docs.clinikehr.com/data-protection-addendum. The hospital can end the link or revoke the connection at any time, and access ends on your next request.

There is no insurance test data. A test key reaches only its own sandbox clinic, which holds no insurance data. Lists answer an empty page, and reads and writes answer 404. The rest of the API has a sandbox, but the insurance routes are tested against a linked hospital. So the one request sample here, in Step 4, is the one printed in the insurance page of the documentation. The worked example and the handler outline are labelled illustrative.

The Life of a Request

The hospital builds a request in its own screens. It lists at least one diagnosis, and items with a description, a quantity and a price each. It sets the kind of request and an urgency: routine, urgent or emergency. Its team can run an AI review, a suggestion that never changes the request. Until the request is sent, none of it reaches you. Drafts and requests under review never appear in the API.

A person at the hospital then sends the request through the channel the hospital chose for your HMO. Once it is sent, staff can no longer edit it. For you it now has the status sent_for_authorization, and the event insurance.pa.requested fires. You decide it, and the status becomes approved or denied.

StageWho actsWhat you see through the API
Draft, AI reviewed, ReviewedHospital staffNothing
SentA person at the hospitalStatus sent_for_authorization, and the event insurance.pa.requested
DecidedYou, through the APIStatus approved or denied, and the event insurance.pa.decided
Claim started from an approvalHospital staffThe claim carries the PA code (pa_code). You read it on the claim once the claim is submitted.

You read only requests sent to your HMO. A request of another insurer, an unsent request and a restricted patient's request all answer the same 404, and the API never tells them apart.

How to Decide a Request, Step by Step

Every route below starts with https://api.clinikehr.com/v1/clinics/YOUR_CLINIC_ID, except GET /v1/connections in Step 1. Send your live key as a bearer key (in the Authorization header), from your own backend. Never call the API from a browser or a phone app, and never put a key in a URL.

Step 1: Check What Your Key Reaches

What to do. Call GET /v1/connections with your live key. Each row names a clinic. Its clinic_id is the YOUR_CLINIC_ID in every later route. A row also lists the granted scopes (the permissions the hospital gave you), when the connection started and, where the hospital set one, when it needs re-approval.

What you need. A live key made in the developer portal. You choose its end date, and it can be at most 12 months away. A test key never reaches a real hospital.

How you know it worked. The hospital is in the list, and its granted scopes include insurance.pa:read and insurance.pa:decide. A hospital that revoked the connection, let it expire or switched its API access off is simply not listed.

The mistake. Treating a connection as a link. Until the hospital links your organization to its record of your HMO, you are treated as a developer. A developer can send requests for patients the hospital linked, but cannot read or decide one. A decision from a developer is refused with the code hmo_not_linked.

Step 2: List the Requests Sent to You

What to do. Call GET /insurance/pre-authorizations under the clinic path above. To see only requests that wait for you, filter on status sent_for_authorization. The status filter also takes approved, denied, expired and cancelled. You can filter on insurance_company_id too, and on updated_since, which returns only what changed after a time you give. The answer holds data, has_more and next_cursor. Pass the next_cursor you were given back as starting_after.

What you need. The insurance.pa:read permission, and your organization linked to the hospital's record of your HMO.

How you know it worked. Rows come back with the status sent_for_authorization. An empty page means nothing matches your filters.

The mistake. Building a cursor yourself. A cursor is a marker for the next page, and you cannot build one, so only ever pass back the one you were given.

Step 3: Read One Request Before You Decide

What to do. Call GET /insurance/pre-authorizations/PA_ID. The single read carries what the list leaves out: the lines (items) and the diagnoses. At request level you get the request_number, the request_type (service, procedure, medication, equipment, hospitalization, consultation or lab_test), the priority_level, the estimated_cost, the enrollee_id and the insurance_plan_id. Each line has a description, a tariff code, a quantity, a unit cost, a total cost, a diagnosis and the clinical reason the hospital gave. A tariff code is the code in the price list agreed with the hospital.

To see what the hospital recorded for the enrollee, call GET /insurance/enrollees/ENROLLEE_ID, using the enrollee_id from the request. It reads the record the hospital holds, so it is not live eligibility, and you cannot create or change an enrollee.

What you need. The insurance.pa:read permission. The enrollee route needs insurance.enrollees:read.

How you know it worked. The request has the status sent_for_authorization, with its lines and diagnoses.

The mistake. Deciding from a list row alone. A list row has no lines, so you have not seen what you are approving.

Step 4: Approve With a PA Code

What to do. Post one decision to POST /insurance/pre-authorizations/PA_ID/decision. Set decision to approved and give a pa_code. You may add an approved_amount. The pa_code is free text of up to 100 characters. The documentation prints AUTH-20261009-001 as a sample and states no format. If you send no approved_amount, it defaults to the hospital's estimated cost. It can never be higher than that estimate.

This is the sample from the documentation, with its example key. It works only with a live key against a hospital that has linked your organization.

curl -X POST "https://api.clinikehr.com/v1/clinics/YOUR_CLINIC_ID/insurance/pre-authorizations/PA_ID/decision" \
  -H "Authorization: Bearer ehr_live_EXAMPLEKEYID0000000000_EXAMPLESECRET…" \
  -H "Idempotency-Key: 0c6a4a56-6d0b-4a7d-8b53-6f2f0c9d1e22" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approved", "pa_code": "AUTH-20261009-001", "approved_amount": 400 }'

An idempotency key is a unique value you make up for one action, and a UUID (a random unique ID) is a good choice. Send the same key again and the API returns the first answer instead of doing the work twice.

What you need. The insurance.pa:decide permission, and the link from Step 1.

How you know it worked. Read the request again. Its status is approved, and it carries your pa_code and the approved_amount. If you replay the call with the same key, the answer says idempotent_replay: true.

The mistake. Copying the sample. The key and the code in it are examples. Use your own live key and a PA code of your own.

Step 5: Or Deny With a Reason

What to do. Post to the same decision route with decision set to denied and a reason of up to 1,000 characters. A denial with no reason is refused. Give a reason in plain words that the hospital's team can act on.

To mark single lines, send items, which holds up to 500 entries. Each entry has an item_id, approved (true or false) and a reason. A line you leave out follows the decision. The decision itself is still approved or denied. The API has no "partly approved" decision.

What you need. The same permission and link as Step 4. No pa_code is needed for a denial.

How you know it worked. Read the request again. Its status is denied.

The mistake. Sending a denial with no reason. It is refused, so have the reason ready before you post.

Step 6: Decide Once and Retry Safely

What to do. Treat each decision as final. A request is decided once, and the API only decides a request that is still sent_for_authorization. A second decision answers 409 with the code invalid_status_transition. Generate the Idempotency-Key once for each decision, and reuse the same key on every retry. Your code branches on the code of an error, never on its detail text.

What you need. A place to keep the key until the call has succeeded.

How you know it worked. A retry with the same key returns the first answer, and nothing is decided twice. The errors table below lists the two other 409 codes a key can cause.

The mistake. Making a new key for every retry. If the first call had worked, the retry looks like a second decision and answers invalid_status_transition.

A Worked Example (Illustrative)

These figures are invented to show the rules. They are not data. The hospital sent a request with an estimated cost of 400, in its own currency.

What you postapproved_amountWhat happens
Approved with code AUTH-20261009-001noneApproved at 400, the estimate
Approved with the same code300Approved at 300
Approved with code AUTH-20261009-001450Refused, because 450 is above the estimate
Denied with the reason "Not covered by the plan"noneDenied
Denied with no reasonnoneRefused
A denial with a new key, after the approvalnone409 invalid_status_transition

Step 7: Follow the Two Events

What to do. A webhook is a delivery that ClinikEHR pushes to an https address you register. Register one on the developer portal's Webhooks page. You need the Admin role or higher there. You then get insurance.pa.requested when a request is sent to the insurer and insurance.pa.decided when a request becomes approved or denied.

EventIt fires whenPermission
insurance.pa.requestedA request reaches sent_for_authorizationinsurance.pa:read
insurance.pa.decidedA request becomes approved or deniedinsurance.pa:read

The data in the event carries only the pa_id, the request_number, the insurance_company_id, the status, the estimated_cost and the approved_amount. It has no patient name and no PA code. So treat an event as a signal, then read the request. An event says a request was sent or decided. It does not say who decided it.

Each delivery is signed. Three headers come with it: webhook-id, webhook-timestamp and webhook-signature. The signature is an HMAC-SHA256 over the id, the timestamp and the raw body, joined by dots. Use the raw body, compare in constant time, and refuse a delivery whose timestamp is far off. The signing secret looks like whsec_ followed by 48 hex characters, and it is shown once, when you create or rotate the endpoint. The signature guide has verifiers you can copy.

If your endpoint was down, catch up with the change feed: GET /v1/clinics/YOUR_CLINIC_ID/events, with after_seq and a limit of 1 to 200. Events are kept for 30 days, and a clinic has events only once someone is listening. Process each event once, by its seq or its webhook-id. The events page explains the feed.

What you need. The https address must be reachable from the public internet. You see an event only for your own HMO's requests, and only while your connection grants insurance.pa:read.

How you know it worked. A delivery reaches your endpoint with a signature that checks out, and reading the request named by its pa_id gives you the current state.

The mistake. Waiting for the PA code to arrive inside the event. It never does. Read the request.

A Handler Outline (Illustrative)

This outline describes the steps in words. The documentation prints no sample event body, so none is shown here.

  1. On each delivery, check the signature and the timestamp, and skip a webhook-id or seq you have already handled.
  2. Read the request named by pa_id.
  3. For insurance.pa.requested, check the request against your own plan rules, then post the decision with a new Idempotency-Key.
  4. For insurance.pa.decided, read the request to see its status, its pa_code and its approved_amount.

Using the Client Libraries

The ClinikEHR API has two published client libraries: @clinikehr/api on npm and clinikehr on PyPI. They are optional. Use version 0.1.3 or later of the TypeScript client.

npm install @clinikehr/api

The TypeScript client needs Node.js 18 or later and runs on your own server. It refuses to run in a web browser. This is the documented way to create it.

import { createClient } from "@clinikehr/api";

const client = createClient({ apiKey: process.env.CLINIKEHR_API_KEY! });

The client puts a fresh Idempotency-Key on every POST and reuses the same key if it retries. If your own queue may redeliver a message, pass your own key with idempotencyKey, so both attempts count as one. For the exact call to a route, follow the TypeScript guide.

pip install "clinikehr>=0.1,<0.2"

The Python client needs Python 3.11 or later. Its calls are described in the Python guide.

What the Hospital Sees After You Decide

The decision shows on the request, with the authorization code. A decision that arrives through the API lands in the same place as one staff record by hand. From an approved request, staff can choose Create a claim. It opens a claim with the patient, the HMO, the code and the lines you approved, at the approved amounts, for staff to review. A claim carries one authorization.

If a claim line is more than you approved for it, or is not an approved line, the claim form says so and by how much. It changes nothing. The hospital then decides whether to ask you or to bill the difference to the patient. When your HMO later checks and decides that claim, it can send pa_code to tie the claim to the authorization it issued. The hospital's side, from member records to claims, is in the guide to getting paid by HMOs in Nigeria.

What the API Does Not Do

  • It does not send a request to your HMO by itself. A person at the hospital sends it, through the channel the hospital chose.
  • It does not score a request or decide one for you. The decision is yours.
  • It moves no money. An approval is a decision on a request, and payment happens outside the API.
  • The decision has no field for a start or end date. The API can neither set nor show one.
  • It does not show drafts, requests under review or another insurer's requests.

If You Are a Developer, Not an Insurer

A developer organization is one a hospital has connected but linked to no insurer. The route to send a request is POST /insurance/pre-authorizations under the clinic path, with the permission insurance.pa:submit and an Idempotency-Key. The request is saved as sent_for_authorization, and the API answers 201. An organization linked to an insurer that tries to send one is refused with role_required.

Errors You May Meet

These are the codes that matter for a decision.

CodeStatusWhat it means
hmo_not_linked403The hospital has not linked your organization to an insurer, or you are a developer or the clinic
not_found404The request does not exist, is another insurer's, was not sent to you, or belongs to a restricted patient
invalid_status_transition409The request is already decided
insurance_region_unsupported409The hospital uses the US insurance setup
idempotency_key_conflict409You reused a key for a different request
idempotency_key_reused409You retried while the first call was still running
not_verified403A live organization key was used while the workspace is not verified
workspace_plan_required403A live organization key was used while the workspace is still on the Sandbox plan
rate_limited429You are over the per-minute limit, and a Retry-After header says when to try again

Three more codes mean an approval or an accepted addendum is missing: approval_required, kind_not_approved and agreement_required. The errors page describes them.

Mistakes That Cost an HMO Time or Trust

  1. Treating an approval as a promise to pay. An approval removes the commonest reason for refusal, not every one. The claim still has to match the approval on items, amounts and dates.
  2. Assuming the API is the only way a request gets decided. Read the request's current status before you post. Staff can record a decision by hand.
  3. Reusing a PA code. The documentation does not say whether the API refuses a code already used on another request, and the hospital's own screen says each code belongs to one request. Issue a new code for each request.
  4. Polling instead of listening. Every read counts against your per-minute limit and your monthly allowance. Webhook deliveries are not counted. Use events as the normal way, and keep reads for catching up.
  5. Treating all requests alike. The hospital marks each request routine, urgent or emergency. Show the priority_level to the people who decide, and sort on it.
  6. Using what you read for another purpose. The addendum limits an HMO to using the information to administer that insurer's cover for the clinic's patients. Read it with your own legal adviser. This guide is not legal advice.

Frequently Asked Questions

What is a pre-authorization API for HMOs?

It is a set of routes that lets an HMO's own software read the pre-authorization requests a hospital has sent and answer each one. In the ClinikEHR API (api.clinikehr.com), the HMO lists requests, reads one, and posts a decision: approved with a PA code, or denied with a reason. A person at the hospital sends each request.

What does an HMO need before it can read a hospital's requests?

Two Enterprise requirements apply, and they are separate from HMO billing in the app. The HMO's developer workspace must be on the Enterprise contract plan, which ClinikEHR sets on request. The hospital must be on the Enterprise plan with API access on, and its owner must set Insurance to Outside the US. HMO billing inside the app needs no Enterprise plan. The HMO also needs a verified workspace, reviewer approval for the insurer data kind, an accepted addendum, and a hospital that connects and links it.

How does an HMO approve or deny a request through the API?

It posts one decision to the request's decision route, with an Idempotency-Key. An approval needs a pa_code of up to 100 characters. A denial needs a reason of up to 1,000 characters. The key needs the insurance.pa:decide permission, and the HMO's organization must be linked to the hospital's record of that insurer.

What does approved_amount do?

It sets the amount approved, and it applies to approvals only. If the HMO sends none, it defaults to the hospital's estimated cost. It cannot exceed that estimate. In an illustrative request with an estimate of 400, an approval for 300 is accepted and an approval for 450 is refused.

Can a decision be changed after it is posted?

No. A request is decided once, and a second decision answers 409 with the code invalid_status_transition. Sending the same decision again with the same Idempotency-Key is different. It returns the first answer and decides nothing twice.

Does the insurance.pa.decided event carry the PA code?

No. The event carries the request's id and number, the insurer's id, the status, the estimated cost and the approved amount. It has no patient name and no PA code. Treat the event as a signal, then read the request to get the code.

Can I try the pre-authorization routes with a test key?

No. A test key reaches only its own sandbox clinic, which holds no insurance data. Lists answer an empty page, and reads and writes answer 404. Trying the routes takes a live key and a hospital that has connected and linked your organization.

How does the hospital see the decision and the PA code?

The decision shows on the request, with the authorization code. A claim started from an approval carries the same code, the patient, the HMO and the approved lines at the approved amounts. If a claim line is above what was approved, the claim form says so and by how much, and changes nothing. An approval is not a promise of payment.

Conclusion

Deciding a pre-authorization through the ClinikEHR API comes down to four moves. Read the request the hospital sent. Decide it once, with a PA code or a reason. Send an Idempotency-Key with the decision. Read the request again when an event arrives.

To connect your HMO, open the developer portal and read the insurance page of the documentation first. To ask about the Enterprise contract plan for your workspace, contact ClinikEHR.

Connect your HMO through the developer portal

Create a developer workspace to begin. A ClinikEHR reviewer approves insurer access; the hospital chooses to link you.
Open the developer portal →
★★★★★4.9/5 Rating
No credit card required to start

Related Articles

Facts are as of the dates shown. Check the linked sources for updates. This is general information, not legal or professional advice.

Stay in the loop

Subscribe to our newsletter for the latest updates on healthcare technology, HIPAA compliance, and exclusive content delivered straight to your inbox.

Weekly updates
Healthcare insights
HIPAA updates
Subscribe to our Newsletter
Join over 100,000 healthcare professionals

We respect your privacy. Unsubscribe at any time.