Welcome to the unofficial PHP SDK for the CLAIM.MD API! π This library aims to simplify interactions with the official CLAIM.md API, providing a more developer-friendly way to integrate CLAIM.md services into your PHP applications.
Nextvisit Inc. is not affiliated with CLAIM.MD in any way. This package is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. Use at your own risk.
That being said, if you encounter any issues or have suggestions for improvement, feel free to open an issue or contribute to the package. π
This library wraps all 16 service endpoints in Claim.MD API v1.19 and parses webhook payloads.
- List Received ERAs: Get a list of all ERAs that have been received.
- Get ERA 835: Get a specific ERA in the 835 format.
- Get ERA PDF: Get a specific ERA in the PDF format.
- Get ERA JSON: Get a specific ERA in the JSON format.
- Upload Files: Upload batch files to the CLAIM.md service.
- Get Upload List: Retrieve a list of uploaded files.
- Enroll Providers: Enroll providers using detailed enrollment data.
- Fetch Provider Enrollment: If a provider is already enrolled, that information will be
received from the same
enroll()method.
- Claim Appeal: Submit and manage claim appeals.
- Fetch Claim Notes: Retrieve notes made on a specific claim.
- Archive Claim: Archive one or more Claim.MD claims.
- Download Claims as 837: Download claims by transmission date with page iteration.
- Claim Modifications: Retrieve modifications for claims.
- Fetch Responses: Retrieve claim statuses (referred to as "responses" in the Claim.MD API) from the Claim MD API.
- Fetch All Responses: Automatically handle pagination to retrieve all claim statuses.
- Realtime Eligibility X12 270/271: Validate and check the eligibility of X12 270/271 formatted claim. Receiving the response in the format as well.
- Realtime Eligibility JSON: Validate and check the eligibility of a claim via parameters. Receiving the response in JSON format.
- Parse Webhook Payload: Parse inbound webhook events for provider enrollment updates and appeal form creation/updates.
- Fetch Payers: Retrieve a list of payers or a specific payer.
DTOs can make passing data to and from cleaner in your code. Consider using them over a traditional array.
Note: All methods which use a DTO can instead take an array with correct Claim.MD API mappings.
- Configuration Handling: Easily configure the client with account keys and other settings.
- Validation: Built-in validation for fields like dates, emails, phone numbers, state codes, etc.
Requires PHP 8.3 or later with the JSON extension. Composer accepts Guzzle ^7.15.5 || ^8.1.
You can install the package via Composer:
composer require nextvisit/claim-md-phpFirst, configure the Client with your account key:
use Nextvisit\ClaimMD\Client;
use Nextvisit\ClaimMD\Config;
$accountKey = 'your-account-key'; // Never hardcode your keys!
$config = new Config();
$client = new Client($accountKey, $config);HTTP 200 responses with a non-empty top-level error now throw ApiException.
The exception stores the HTTP status and complete response body. Claim.MD error codes are returned separately as strings.
Both object and list errors are supported, including error_mesg and error_message.
use Nextvisit\ClaimMD\Exceptions\ApiException;
use Nextvisit\ClaimMD\Requests\ClaimRequest;
$claimRequest = new ClaimRequest($client);
try {
$response = $claimRequest->archive('claim-id');
} catch (ApiException $e) {
$httpStatus = $e->getStatusCode();
$apiCodes = $e->getApiErrorCodes();
$errors = $e->getApiErrors();
$responseBody = $e->getResponseBody();
$message = $e->getMessage();
}HTTP 401, 404, 429, and 5xx responses keep their specific exception classes.
Network failures throw Guzzle exceptions. Per-claim status messages remain part of the returned data.
sendRequest() returns JSON arrays. sendX12Request() returns X12 bytes and handles JSON error bodies through the same exceptions.
use Nextvisit\ClaimMD\Requests\ERARequest;
use Nextvisit\ClaimMD\DTO\ERADTO;
$eraRequest = new ERARequest($client);
// optionally pass an array or ERADTO with at least the ERA ID to get remits since then.
$eraDto = new ERADTO(eraId: 'era-id');
$recentResponse = $eraRequest->getList($eraDto);
$allResponse = $eraRequest->getList();use Nextvisit\ClaimMD\Requests\ERARequest;
$eraRequest = new ERARequest($client);
$response = $eraRequest->get835('era-id');use Nextvisit\ClaimMD\Requests\ERARequest;
$eraRequest = new ERARequest($client);
// optional PCN can be used after the era-id.
$pcnResponse = $eraRequest->getPDF('era-id', 'pcn');
$regularResponse = $eraRequest->getPDF('era-id');use Nextvisit\ClaimMD\Requests\ERARequest;
$eraRequest = new ERARequest($client);
$response = $eraRequest->getJson('era-id');use Nextvisit\ClaimMD\Requests\FileRequest;
$fileRequest = new FileRequest($client);
$response = $fileRequest->upload(fopen('path/to/your/file.txt', 'r'));
print_r($response);use Nextvisit\ClaimMD\Requests\FileRequest;
$fileRequest = new FileRequest($client);
$response = $fileRequest->getUploadList();use Nextvisit\ClaimMD\Requests\ProviderRequest;
use Nextvisit\ClaimMD\DTO\ProviderEnrollmentDTO;
// If this provider is already enrolled, the response will contain that information.
$providerEnrollment = new ProviderEnrollmentDTO(
payerId: 'payer-id',
enrollType: 'era',
provTaxId: 'provider-tax-id',
provNpi: 'provider-npi',
provNameLast: 'Doe',
provNameFirst: 'John',
contactEmail: 'john.doe@example.com'
);
$providerRequest = new ProviderRequest($client);
$response = $providerRequest->enroll($providerEnrollment);For an organization without an NPI, supply its name and set isOrganization: true:
$organization = new ProviderEnrollmentDTO(
payerId: 'payer-id',
enrollType: 'era',
provTaxId: '123456789',
provNameLast: 'Example Clinic',
isOrganization: true
);
$response = $providerRequest->enroll($organization);The flag defaults to false and is excluded from the API payload. Individuals without an NPI still need both names.
For an API field array, use ProviderEnrollmentDTO::fromArray($data, isOrganization: true).
use Nextvisit\ClaimMD\DTO\ClaimAppealDTO;
use Nextvisit\ClaimMD\Requests\ClaimRequest;
$appealDto = new ClaimAppealDTO(
claimId: 'claim-id',
remoteClaimId: 'remote-claim-id',
contactName: 'Jane Doe',
contactEmail: 'jane.doe@example.com',
contactPhone: '+1234567890'
);
$claimRequest = new ClaimRequest($client);
$response = $claimRequest->appeal($appealDto);use Nextvisit\ClaimMD\Requests\ClaimRequest;
$claimRequest = new ClaimRequest($client);
$response = $claimRequest->archive('claim-id');
$bulkResponse = $claimRequest->archive(['claim-id-1', 'claim-id-2']);Bulk requests send repeated claimid fields. Pass a non-empty list of non-empty strings.
Use 1500 for 837P, ub for 837I, or dental for 837D. Dates use YYYY-MM-DD and pages start at zero.
Each request returns up to 1,000 claims as X12 bytes. The optional billing NPI, tax ID, and payer fields filter the results.
use Nextvisit\ClaimMD\Requests\ClaimRequest;
$claimRequest = new ClaimRequest($client);
$x12 = $claimRequest->downloadTransmittedClaims(
transmitDate: '2026-08-19',
claimForm: '1500',
billNpi: '1234567890',
billTaxId: '123456789',
payerId: 'payer-id',
page: 0
);
file_put_contents('claims.837', $x12);
foreach ($claimRequest->downloadAllTransmittedClaims('2026-08-19', '1500') as $page => $x12) {
file_put_contents("claims-{$page}.837", $x12);
}The single-page method throws ApiException for code 711 when no claims remain.
The generator stops on that code and throws other errors. Each yielded value is a complete 837 file.
Claim.MD generates files from current claim data, so later edits can appear in the download. Claim download documentation
use Nextvisit\ClaimMD\Requests\ClaimRequest;
$claimRequest = new ClaimRequest($client);
$modId = 'mod-id'; // List modifications that have occurred after modId by specifying.
$claimMdId = 'claimmd-id'; // Get responses for a specific claim.
$field = 'some-field'; // Specify changes to a particular field.
$specifiedResponse = $claimRequest->listModifications($modId, $claimMdId, $field);
// list all modifications to all claims
$allResponse = $claimRequest->listModifications();use Nextvisit\ClaimMD\Requests\ClaimRequest;
$claimRequest = new ClaimRequest($client);
// specify notesId to get notes since that note.
$noteId = 'note-id';
// specify claimId to get notes on just that claim.
$claimId = 'claim-id';
$specifiedResponse = $claimRequest->notes($noteId, $claimId);
// list all notes on all claims
$allResponse = $claimRequest->notes();use Nextvisit\ClaimMD\Requests\ResponseRequest;
$responseRequest = new ResponseRequest($client);
// Get responses since responseId
$responseId = 'response-id';
$recentResponse = $responseRequest->fetchResponses($responseId);use Nextvisit\ClaimMD\Requests\ResponseRequest;
$responseRequest = new ResponseRequest($client);
// optionally pass the claimmd-id to get responses for that id
// Note per the ClaimMD API "Do not use this option for periodic status updates".
foreach ($responseRequest->fetchAllResponses() as $response) {
// do something with the response
print_r($response);
}use Nextvisit\ClaimMD\Requests\EligibilityRequest;
$eligibilityRequest = new EligibilityRequest($client);
$eligibility270 = fopen('path/to/your/file.270', 'r');
// The response array contains the 271 response.
$realtimeResponse = $eligibilityRequest->checkEligibility270271($eligibility270);use Nextvisit\ClaimMD\Requests\EligibilityRequest;
use Nextvisit\ClaimMD\DTO\EligibilityDTO;
$eligibilityRequest = new EligibilityRequest($client);
// build an EligibilityDTO or pass an existing array
$eligDto = new EligibilityDTO(
insLastName: 'Doe',
insFirstName: 'Jane',
payerId: 'payer-id',
// 18 or G8 are the only valid codes
patientRelationship: '18',
serviceDate: '20220203',
providerNpi: 'provider-npi',
providerTaxId: 'provider-tax-id'
);
// response will be in the response array
$response = $eligibilityRequest->checkEligibilityJSON($eligDto);use Nextvisit\ClaimMD\DTO\WebhookPayloadDTO;
// Parse directly from the raw JSON request body
$json = file_get_contents('php://input');
$webhook = WebhookPayloadDTO::fromJsonString($json);
// Or from a decoded array
$webhook = WebhookPayloadDTO::fromArray($decodedData);
// Access top-level fields
$webhook->utcTime; // UTC current time
$webhook->acctNumber; // Claim.MD Account Number
$webhook->remoteAcctNumber; // Customer assigned account number
// Iterate over events
foreach ($webhook->events as $event) {
$event->eventId; // Unique event identifier
$event->eventType; // "enroll" or "appeal"
$event->eventTime; // UTC time of event
if ($event->eventType === 'enroll') {
$event->enroll->enrollId; // Enrollment ID
$event->enroll->event; // "enrolled", "received", "completed", "rejected"
$event->enroll->enrollType; // "era", "1500", "ub", "elig", "attach"
$event->enroll->provNpi; // Provider NPI
$event->enroll->payerId; // Payer ID
}
if ($event->eventType === 'appeal') {
$event->appeal->appealId; // Appeal ID
$event->appeal->event; // "created", "mailed", "update", "faxed", "transmitted", "failure"
$event->appeal->appealType; // "electronic", "mail", "fax", "download"
$event->appeal->claimId; // Associated Claim.MD claim ID
$event->appeal->remoteClaimId; // User-assigned claim ID
$event->appeal->serviceFee; // Service fees
$event->appeal->pages; // Number of pages
}
}use Nextvisit\ClaimMD\Requests\PayerRequest;
$payerRequest = new PayerRequest($client);
// specify a specific payer
$payerId = 'payer-id';
$specifiedPayerResponse = $payerRequest->listPayer(payerId: $payerId);
// specify a general payer name search
$payerName = 'payer-name';
$searchResponse = $payerRequest->listPayer(payerName: $payerName);
// get all payers
$allResponse = $payerRequest->listPayer();use Nextvisit\ClaimMD\DTO\ClaimAppealDTO;
$claimAppealDto = new ClaimAppealDTO(
claimId: '12345',
remoteClaimId: 'remote-claim-123',
contactName: 'John Doe',
contactTitle: 'Claims Manager',
contactEmail: 'john.doe@example.com',
contactPhone: '+1-555-123-4567',
contactFax: '+1-555-765-4321',
contactAddr1: '123 Main St',
contactAddr2: 'Suite 456',
contactCity: 'Los Angeles',
contactState: 'CA',
contactZip: '90001'
);
// or construct from an array
$data = [
'claimid' => '12345',
'remote_claimid' => 'remote-claim-123',
'contact_name' => 'John Doe',
'contact_title' => 'Claims Manager',
'contact_email' => 'john.doe@example.com',
'contact_phone' => '+1-555-123-4567',
'contact_fax' => '+1-555-765-4321',
'contact_addr_1' => '123 Main St',
'contact_addr_2' => 'Suite 456',
'contact_city' => 'Los Angeles',
'contact_state' => 'CA',
'contact_zip' => '90001',
];
$claimAppealDto = ClaimAppealDTO::fromArray($data);use Nextvisit\ClaimMD\DTO\ProviderEnrollmentDTO;
$providerEnrollmentDto = new ProviderEnrollmentDTO(
payerId: 'payer-123',
enrollType: 'era',
provTaxId: '123456789',
provNpi: '9876543210',
provNameLast: 'Doe',
provNameFirst: 'John',
provNameMiddle: 'M',
contact: 'Jane Smith',
contactTitle: 'Billing Manager',
contactEmail: 'jane.smith@example.com',
contactPhone: '+1-555-123-4567',
contactFax: '+1-555-765-4321',
provId: 'provider-id-123',
provAddr1: '123 Main St',
provAddr2: 'Suite 100',
provCity: 'Los Angeles',
provState: 'CA',
provZip: '90001'
);
// or generate from an array
$data = [
'payerid' => 'payer-123',
'enroll_type' => 'era',
'prov_taxid' => '123456789',
'prov_npi' => '9876543210',
'prov_name_l' => 'Doe',
'prov_name_f' => 'John',
'prov_name_m' => 'M',
'contact' => 'Jane Smith',
'contact_title' => 'Billing Manager',
'contact_email' => 'jane.smith@example.com',
'contact_phone' => '+1-555-123-4567',
'contact_fax' => '+1-555-765-4321',
'prov_id' => 'provider-id-123',
'prov_addr_1' => '123 Main St',
'prov_addr_2' => 'Suite 100',
'prov_city' => 'Los Angeles',
'prov_state' => 'CA',
'prov_zip' => '90001',
];
$providerEnrollmentDto = ProviderEnrollmentDTO::fromArray($data);use Nextvisit\ClaimMD\DTO\EligibilityDTO;
$eligibilityDto = new EligibilityDTO(
insLastName: 'Doe',
insFirstName: 'John',
payerId: 'payer-123',
patientRelationship: '18',
serviceDate: '20230920',
providerNpi: '9876543210',
providerTaxId: '123456789',
insMiddleName: 'M',
serviceCode: 'SC123',
procCode: 'PC456',
insNumber: 'INS987654',
insDob: '19800101',
insSex: 'M',
patLastName: 'Smith',
patFirstName: 'Jane',
patMiddleName: 'K',
patDob: '20100101',
patSex: 'F',
provNameLast: 'Doe',
provNameFirst: 'Jane',
provTaxonomy: '207Q00000X',
provTaxIdType: 'E',
provAddr1: '123 Main St',
provCity: 'Los Angeles',
provState: 'CA',
provZip: '90001'
);
// or convert an array to DTO
$data = [
'ins_name_l' => 'Doe',
'ins_name_f' => 'John',
'payerid' => 'payer-123',
'pat_rel' => '18',
'fdos' => '20230920',
'prov_npi' => '9876543210',
'prov_taxid' => '123456789',
'ins_name_m' => 'M',
'service_code' => 'SC123',
'proc_code' => 'PC456',
'ins_number' => 'INS987654',
'ins_dob' => '19800101',
'ins_sex' => 'M',
'pat_name_l' => 'Smith',
'pat_name_f' => 'Jane',
'pat_name_m' => 'K',
'pat_dob' => '20100101',
'pat_sex' => 'F',
'prov_name_l' => 'Doe',
'prov_name_f' => 'Jane',
'prov_taxonomy' => '207Q00000X',
'prov_taxid_type' => 'E',
'prov_addr_1' => '123 Main St',
'prov_city' => 'Los Angeles',
'prov_state' => 'CA',
'prov_zip' => '90001',
];
$eligibilityDto = EligibilityDTO::fromArray($data);use Nextvisit\ClaimMD\DTO\ERADTO;
$eraDto = new ERADTO(
checkDate: '09-01-2023',
receivedDate: 'today',
receivedAfterDate: '08-01-2023',
checkNumber: '123456',
checkAmount: '1000.00',
payerId: 'payer-123',
npi: '9876543210',
taxId: '123456789',
newOnly: '1',
eraId: 'ERA12345',
page: '1'
);
// or convert an array to DTO
$data = [
'CheckDate' => '09-01-2023',
'ReceivedDate' => 'today',
'ReceivedAfterDate' => '08-01-2023',
'CheckNumber' => '123456',
'CheckAmount' => '1000.00',
'PayerID' => 'payer-123',
'NPI' => '9876543210',
'TaxID' => '123456789',
'NewOnly' => '1',
'ERAID' => 'ERA12345',
'Page' => '1',
];
$eraDto = ERADTO::fromArray($data);use Nextvisit\ClaimMD\DTO\WebhookPayloadDTO;
// Parse from a raw JSON string (e.g., webhook request body)
$json = file_get_contents('php://input');
$webhookPayload = WebhookPayloadDTO::fromJsonString($json);
// Or from a decoded array
$data = [
'UTCTime' => '2026-03-13T12:00:00Z',
'acct_number' => 'ACCT-001',
'remote_acct_number' => 'REMOTE-001',
'events' => [
[
'eventid' => 'EVT-001',
'event_type' => 'enroll',
'event_time' => '2026-03-13T11:00:00Z',
'event_data' => [
'enroll' => [
'enrollid' => 'ENR-001',
'event' => 'enrolled',
'enroll_type' => 'era',
'prov_npi' => '1234567890',
'payerid' => 'PAYER-001',
],
],
],
],
];
$webhookPayload = WebhookPayloadDTO::fromArray($data);Pushes to main run the PHP suite and release tests. After they pass, the Release workflow reads
Conventional Commits since the latest stable tag and selects the largest SemVer bump:
| Commit | Bump |
|---|---|
fix:, perf:, revert: |
Patch |
feat: |
Minor |
! after the type/scope, or a BREAKING CHANGE: / BREAKING-CHANGE: footer |
Major |
docs:, chore:, ci:, and other types without a breaking change |
No release |
Use a Conventional Commit title when squash-merging a pull request. For example,
feat: add 837 downloads selects a minor bump and fix!: throw exceptions for API errors selects a major bump.
The workflow commits CHANGELOG.md, pushes an annotated vX.Y.Z tag, and publishes a GitHub release with the same notes.
Composer reads package versions from Git tags. Packagist has automatic updates enabled for this package.
The workflow uses the built-in GITHUB_TOKEN. Runs are serialized and skip a source commit if main has advanced.
Re-running a failed job completes a missing GitHub release after a successful tag push. The Actions Run workflow button also runs tests before publishing from main.
Preview the next release from committed history without changing files or publishing:
python3 .github/scripts/release.py --dry-runDevelopment uses Pest 4 and Mockery 1.6. Composer resolves the lockfile against PHP 8.3 to keep it installable on the minimum supported version. After code, dependency, and documentation changes are complete, add the affected tests and run:
composer test
composer validate --strict
composer check-platform-reqs
composer audit
python3 -B -m unittest discover -s .github/scripts -p 'test_*.py'Contributions are welcome! If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request.
This package is licensed under the MIT License. See the LICENSE file for more information.
