For developers
Headless. Every capability is an API call.
Today Payerworks is an engine you use beside your current clearinghouse: nothing is sent to a payer until that payer’s route opens, and none is open yet. Payerworks’ engine has no screen you have to use. Get a test key on this page, run claims, eligibility questions and claim status questions through the sandbox payers, and from your own system write 837P, 837I, 837D, 270 and 276 files and read every remittance, acknowledgment, eligibility answer and status answer as the next step.
Principles
How it works
Built on the code we bill with
The remittance reader, the claim status reading and the NPI check are the modules the billing company behind Payerworks runs on its own claims and remittances, imported, never copied. The rest, the 837P writer, the 837I and 837D writers, the claim readers and edits, the payer directory, the eligibility and denial explainers, claim tracking, enrollment, the sandbox payers and your account, is Payerworks’ own code, written for this API.
Errors you can act on
Every refusal comes back with a stable code, the path of the field, and a sentence a person can read. A rejection from a payer comes back the same way.
Honest about sending
The API builds, checks and reads every file, and the sandbox payers answer your test files end to end. It does not yet send a file to a real payer: transmission opens one payer at a time as Payerworks’ own routes go live, and until then you send on the connection you already have.
Test it without a call
A test key comes from this page and the sandbox payer answers it right away, with no meeting and no contract. A live key needs a signed business associate agreement first, because a live file carries real patients.
Answers pushed to you
Set a webhook and every answer the 837P claims sandbox makes, and every change to an enrollment request, is posted to your server as it is made, signed under the Standard Webhooks specification, so any of its libraries checks it. An event your server did not take is tried again, and every event can be listed and sent again for 30 days. Up to ten more webhook destinations, each with its own secret and its own event types, are each tried again on their own schedule for more than three days.
What is kept, and for how long
A claim, a file or a remittance you send is answered and forgotten: never stored, never logged. A batch job is the one exception you ask for by name: it keeps your file until the job finishes and its result for 24 hours after, then deletes both. What is kept is a count of your key’s calls; your webhook’s address and signing secret; each webhook event queued for you, for 30 days, which holds the sandbox’s invented answer and, from what you sent, only your claim references and your file’s control numbers, or for an enrollment event only its ids, statuses and words (up to 2,000 events a UTC day); and for 24 hours a fingerprint of each request sent with an Idempotency-Key, never the request itself. Your webhook destinations (each one’s name, address, event types and signing secret, and what was posted to it, for as long as its event is kept), the address you set for notices and your keys’ names are kept until you remove them. Enrollment is the one part of the API that keeps what you send: the providers and enrollment requests you create (names, NPIs, tax ids, contacts and notes) and the PDFs you upload for a task, because a request lives for weeks and Payerworks people work it. They are for a practice’s business facts; never put a patient’s information in them. A test key’s providers and requests expire 30 days after their last change, and its PDFs 30 days after each was uploaded, or about a day after you delete one; a PDF uploaded but never confirmed goes after a day, whatever the key. A live key’s are kept until you delete them, and the copy of a deleted live PDF for 30 days more. Apart from that, our backups hold every PDF that was there each night, a test key’s included, for 35 days, and each Sunday’s for a year.
Test end to end
The sandbox payers
Five sandboxes answer the way a payer and its front end would. Claims sent to POST /v1/sandbox/claims come back in the answer to one call as a 999, a 277CA and an 835, every file explained and, once you set a webhook, posted as events too; institutional and dental claims sent to POST /v1/sandbox/claims/837i and /837d come back the same way, with no events. Eligibility questions sent to POST /v1/sandbox/eligibility come back as a 271, claim status questions sent to POST /v1/sandbox/claim-status as a 277, and prior authorization requests sent to POST /v1/sandbox/authorizations as a 278 response, each explained, with no events. Enrollment has the fifth, the sandbox payer PWSANDBOX, below. Every file is a test file. Use invented patients only.
How the subscriber’s member ID ends decides each answer, and each sandbox reads only its own list.
Claims POST /v1/sandbox/claims
RejectedMember ID ends in
REJA member ID ending in REJ is rejected by the payer’s front end. The 277CA refuses the claim with STC A7:164:IL (the subscriber’s member number is invalid), and no remittance follows.
DeniedMember ID ends in
DENYA member ID ending in DENY is accepted on the 277CA and then denied on the 835 the way payers send a refusal: claim status 1 (processed), nothing paid, the whole charge adjusted under CO 50, and each line carrying remark N115, so only the adjustments show the refusal.
With the payerMember ID ends in
PENDA member ID ending in PEND is accepted on the 277CA and stays with the payer, so no remittance line comes back yet.
Paid, to the deductibleMember ID ends in
DEDA member ID ending in DED is accepted and paid, but most of what the payer allows is applied to the patient’s deductible (PR 1), so the payer pays only the rest, the patient owes the deductible, and the remainder of the charge is a contractual write-off (CO 45).
Paid, less a copayMember ID ends in
COPAYA member ID ending in COPAY is accepted and paid less a fixed copay the patient owes (PR 3), and the remainder of the charge is a contractual write-off (CO 45).
Refused for missing informationMember ID ends in
D16A member ID ending in D16 is accepted and then refused on the 835 for missing information: nothing is paid, every line is adjusted under CO 16 with remark M51 (a procedure code is missing or invalid), and the claim status code says processed, so only the adjustments show the refusal.
A line bundledMember ID ends in
D97A member ID ending in D97 is paid except for its last line, which the payer bundles into another service (CO 97, remark M15) and pays nothing for. A claim of one line has that line bundled, so nothing is paid.
Reversed and paid againMember ID ends in
REVA member ID ending in REV comes back as a correction: the 835 first reverses an earlier payment on the claim (claim status 22, every amount negated), then pays the claim again on a higher allowed amount, so the check carries only the difference.
Paid, with a recoveryMember ID ends in
PLBA member ID ending in PLB is paid, and its check also carries a provider-level adjustment: the payer recovers an overpayment on an earlier claim (PLB reason WO), so the check is less than the claims it pays.
Paid and forwardedMember ID ends in
XOVERA member ID ending in XOVER is paid as primary and forwarded to the patient’s secondary payer (claim status 19, the secondary named as the crossover payer, remark MA18), so it is not filed to the secondary by hand.
Information asked forMember ID ends in
RFAIA member ID ending in RFAI is accepted on the 277CA and then held for a document: the payer sends a 277 request for additional information asking for a progress note by a day thirty days later, and pays nothing until a solicited 275 answers it. Send the same claims again, to the same sandbox route (professional, institutional or dental), with that 275 in attachments_275, and the claim is paid as any other.
PaidAny other member ID
Any other member ID is accepted and paid: the payer allows part of the charge, the patient owes coinsurance on what was allowed (PR 2), the rest is a contractual write-off (CO 45), and every service line carries its share to the cent.
Eligibility POST /v1/sandbox/eligibility
Not foundMember ID ends in
NOTFOUNDA member ID ending in NOTFOUND is not found: the 271 refuses in the subscriber’s loop with AAA 75 and states no benefits.
Birth date does not matchMember ID ends in
BADDOBA member ID ending in BADDOB is found, but the birth date does not match: AAA 71 in the patient’s loop, and no benefits.
Payer cannot answerMember ID ends in
DOWNA member ID ending in DOWN reaches a payer that cannot answer: AAA 42 in the payer’s loop, follow-up R. Ask again later, unchanged.
Coverage endedMember ID ends in
INACTIVEA member ID ending in INACTIVE answers with coverage that ended last year: EB 6 on service type 30, with last year’s plan dates.
Out of network onlyMember ID ends in
OONA member ID ending in OON is active with out-of-network benefits only: every figure is stated for the out-of-network tier.
Prior authorizationMember ID ends in
AUTHA member ID ending in AUTH is active, and each service asked about requires prior authorization (EB11 Y).
Deductible metMember ID ends in
META member ID ending in MET is active with the deductible met: the remaining deductible is zero.
Another payer firstMember ID ends in
OTHERA member ID ending in OTHER is active, and another payer is primary: an EB R line names it in a 2120 loop.
ActiveAny other member ID
Any other member ID is active, with the full grid: individual and family deductible and out-of-pocket limit for the calendar year, in and out of network, each with what remains, plus coinsurance and a copay per visit on each service asked.
Each 271 the eligibility sandbox writes can be read back in the JSON grouped by plan as well: send it to POST /v1/eligibility/explain with ?include=answer&shape=plans, and the same answer is explained the same from either.
Claim status POST /v1/sandbox/claim-status
A claim status question follows the claims list, so it agrees with that claim’s 277CA and 835: the same payer claim number and the same paid amount. These endings are the status sandbox’s own, the answers a status question alone can get:
Not found yetMember ID ends in
NOFILEA member ID ending in NOFILE is not found: the payer has no claim on file for the question (A4:35). It is never a rejection.
No answer in timeMember ID ends in
SLOWA member ID ending in SLOW gets no answer in time: the payer’s system did not respond (E2:689:PR), so there is nothing to record and the question is asked again on the next business day.
Records asked forMember ID ends in
INFOA member ID ending in INFO is pended for information the payer already asked the provider for (P3:123:1P), and the answer names it: medical notes or a report (P3:297:1P).
No payment comingMember ID ends in
NOPAYA member ID ending in NOPAY is finalized with no further payment forthcoming (F4:1). The claims sandbox pays the same claim, and the answer states what it paid: nothing more is coming.
One exception: when a claim’s payment depends on its service lines (member IDs ending in D97), the status answer says the claim is finalized and leaves what was paid to the remittance, because a status question carries no lines. The claim status sandbox answers test files only: a 276 marked for production use is refused.
Prior authorization POST /v1/sandbox/authorizations
It answers from the same payer as the eligibility sandbox, and the endings the two share answer alike: a member the eligibility sandbox cannot find is not found here either, and a claim the claims sandbox denies is not certified here. These are its endings:
Not foundMember ID ends in
NOTFOUNDA member ID ending in NOTFOUND is not found: the response refuses in the subscriber’s loop with AAA 75 and reviews nothing.
Birth date does not matchMember ID ends in
BADDOBA member ID ending in BADDOB is found, but the birth date does not match: AAA 71 in the patient’s loop, and nothing reviewed.
Payer cannot answerMember ID ends in
DOWNA member ID ending in DOWN reaches a payer that cannot answer: AAA 42 in the payer’s loop, follow-up R. Send it again later, unchanged.
Member ID refusedMember ID ends in
REJA member ID ending in REJ is refused as a member ID the payer does not accept: AAA 72 in the subscriber’s loop, the same refusal the claims sandbox gives.
Not covered that dayMember ID ends in
INACTIVEA member ID ending in INACTIVE is not certified because the patient was not covered on the date of service: HCR A3 with reason 53, as the eligibility sandbox ends that coverage last year.
No authorization neededMember ID ends in
NOTREQA member ID ending in NOTREQ needs no authorization: HCR NA with reason 0H, and no certification number.
Approved in partMember ID ends in
PARTIALA member ID ending in PARTIAL is certified in part: each service asked for two or more units is approved for half of them, rounded down (HCR A2, reason 10); a service of one unit is approved in full.
Approved, other datesMember ID ends in
MODDATEA member ID ending in MODDATE is certified with changes (HCR A6): the dates approved start two weeks after the dates asked and run for thirty days.
DeniedMember ID ends in
DENYA member ID ending in DENY is not certified as not medically necessary (HCR A3, reason 0F), as the claims sandbox denies the claim.
More information asked forMember ID ends in
INFOA member ID ending in INFO is pended until the payer has more patient information (HCR A4, reason 0U), and a message names the visit notes it needs.
Pended for reviewMember ID ends in
PENDA member ID ending in PEND is pended while the first utilization review runs (HCR A4, reason 30), with an administrative reference number to follow it by.
ApprovedAny other member ID
Any other member ID is certified in total (HCR A1) with a certification number, every service approved for the units asked, effective from the first date asked for at least ninety days.
Every answer is a test interchange from the payer id PWSANDBOX. Run them in your browser.
curl "$PAYERWORKS_BASE_URL/v1/sandbox/claims" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sender_id": "SAMPLEBILLER", "claims": [ … ] }'270 and 271
Eligibility, asked and explained
Write the 270 from the JSON your system already holds, send it on your own connection, and give Payerworks the 271 that comes back, as X12 or as JSON. The answer is said per patient, in words.
- A deductible or out-of-pocket figure is read only inside its own network tier, coverage level, insurance type and period. When a payer states several, such as Medicare Part A and Part B, or last year and this year, each is said under its own type and dates, and none is dropped.
- In network and out of network are the plan’s benefit tiers as the payer stated them. A 271 does not say whether a provider participates with the plan, and the answer never says it does.
- A refusal is said as the next step by the payer’s own follow-up code: correct these fields, ask again later, or do not send it again.
- Whether the plan covered the day you asked about is read inside both the plan’s dates and the member’s own eligibility dates, from the later begin to the earlier end, so a member whose coverage ended before the plan year did is not covered after it. Where a payer states coverage separately for each insurance type, such as Medicare Part A active and Part B not, each is said with its own state.
- A figure the payer did not state is null, never zero.
- The answer may come as an X12 271, as the flat JSON list of benefits most clearinghouse APIs return, or as that JSON grouped by plan with each kind of benefit in its own list. The three forms of one answer explain the same; what the grouped shape holds that is not read is listed as not read, and an entry the sender set aside is read as the payer stated it, and said.
- A question can ask about procedures as well as service types. One that asks only about procedures asks no service type beside them, and a question that uses no procedure field is written exactly as before.
# Write the 270 from your JSON, send it on your own connection, then explain the 271 that comes back.
curl "$PAYERWORKS_BASE_URL/v1/eligibility/270" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d @question.json
curl "$PAYERWORKS_BASE_URL/v1/eligibility/explain" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile x12 answer.271 '{ x12: $x12, asked: { service_type_codes: ["30", "98"] } }')"276 and 277
Claim status, asked and explained
Write the 276 from the JSON your system holds, send it on your own connection, and give Payerworks the 277 that comes back, as X12 or as JSON. Each question is answered with where the claim stands, the money, the next step and when to ask again.
- A claim the payer cannot find in its first few business days is not loaded yet, never rejected; after that, the answer says to confirm the payer received it.
- A payer whose system did not answer is asked again the next business day. A question the payer refused because of its own data is corrected first, never asked again unchanged.
- Finalized is not the same as paid: a finalized answer that states no amount leaves the money to the remittance.
- Business days are Monday to Friday, and holidays are not counted.
- The payer’s own claim number (REF*1K) and the clearinghouse’s trace number (REF*D9) are said apart, so one is never taken for the other.
- A file holding more than one call can read is refused before it is read, naming what was counted and the limit: split it.
# Write the 276, send it on your own connection, then explain the 277 that comes back.
curl "$PAYERWORKS_BASE_URL/v1/claim-status/276" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d @inquiry.json
curl "$PAYERWORKS_BASE_URL/v1/claim-status/explain" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile x12 answer.277 '{ x12: $x12, asked: { submitted_on: "2026-10-05" } }')"837P, 999, 277CA and 835
Where a claim stands, from the files you hold
Send the claim (JSON or the 837P), and the 999s, 277CAs and 835s you hold for it. Payerworks answers where each claim stands, who said so, what was paid, and a dated timeline of every file. It reads only what you send: nothing is stored and no payer is asked.
submitted- Sent, and no acknowledgment of this submission is among the files you sent.
received- Received, and not yet accepted for adjudication: a receipt is not an acceptance.
accepted- Accepted into the payer's adjudication system, and being processed.
information_requested- The payer asked for more information before it decides the claim (a 277 request for additional information), and no 275 among the files you sent answers it.
rejected- Rejected before adjudication: the payer never decided it, and no remittance follows a rejection.
processed- Adjudicated: the payer accepted responsibility for the claim. That is not the same as paid in full, or paid at all.
denied- Adjudicated and denied: the payer decided it is not responsible for the claim.
unknown- No single state can be read from the files: the answers are mixed or incomplete, or use codes a claim acknowledgment does not carry.
- A remittance decides first; then the acknowledgments, a rejection before anything else and the payer’s word before a clearinghouse’s; then the newest date. A late receipt never hides a rejection.
- An accepting 999 moves nothing: it says the file was read, not that the claim was taken.
- Only the latest submission of a claim counts, and a corrected claim joins the claim whose payer claim number it names.
- An 835 sent twice is counted once and named, never added to the paid total twice.
- A reversal whose original decision is not among the files you sent is named on the timeline but left out of the paid total.
curl "$PAYERWORKS_BASE_URL/v1/claims/track" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile c claims.837 --rawfile a ack.277 --rawfile r remit.835 '{ x12_837: $c, acknowledgments: [$a], remittances: [$r] }')"835 and appeals
Denials, said as the next step
Send an 835, or the claims as JSON, and each claim comes back with what the payer did and what to do about it.
- The verdict is read from the money and the adjustment codes, never the claim status code alone: a refusal usually arrives as “processed as primary” with a CO-50 or a CO-16 beside it.
- Whether the patient may be billed is said in dollars, by group: patient responsibility may be billed, a contractual adjustment may not.
- A correction is said as the fields to send on the replacement claim, and a request for records names what to send.
- A deadline is computed only from a regulation it cites (Medicare fee-for-service, by its filing indicator) or from the limits you send. Otherwise its date is null, and its words say the payer’s contract or provider manual states it.
- An appeal letter is written by template from your facts only, and a letter you edit can be checked against them before it goes.
- An underpayment letter states one of two bases, from your figures: a contracted amount above what the payer allowed, or a payment below the allowed amount less the patient’s share. With neither, no letter is written.
- A Medicare late filing (CO-29) is said as not appealable, and a Medicare duplicate (CO-18) as a reopening, each with the regulation it rests on. A Medicare filing limit counts from the earliest service line’s date, and a Medicare redetermination or reopening window from the remittance advice’s own date (DTM*405), else the payment date (BPR16).
# Every claim in an 835, said as the next step.
curl "$PAYERWORKS_BASE_URL/v1/denials/next-step" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile x12 remittance.835 '{ x12: $x12, facts: { filing_limit_days: 90 } }')"
# An appeal letter from your facts, by its kind.
curl "$PAYERWORKS_BASE_URL/v1/appeals/letter" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" -H "Content-Type: application/json" \
-d @letter.jsonFollow-up, remittances and your own numbers
After the claim is sent
Follow up on open claims, check the remittances that come back, and count what your own files show. Each answer reads only what you send in the call.
- A payer’s days to pay, its denial rate or an overturn rate is learned from your files alone, with how many it rests on, and is read as a pattern only past a floor (five paid claims, five decided appeals, two refusals of one reason). Below the floor the answer says there is not enough history. Payerworks holds no data of its own about any payer, so nothing here is a benchmark.
- A month is the calendar month; the current month counts to today.
- A claim the payer’s search did not find gets the next questions to ask in order, each built from your own question and the facts you hold, never a value nobody gave.
- Two checks are one payment only when their trace number and its originator agree, and a deposit is matched by amount and date only when exactly one check fits.
- A line with no rate in your fee schedule is said as no rate given, never read as zero, and Medicare’s published rate is labeled as Medicare’s, never as your contract.
Estimates
What a patient may owe
Estimates, a Good Faith Estimate, an equipment replacement window and a poverty guideline percent, each figured from what you send.
- An estimate never supplies an allowed amount, and a benefit the 271 did not state is said as not stated, never read as zero.
- Whether a copay replaces the deductible and coinsurance, or comes first, is plan design a 271 does not carry: you say which.
- A Good Faith Estimate is your document. Payerworks lays it out with every element the regulation lists, and issues, delivers and keeps nothing.
- The replacement window checks no Medicare record, and the poverty guideline is the 2026 table, named and dated; another year is refused.
CMS-1500, PDFs, appeal forms and the 275
Documents, forms and attachments
The paper side of the same claims and answers, drawn from what you send: the CMS-1500, a remittance laid out for reading, an eligibility answer as a summary, CMS’s own Medicare appeal forms filled in, and a claim’s attachments as an X12 275. Each PDF comes back in the answer as base64 with its SHA-256; Payerworks keeps no copy and sends nothing.
- A CMS-1500 is the claim POST /v1/claims/837p would write: the same checks refuse it the same way, and the form draws the repaired claim. Six service lines go on a form and a claim takes as many forms as it needs, each Box 28 the total of its own lines. Boxes 12 and 31 are dated only when you send signed_on.
- form: full draws CMS’s own form under the data; data_only draws the data alone, to print onto pre-printed red forms. A value too wide for its box is set smaller, never cut, and the answer names it.
- The remittance and the eligibility summary are drawings of the explain answers, POST /v1/remittances/explain and POST /v1/eligibility/explain, never a second reading: every figure is the one those routes give, and a figure the file did not state is left blank.
- The Medicare appeal forms are CMS’s published files, unaltered but for the fields typed in: the redetermination request (CMS-20027) and the reconsideration request (CMS-20033), for Original Medicare only. Nothing is signed, dated or ticked for you; the deadline comes with the regulation it is counted under, and a request past it with no reason given is warned.
- A 275 carries each attachment under the control number the claim’s PWK segment announced with transmission EL, and a control number the claim did not announce is refused. A claim that announces an attachment this way is told so when it is validated, so it does not wait for a file that never comes.
- The 275 is written as public payer companion guides show it, not from the licensed implementation guide, and the answer says so: check your payer’s own guide before you send one.
- A LOINC code you give for an attachment’s kind is checked by its check digit, and named in LOINC’s own words where Payerworks holds it.
LOINC
This material contains content from LOINC (http://loinc.org). LOINC is copyright © Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee and is available at no cost under the license at http://loinc.org/license. LOINC® is a registered United States trademark of Regenstrief Institute, Inc.
Read with a model, switched off
Paper EOBs and card photos
Two routes read a picture with a model: a paper explanation of benefits into a draft 835, and an insurance card into the payer, the member ID and the payer directory’s rows. Both are built and switched off until Payerworks turns them on, route by route (until then each answers 501 model_read_not_offered, which is not retried), and then answer test keys only until an agreement covers the model for live data.
- A paper EOB comes back as a draft, never an ERA, and nothing is posted: every value with the reader’s confidence and where on the page it was read. The payments are checked against the check total the page states by arithmetic, never by the model, and a difference is said with both figures. The draft 835 is a test interchange whatever your key, and an element the page did not print is left empty and named.
- A card is read twice, and a member ID the two reads spell differently is said. A plan type is given only beside words the card prints, and an electronic payer ID printed on the card is shown only where a payer publishes it, because one misread character is another payer’s ID. A match in the directory says the directory holds published facts about that payer, never that a claim can be sent to it.
- Send one page, or two at most, up to 1.4 MB. A read that cannot finish inside one call is refused with what to send instead; it is never kept to finish later.
- An account’s keys together may make 100 reads a UTC day on each route. A call refused for the route being off, a live key or a body with no file is not counted. Neither route takes an Idempotency-Key, so a second call is a second read.
- With a test key, send invented documents only. Nothing stops a real EOB or a real card from being sent with one, and no business associate agreement covers the model for it, so never send one. The picture is read and forgotten: never stored, never logged.
Rules, standard charges files and the sandbox over days
Your own checks
Three routes for what your own system must hold to: your own claim rules, a hospital’s standard charges file, and the claims sandbox over the days a real claim takes.
- POST /v1/edits/rules/check takes rules you write for your own payers: a field’s path, an operator from a fixed list of twenty, a value, your message and whether it is an error or a warning. Each rule is checked for the format and applied to the claims you send with it, and the same rules may be sent as rules on POST /v1/claims/validate and POST /v1/claims/837p, where one of severity error refuses the claim and 837p writes nothing. A rule fires on the fault it names, says your own words and is marked source: caller, so it never reads as a Payerworks edit.
- A rule’s pattern is a small fixed format, never code, and each call has a step budget: a rule it does not reach is listed as not checked, never as passed. Nothing is stored.
- POST /v1/price-transparency/validate holds a hospital’s standard charges file, the tall or wide CSV or the JSON, to CMS’s version 3.0.0 template and schema, read from CMS’s own repository at one commit. Each problem names its row or path, the column and the rule; Payerworks’ own advice comes as warnings, never as a fault. Nothing is hosted, published or kept.
- POST /v1/sandbox/claims/timeline spreads the claims sandbox over simulated days: the 999 after five minutes, a 277CA saying each claim was received after an hour, the payer’s own 277CA after two days and the 835 after fourteen. Send a later as_of and the cursor you were given to read what came next; a poller steps the clock, and a webhook handler is tested by posting each item’s event to it. Nothing is stored: the same seed, claims and start give the same run, and Payerworks posts none of its events.
Providers, payers and tasks
Enrollment, the way a payer asks for it
Three things stand between a provider and a payer’s files. Credentialing is the payer checking the provider. Payer enrollment is the provider joining the payer’s plans. Both are the provider’s own work with each payer, and Payerworks does neither. Transaction enrollment registers a provider to exchange one kind of file with one payer through one clearinghouse. That is what these routes ask Payerworks for. Payerworks is not yet registered with any payer, so today a request waits with Payerworks and nothing is sent; the sandbox payer runs the whole path so you can build against it now.
- Make a provider once: its name, billing NPI, tax id and contacts. Then one request per provider, payer and transaction.
- Every request says whose turn it is: yours, the provider’s, Payerworks’ or the payer’s. When it is the provider’s, its tasks say what to do, in rank order, each with its instructions, links and fields of text or a PDF. Payerworks’ own tasks are listed too, so a task held up by one of them names it.
- Every request keeps the history of each status change, and every change raises one event, numbered by sequence, so a receiver can order and dedupe them without trusting the order they arrive in.
- A request can be changed until it is sent to a payer, and canceled in any open status. Every POST takes an Idempotency-Key, so a retried create answers the same request.
The four transactions
eligibility- Eligibility and benefits: Payerworks writes the 270 and reads the 271.270/271
claim_status- Claim status: Payerworks writes the 276 and reads the 277.276/277
professional_claims- Professional claims: Payerworks writes and checks the 837P.837P
remittances- Remittances: Payerworks reads and explains the 835.835
A request for anything else is refused (transaction_not_built), because Payerworks does not yet enroll for it: institutional_claims, dental_claims, attachments, eft, coordination_of_benefits, insurance_discovery, medicare_number_lookup. 999 and 277CA acknowledgments are never enrolled on their own: they come back on the route the claim went.
Each status, and whose turn it is
draftYour turnBeing written. Only this key sees it.
You: submit, change or delete it.
payerworks_action_requiredPayerworks’ turnSubmitted; Payerworks’ turn. reason_code says why.
Payerworks staff on the desk, or the sandbox.
provider_action_requiredThe provider’s turnOne or more provider tasks are open.
You, completing the tasks in rank order.
with_payerThe payer’s turnPayerworks sent the request to the payer and follows up. Never set while the route is not open.
Payerworks staff, recording the payer’s answer.
liveNobody’s turnThe payer confirmed. Files of this kind may flow.
Payerworks staff, if the payer later ends it.
rejectedYour turnThe payer, or Payerworks before sending, refused it. rejection holds the words and the remedy.
None: fix it and file a new request.
canceledNobody’s turnEnded at your request or by Payerworks before it went live.
None.
endedNobody’s turnWas live; the payer ended the route.
None: a new request starts again.
You never write a status: no write route takes one.
Why a request is Payerworks’ turn
A request at payerworks_action_required carries reason_code, said in these words ({x12} and {payer} are the request’s):
route_not_open- Waiting on Payerworks. Payerworks is not yet registered to exchange {x12} files with {payer}, so nothing has been sent to the payer. Your request waits here, and you can change or cancel it until it is sent.
preparing- Waiting on Payerworks. Payerworks is preparing what the payer asks for.
reviewing- Waiting on Payerworks. Payerworks is checking the answers the provider gave.
Today Payerworks has no payer route open yet, so no live request is with a payer or live. A live request you submit goes to payerworks_action_required with reason_code route_not_open, and the answer to the create or the submit says so in a warning. From there Payerworks staff may ask the provider for a task, or reject or cancel it, but nobody can mark it with the payer or live until the route opens.
What each payer asks
- GET /v1/enrollment-requirements/{payer_id} answers for every payer id: per transaction, whether Payerworks’ route is open, who files, the fields needed and each step the payer publishes, with its source and the day Payerworks read it.
- Payerworks holds published steps for the four Medicare DME MACs and CMS’s HETS. Every other payer answers published: false, which means Payerworks holds no facts for it, never that nothing is required.
- Payerworks states no timeframe of its own. Where a payer publishes one, it is quoted with its source.
The sandbox payer, end to end
With a test key, enroll a provider with the sandbox payer, PWSANDBOX. The last word of the provider’s name picks what happens (make one provider per scenario), and POST /v1/sandbox/enrollments/{id}/advance makes the sandbox payer’s next move. A scenario takes the same path every time, and a call retried with the same Idempotency-Key answers the same ids. The sandbox payer’s task instructions and the words of its rejection start with “Sandbox:”, and so do a request’s status_words once it is with the sandbox payer, live, rejected or ended. A cancel asked while a request is with the sandbox payer or live is what the next advance does: canceled, or ended.
Any other word
Sandbox: a provider whose name ends in any other word goes live in one call: submitting moves it to payerworks_action_required (preparing), then with_payer, then live, with an event for each move. Advance has nothing to do.
Provider name ends in
WAITSSandbox: a provider whose name ends in WAITS waits at payerworks_action_required (route_not_open) and stays there. That is the state every live request is in today, so build that screen first.
Provider name ends in
TASKSSandbox: a provider whose name ends in TASKS gets two provider tasks on submit: rank 0 asks for a packet number (an identifier), rank 1 is steps done elsewhere, closed with no values. Once both are complete it waits for review; advance moves it to with_payer, then live.
Provider name ends in
SIGNSandbox: a provider whose name ends in SIGN gets one task with a document field, signed_form, and an invented one-page form as its template. Download the template, upload a signed PDF, complete the task; advance moves it to with_payer, then live.
Provider name ends in
BLOCKEDSandbox: a provider whose name ends in BLOCKED gets a Payerworks task at rank 0 and a provider task at rank 1. Completing rank 1 first answers 409 task_blocked, naming the Payerworks task. Advance completes the Payerworks task; complete yours; advance moves it to with_payer and live.
Provider name ends in
REPLACESandbox: a provider whose name ends in REPLACE starts as TASKS. The first advance deletes the open rank 0 task and adds a new one in its place (enrollment.task.deleted with superseded_by, then enrollment.task.assigned).
Provider name ends in
REJECTSandbox: a provider whose name ends in REJECT goes with_payer on submit; advance rejects it with the payer’s words and the remedy, carried whole.
Provider name ends in
ENDSSandbox: a provider whose name ends in ENDS goes live on submit; advance ends the live route.
Provider name ends in
SLOWSandbox: a provider whose name ends in SLOW goes with_payer on submit and stays there until advance moves it to live.
import { Payerworks } from "./payerworks.mjs";
const pw = new Payerworks({ apiKey: process.env.PAYERWORKS_API_KEY, baseUrl: process.env.PAYERWORKS_BASE_URL });
// A test key enrolls with the sandbox payer; the last word of the name picks the scenario.
const provider = await pw.postProviders({ name: "Sample Family Clinic TASKS", npi: "1234567893", tax_id: "12-3456789", tax_id_type: "ein" });
let enr = await pw.postEnrollments({
provider_id: provider.id, payer: { id: "PWSANDBOX" }, transaction: "remittances", submit: true,
notify_email: "dev@sampleclinic.example",
primary_contact: { organization_name: "Sample Family Clinic LLC", email: "enrollment@sampleclinic.example", phone: "8015550100",
address: { line1: "100 Main St", city: "Salt Lake City", state: "UT", zip: "84101" } },
});
console.log(enr.status, enr.waiting_on, enr.status_words);
for (const t of enr.tasks.filter((t) => t.responsible === "provider" && !t.complete)) {
const values = t.fields.map((f) => ({ key: f.key, text: "PKT-0001" }));
({ enrollment: enr } = await pw.postTasksById(t.id, { values, complete: true }));
}
// A PDF for a document task: the upload and the confirm in one helper.
// await pw.uploadDocument(enr.id, task.id, "signed_form", "signed-form.pdf", pdfBytes);
const { moved } = await pw.postSandboxEnrollmentsByIdAdvance(enr.id);
console.log(moved);Enrollment events
Each change raises one event, posted to your webhook and listed by GET /v1/events. An event carries ids, statuses and words only, never a tax id, a contact, a note or a document: GET /v1/enrollments/{id} for the rest. Payerworks also emails your key’s notice address (PUT /v1/account/contact) when a request moves without your doing it, to provider_action_required, with_payer, live, rejected, canceled or ended: every such move inside the same 15 minutes goes in one email, which names each request’s id, the provider’s name and NPI, the payer, the transaction and the status, and never a tax id, a contact, a note or a document. With no notice address set, nothing is emailed.
enrollment.updated- An enrollment request changed: a status change no type below names, or its fields, tasks, documents or notes. It carries the status, the previous status, whose turn it is, the reason, the fields that changed and the sequence.
enrollment.live- The payer confirmed the request: files of this kind may flow. It adds when, and the payer’s reference.
enrollment.rejected- The request was refused. It adds the rejection: the payer’s words whole, and the remedy.
enrollment.canceled- The request was canceled, or you asked to cancel one that is with the payer or live (cancel_requested says which).
enrollment.ended- A live route ended: the payer ended it, or a cancel you asked for while it was live went through (in the sandbox, at the next advance).
enrollment.task.assigned- A task was added: its id, rank, who holds it, its title and its fields.
enrollment.task.completed- A task was finished, by the provider, or by Payerworks for its own.
enrollment.task.deleted- A task was replaced by another (superseded_by names it).
What is refused, and why
- A Social Security number, as a tax id or anywhere: a provider enrolls with its EIN, which the IRS issues (ssn_not_accepted). A tax id whose first two digits are not a prefix the IRS assigns to an EIN is refused too (not_an_ein); that catches typos and some Social Security numbers, never all of them, so never send one.
- A bank account, routing or account number: no task asks for one, and free text holding a run of eight or more digits is refused (holds_long_number).
- A phone that is all one digit or not a US number, and on a live key an email at a reserved or example domain: the payer’s mail and calls would go nowhere.
- A real payer with a test key (test_key_sandbox_payer_only), and the sandbox payer with a live key (sandbox_payer_test_only).
- Eligibility at a DME MAC: traditional Medicare eligibility is asked of CMS’s HETS (ask_another_payer, naming the payer that answers it).
- A second open request for the same provider, payer and transaction (409 enrollment_exists, naming the one that stands).
- An upload that is not a PDF, or is over 10 MB.
Every field problem
A refusal that lists problems (invalid_provider, invalid_enrollment, invalid_task_values) names each one’s code and field. The codes:
unknown_field- A field this request does not take; nothing is dropped silently.
required- A required field is missing.
invalid_type- A field is not of the type it takes.
too_short- Text shorter than the field takes.
too_long- Text longer than the field takes.
invalid_npi- An NPI is 10 digits with a valid check digit.
invalid_tax_id- A tax id is 9 digits, with or without one hyphen after the second digit.
invalid_tax_id_type- tax_id_type is "ein".
ssn_not_accepted- A Social Security number is never accepted: a provider enrolls with an EIN, which the IRS issues.
not_an_ein- The first two digits are not a prefix the IRS assigns to an EIN.
invalid_email- An email address in the form name@domain.
reserved_email_domain- On a live key, an email at a reserved or example domain, which can never receive mail.
invalid_phone- A phone is 10 digits, area code first; the area code and the exchange do not start with 0 or 1.
phone_not_real- A phone that is all one digit is not a number anyone answers.
invalid_phone_extension- An extension is 1 to 6 digits.
contact_name_form- A contact gives organization_name, or first_name and last_name, not both.
invalid_state- A two-letter US state, DC or territory code.
invalid_zip- A ZIP is 5 digits, or 9 with one hyphen allowed.
too_many_contacts- At most 5 contacts.
holds_long_number- Free text may not hold a run of 8 or more digits: nothing that could be a bank or account number is kept.
invalid_payer_id- A payer id is 1 to 80 letters and digits.
test_key_sandbox_payer_only- A test key enrolls with the sandbox payer, PWSANDBOX, only.
sandbox_payer_test_only- The sandbox payer answers test keys only.
invalid_transaction- transaction is eligibility, claim_status, professional_claims or remittances.
transaction_not_built- Payerworks does not yet enroll for this kind of file.
ask_another_payer- This payer does not answer this transaction; the problem names the payer that does.
ptan_required- This payer’s published steps need the provider’s PTAN.
invalid_ptan- A PTAN is 4 to 10 letters and digits; for a DME MAC, exactly 10 digits.
invalid_date- A day written YYYY-MM-DD.
effective_date_out_of_range- The requested effective date is from today to 180 days ahead.
effective_date_past_45_days- A DME MAC remittance request asks an effective date at most 45 days after it is submitted (CEDI’s rule).
invalid_era_aggregation- era_aggregation is "tin" or "npi".
era_aggregation_remittances_only- era_aggregation applies to a remittances request only.
field_locked- This field changes only while the request is a draft.
unknown_field_key- The task has no field with this key.
value_type_mismatch- A text field takes text; a document field takes a document_id.
text_too_long- A task’s text value is at most 2,000 characters.
missing_required_value- Completing a task needs a value for every required field.
document_not_uploaded- The document has not been uploaded and confirmed.
document_not_on_task- The document was uploaded for another task or field.
invalid_filename- A filename ends .pdf and is 1 to 120 characters of letters, digits, spaces, dots, dashes and underscores.
invalid_field_key- A field key is lower snake case, 1 to 40 characters.
field_key_refused- A task field may never ask for a bank, routing or account number, or a Social Security number.
invalid_link- A link is https, to a public host.
invalid_value- A value this field does not take.
Many requests from one file
POST /v1/enrollments/import/check reads a CSV of up to 100 rows, one request a row, and checks each exactly as POST /v1/providers and POST /v1/enrollments check their bodies, saving nothing; POST /v1/enrollments/import then makes each valid row. The header is the template below, and a column the import does not take is refused by name, never dropped.
provider_name,npi,tax_id,tax_id_type,payer_id,payer_name,transaction,contact_organization_name,contact_first_name,contact_last_name,contact_email,contact_phone,contact_phone_extension,contact_address_line1,contact_address_line2,contact_city,contact_state,contact_zip,notify_email,requested_effective_date,era_aggregation,ptan,note,submit- A provider your key already has with that NPI and tax id is used as it is, never changed, and the row says so. A request that already stands, or an earlier row of the file asking the same, is a duplicate that names it.
- The import stops starting rows when its time in one call runs out, and names the first row not run: send that row and the rows after it again. An Idempotency-Key retry of the same file answers the same requests, never duplicates of them.
- With a live key, each NPI is looked up in the NPI registry, which answers one of three ways: it holds an active record (with Type 1 or 2 and whether the row’s provider name matches, each row on its own), it holds none, or it could not be asked just now. Each is a warning, never a refusal, and nothing it says is kept.
- GET /v1/enrollments/export answers every request matching the list’s filters as CSV, up to 5,000 rows, with no contact, note or full tax id; a cell a spreadsheet would run as a formula starts with an apostrophe.
- GET /v1/enrollments/durations says how long your own requests spent waiting on Payerworks, on the provider and with the payer, by payer and transaction. A figure is given once five stays have finished, the median and the middle half, each with how many it rests on; fewer says not enough history. Payerworks has no figures from anyone else.
The import template’s columns
provider_name- The provider's name, 2 to 100 characters.Required.
npi- The billing NPI, 10 digits with a valid check digit. Write it as text so a spreadsheet keeps every digit.Required.
tax_id- The provider's EIN, 9 digits, with or without the hyphen after the second.Required.
tax_id_type- ein (the only kind taken). Left blank: ein.
payer_id- The payer ID, as text: leading zeros are part of it.Required.
payer_name- The payer's name, up to 80 characters.
transaction- eligibility, claim_status, professional_claims or remittances: one a row.Required.
contact_organization_name- The primary contact's organization; or give contact_first_name and contact_last_name.
contact_first_name- The primary contact's first name.
contact_last_name- The primary contact's last name.
contact_email- The primary contact's email.Required.
contact_phone- The primary contact's phone, 10 digits.Required.
contact_phone_extension- 1 to 6 digits.
contact_address_line1- The contact's street address.Required.
contact_address_line2- A suite or unit.
contact_city- The city.Required.
contact_state- A two-letter state, DC or territory code.Required.
contact_zip- 5 digits, or 9 with one hyphen allowed.Required.
notify_email- Where Payerworks writes about this request.Required.
requested_effective_date- YYYY-MM-DD, from today to 180 days ahead.
era_aggregation- tin or npi, remittances only.
ptan- 4 to 10 letters and digits; for a DME MAC exactly 10 digits, and required.
note- Up to 500 characters for Payerworks staff. Never a patient's information.
submit- true submits the request at once; false or blank makes a draft.
How each row ends
ready- Checked: it would be made as it is (the check route only).
created- Made: its request (and its provider, when the key had none with that NPI and tax id).
invalid- Not made: one or more cells would be refused. Each problem names its column.
duplicate- Not made: an open request for this provider, payer and transaction already stands on the key, or an earlier row of this file asks for the same one.
refused- Not made: the store refused it (a limit reached, a lost race, a store failure). The error says which; send the row again.
not_run- Not started: the import ran out of time first. Send this row and the rows after it again.
Every problem a file can have
A file with any of these is refused whole, 422 invalid_import, before any row is checked:
csv_required- The body is { csv: "..." }: the file's text, its first line the header.
csv_too_long- A CSV is at most 500,000 characters.
unterminated_quote- A quoted cell is not closed before the file ends.
stray_quote- A quote appears inside a cell that is not quoted; quote the whole cell and write a quote inside it twice.
no_rows- The file has a header and no rows.
too_many_rows- A file holds at most 100 rows after the header; split it.
unknown_column- A header names a column the import does not take; nothing is dropped silently.
duplicate_column- Two header cells name the same column.
missing_column- A column every import needs is not in the header.
wrong_cell_count- A row has more cells than the header.
cell_too_long- A cell is at most 600 characters.
unknown_body_field- The body takes csv only.
What Payerworks will never do
- Sign for a provider. The provider signs its own agreements with each payer; no field here asks Payerworks to.
- Ask for a Social Security number, a bank account or a routing number.
- Say a request is with a payer, or live, before it is.
Providers and requests belong to the key that made them, like its webhook and its events. A new key starts with none, so keep the key that holds them; a rotated key keeps them, because rotation keeps the key id. Enrollment is the one part of the API that keeps what you send: the providers and enrollment requests you create (names, NPIs, tax ids, contacts and notes) and the PDFs you upload for a task, because a request lives for weeks and Payerworks people work it. They are for a practice’s business facts; never put a patient’s information in them. A test key’s providers and requests expire 30 days after their last change, and its PDFs 30 days after each was uploaded, or about a day after you delete one; a PDF uploaded but never confirmed goes after a day, whatever the key. A live key’s are kept until you delete them, and the copy of a deleted live PDF for 30 days more. Apart from that, our backups hold every PDF that was there each night, a test key’s included, for 35 days, and each Sunday’s for a year.
837P, 837I and 837D, not yet open
Claim files over SFTP
Drop 837P, 837I or 837D interchanges in your own folder over SFTP and read the answers from another: a TA1 when the envelope is not yours, a 999 by your setting, and a report that names every claim’s errors as POST /v1/claims/validate names them. It is built and not yet open: until Payerworks opens it, every /v1/sftp route answers 403 sftp_not_open.
- Make your user with PUT /v1/sftp and add an SSH public key with POST /v1/sftp/ssh-keys. There are no passwords, a private key is refused and never kept, and a key’s comment is dropped.
- A test key’s user sends usage indicator T to receiver PAYERWORKSTEST and a live key’s sends P to PAYERWORKS, both under qualifier ZZ. The answers are always in your key’s mode, whatever the file said.
- The 999 says what Payerworks checked of the envelope and the controls. A claim the checks refuse is said in the report, never in the 999, and no 277CA is written for a dropped file.
- Nothing you drop goes on to a payer.
What is kept, and for how long
- A file you drop in inbound is read once and deleted as soon as its answers are written. A file the lane could not reach is deleted by the bucket 1 day after it arrived; S3 removes an expired file in the day or so after its expiry.
- Each answer (a TA1, a 999 and the report) waits in outbound for 7 days after it was written, then the bucket deletes it; you may delete it sooner. S3 removes an expired file in the day or so after its expiry.
- The bucket keeps no earlier version of a file and is in no backup. Nothing you drop is stored anywhere else, logged or sent on: the lane's own log line names the key, the kind of file and the outcome, never its contents or its name.
- The endpoint's transfer log in CloudWatch records the name of each file you move, so never put a patient's name or any identifier in a file name.
- Nothing is sent to a payer: Payerworks validates and acknowledges the file, and you send the claims on yourself.
- When your key is revoked, its SFTP user is removed at once; when it expires, or when the endpoint could not be reached at the revoke, by the next daily pass, within a day. A file dropped under a revoked or expired key before then is deleted unread, with no answer.
Reference
Routes
Every route is under /v1. The base URL is shown with your key. The whole contract, machine-readable, is openapi.json.
Docs by job, with quickstarts, examples that run and every page as markdown for an agent: payerworks.com/docs. Every rate, size and item limit, read from the code that refuses by it: payerworks.com/limits. What was measured this month, with any incident and its feed: payerworks.com/status.
Claims and files
POST
/v1/claims/validateValidate claims
Every reason a claim would be refused at the front door or by a payer’s first edit, each with a code, the field’s path and a sentence, from the one edit table GET /v1/edits lists: the NPIs’ check digits, the dates, the pointers, a total that must equal its lines, the filing indicator, a character X12 reserves, and each diagnosis, HCPCS Level II code and modifier against the release in force on its date of service (the ICD-10 diagnosis releases for fiscal 2025 to 2027, CMS’s HCPCS file from January 2025) and the place of service against CMS’s list. A CPT code is checked for its shape only, and a dental D code or a CPT modifier, which CMS’s file does not carry, is listed as not checked. Every claim carries what the guides require: the submitter’s contact, the billing provider’s tax id, the patient’s address and date of birth, a facility’s address when one is sent, and the filing indicator. Formatting is repaired first, on a copy, and each claim lists its repairs (phone punctuation, a ZIP+4 hyphen, letter case, a diagnosis code’s dot, spaces) and not_checked, every check that could not run and why. For a claim to a later payer: the payers’ order, each earlier payer’s paid amount, claim and line balance, the adjudication date and the patient’s remaining share each at one level, the adjustment groups, the Medicare secondary payer type, and remark codes in force on the day; when Medicare is the later payer, the claim’s total equals each earlier payer’s paid amount plus all its adjustments, and a claim to Medicare is an original claim (frequency 1). Send the claims as JSON, or an 837P file as x12, read back claim by claim. Send your own rules beside them as rules, written as POST /v1/edits/rules/check reads them, and each is applied to every claim after its repairs: a rule that fires is listed as caller_rule with source caller, and one of severity error makes the claim invalid like any other error. Passing means the claim is well formed, not that a payer will pay it.
POST
/v1/claims/837pWrite an 837P
Up to one hundred claims written as one 005010X222A1 interchange under your sender and receiver ids and your control number, a claim to a secondary or tertiary payer included: send each earlier payer’s adjudication (otherSubscriberInformation, and lineAdjudicationInformation on each line it paid) and the file carries the coordination of benefits loops. Every claim is checked as POST /v1/claims/validate checks it, your own rules included when you send them as rules, and written from its repaired copy; each claim’s answer lists its repairs, what was not checked, and a not_written warning for any field the 837P has no element for. Nothing is written if any claim would be refused. A test key writes test files only.
POST
/v1/claims/837iWrite an 837I
Up to one hundred institutional claims, the UB-04’s electronic form, written as one 005010X223A2 interchange from the same JSON as the 837P route, with the institutional fields: the facility type and frequency, the statement dates, admission type, source and patient status, revenue code lines, principal and admitting diagnoses with present on admission, procedures, occurrence, span, value and condition codes, and the attending and operating providers. Every claim is checked first, and nothing is written if any would be refused. A revenue code or a type of bill is checked for its shape: the code set’s words belong to the NUBC. A test key writes test files only.
POST
/v1/claims/837i/validateValidate institutional claims
The same institutional claims checked against the 837I’s rules without writing a file: each problem names its code, its field and a sentence.
POST
/v1/claims/837dWrite an 837D
Up to one hundred dental claims and predeterminations written as one 005010X224A2 interchange from the same JSON as the 837P route, with the teeth and surfaces, the area of the mouth, tooth status, prosthesis replacement, orthodontic months and up to four diagnoses. A predetermination carries no date of service. A dental procedure code is checked for its shape: the code set’s words belong to the ADA. Nothing is written if any claim would be refused, and a test key writes test files only.
POST
/v1/claims/837d/validateValidate dental claims
The same dental claims checked against the 837D’s rules without writing a file: each problem names its code, its field and a sentence.
POST
/v1/claims/correction-planPlan a corrected or void claim
Say whether you want to replace or void a claim, and give the payer’s claim number, or say the payer never processed it (adjudicated: false); with neither, the plan asks which rather than guess. The plan says the frequency code, whether REF*F8 goes, whether to use a new patient control number, and when a Medicare contractor fixes a processed claim by reopening instead. Send the replacement too, as JSON or an 837P (an 837D for a dental claim), and it is checked against the plan and every claim rule. Companion guides vary, and the answer says so. It writes and sends nothing.
GET
/v1/editsEvery claim edit
Every check Payerworks runs on a professional claim before it is written: its code, SNIP type, severity, what is wrong, how to fix it and where the rule comes from. Also the formatting repairs made first, the coordination of benefits checks a claim to a later payer gets, the checks a correction plan’s replacement gets, and the checks Payerworks does not run, with why. Filter with snip_type, severity, kind or runs_in, applied to every list. Passing every check means the claim is well formed and its codes are valid on their dates, not that a payer will pay it.
GET
/v1/edits/{code}One claim edit
One check by its code, in any case. A check Payerworks does not run answers runs: false and says why; any other code is 404 edit_not_found.
POST
/v1/x12/parseRead an X12 file
Any X12 file Payerworks reads, to JSON with its envelope, in the file’s own delimiters: an 835, a 999, a 277CA, a 270 or 271, a 276 or 277, a 278, or an 837P, 837I or 837D. A claim file is read back to the same JSON its write route takes, each claim also as its own one-claim interchange, so a bulk file splits per claim, and every segment the reader does not read is named by its id, loop and position, never its value. A 277CA is told from a claim status 277 by its implementation guide, any other kind is named, and an interchange that mixes kinds is refused.
POST
/v1/claims/trackTrack claims
Send the claims (as JSON or the 837P) and the 999s, 277CAs and 835s you hold for them. Each claim comes back with where it stands (submitted, received, accepted, rejected, processed, denied or unknown), who said so, what was paid, and a dated timeline of every file. It reads only what you send: nothing is stored and no payer is asked. A payer’s 277 request for additional information may ride with the acknowledgments: a request no 275 answers holds the claim as information requested, with its due day; send your solicited 275s as attachments to see whether they answer it.
POST
/v1/acknowledgments/explainExplain an acknowledgment
Every rejection in a 999 or a 277CA said as the fix: the segment, the loop, the element, and the part of your claim data to correct, matched to your claim reference. A TA1 is said as its accept or reject and its note code, in words.
POST
/v1/claims/cms1500Draw claims on the CMS-1500
Up to one hundred claims in the 837P’s JSON, checked by the same claim checks as POST /v1/claims/837p (your own rules are not applied here) and drawn on the CMS-1500 (02/12): six service lines a form, as many forms as a claim needs (at most 200 a call), each form’s Box 28 the total of its own lines. form: full puts CMS’s own form under the data; data_only prints the data alone, to print onto pre-printed red forms (print one on plain paper and hold it against a form first). Send signed_on to date Boxes 12 and 31, which are otherwise left undated, and payer_address for the carrier block. The answer is your PDF as base64; nothing is sent or kept.
POST
/v1/attachments/275Write a 275 attachment
A claim’s attachments as an X12 275 (005010X210): each file (PDF, TIFF, JPEG, PNG or a CDA document, up to ten and 1.4 MB in all) base64 in its own BIN, under the control number the claim’s PWK segment announced with transmission EL, so the payer can match the two; a control number the claim does not announce is refused. A control number is 2 to 50 letters, digits, dots, dashes or underscores, the same in the claim’s PWK06 and the 275’s TRN02. Written as public payer companion guides show the 275, not from the licensed implementation guide, and the answer says so: check your payer’s own. A test key writes test files only. Names an attachment’s LOINC document type when you give one. A solicited 275 answers a payer’s request: send `request` with the request’s trace, and each attachment may echo its own requested code (stc01) and name its service line (line_control_number); POST /v1/attachments/requests/explain gives every value from the request.
POST
/v1/attachments/requests/explainRead a request for more information
A payer’s 277 request for additional information (005010X213) said claim by claim: what it asks for (each LOINC code it names, in LOINC’s own words, or said as a code Payerworks has no words for), about which claim and which service line, by when against your own today, and the solicited 275 that answers it: the payer’s trace, the claim’s REF*EJ, the submitter id, the payer’s response contact, and a body POST /v1/attachments/275 takes. Read as public sources show the request, not from the licensed implementation guide, and the answer says so. Nothing is stored or sent.
POST
/v1/attachments/requests/checkCheck a 275 against its request
A solicited 275 (or up to ten) held to the 277 request it answers before you send it: BGN01 11, the payer’s trace on every attachment, the request’s REF*EJ and submitter id, every code asked for answered by an attachment echoing its STC, each file’s BIN length, and the due day. Errors and warnings per 275 set, and per claim whether every code is answered: a set with an error of its own answers no code, what is missing is read across every 275 sent, and a line’s request is answered only by an attachment carrying that line’s REF*FJ. Nothing is stored or sent.
POST
/v1/edits/rules/checkCheck your own claim rules
Rules you write for your own payers: a field’s path, an operator from a fixed list of twenty, a value, the payers it applies to, your message and whether it is an error or a warning. Each rule is checked for the format, and applied to any claims you send with it (up to 100). The same rules may ride on POST /v1/claims/validate and POST /v1/claims/837p as rules, where a rule of severity error refuses the claim. Patterns are a small fixed format, never code, and each call has a step budget, and a field a rule reads may reach at most 1,000 values: a rule it does not reach, or whose field reaches more, is listed as not checked with its reason, never as passed. Nothing is stored.
POST
/v1/claims/submitSend claims to a payer
Not offered yet: Payerworks is connected to no payer, so this answers 501 submit_not_offered with each gate and whether it is open (sending switched on, a live key, a signed business associate agreement recorded for your account, a trading partner for the payer, and that partner switched on). Nothing is written or sent. Once every gate is open it writes your claims as an 837P exactly as POST /v1/claims/837p does, under the partner’s interchange ids and a control number Payerworks assigns, and hands the file to the partner; transmitted is true only when the partner’s connection accepted the file. Every patientControlNumber begins with your account’s reference prefix, and one already in flight is refused unless it was rejected or the claim is a replacement or a void. The partner’s 999, 277CA and 835 arrive as submission events to the key that sent the file, never to anyone else. Send an Idempotency-Key: a retry under it answers the first answer and sends nothing again.
GET
/v1/partnersTrading partners
The trading partners claims go to through POST /v1/claims/submit today, with the payer ids each takes and the transactions it carries, and one sentence about Payerworks’ connections. Today the list is empty: Payerworks is connected to no payer.
Eligibility
POST
/v1/eligibility/270Write a 270
Up to one hundred eligibility questions written as one 005010X279A1 interchange, from the JSON shape most clearinghouse APIs take: the payer, the provider, the subscriber and at most one dependent, the day and the service types. Ask about any day, a later one included; with no day named, the payer answers for its own current day. A question may also ask about procedures (CPT, HCPCS, CDT or NDC codes with up to four modifiers), point each to the patient’s diagnoses, and carry the place of service, a prior authorization or referral number, the provider’s taxonomy, a payer PIN and a birth order for twins; a question that asks only about procedures asks no service type beside them. Each question is checked field by field first, and nothing is written if any would be refused. You are told when a member ID begins with the card issuer number (80840), a name holds a title, a degree or a suffix, a backtick was written as an apostrophe, or the day is more than a year back. A question that uses none of these fields is written exactly as before. A test key writes test files only.
POST
/v1/eligibility/explainExplain an eligibility answer
Any 271, as X12 or as the JSON most clearinghouse APIs return, said per patient: whether the coverage is active on the day you asked about, the plan, and for each service type the deductible and out-of-pocket limit with what is met and what remains, in and out of network, individual and family, the coinsurance, the copay, and what is not covered or needs prior authorization at each tier. A refusal comes back as every reason in words, with the field to fix and where to check it, or when to ask again. The JSON may be either shape most clearinghouse APIs return, a flat list of benefits or benefits grouped by plan with each kind in its own list, and one answer explains the same in any of the three forms; what the grouped shape holds that is not read is named. Add ?include=answer for the 271 read to JSON as well, and &shape=plans to have it in the grouped shape.
POST
/v1/eligibility/identity-retryFind the patient again
After a payer could not match the patient (reject reasons 65, 67, 71, 72, 73, 75 or 77, sent as reasons or read from the 271 itself): the next questions to try, in order, each built from your own question and the facts you give (the legal first name, a previous last name, a dependent’s own member ID), each with its changes listed and checked as a 270 is. No name is guessed and there is no nickname table; a day and month swapped is offered only as one to check first. Suggestions only: Payerworks looks nobody up, and nothing is sent.
POST
/v1/eligibility/service-type-testDoes a payer answer a service type
Two answers you hold from one payer, one to 30 (or 35, for dental) and one to a specific service type: whether the payer answered the specific one, likely ignored it, answered it under other service types, or refused it, with how many lines it added and removed. It reads only the two answers, as they stood when they were given: a payer may change what it answers.
POST
/v1/eligibility/coordinationWhich plan pays first
The plans you list for one person, two to ten, put in the order they pay by the NAIC coordination of benefits model regulation and Medicare’s secondary payer rules, each step naming its rule and its section. Medicaid always pays last, and after Medicare a Medigap policy comes before TRICARE; injury coverage not related to the service is left out of the order. Where a rule needs a fact you did not send, no order is given and the fact is named, never guessed. It knows only the plans you send: it cannot find a person’s other coverage.
POST
/v1/eligibility/discovery/verifyIs this coverage the patient’s
Up to ten coverages an insurance discovery search found, each a 271, checked against the patient: the last name (a two-part surname allowed), the first name, the exact date of birth, the gender and the address. Each is said as a match, one to review or not this patient, and one the payer reports as pending is marked to confirm with the payer. Payerworks runs no discovery search itself.
POST
/v1/eligibility/pdfPrint an eligibility answer
A 271, as POST /v1/eligibility/explain takes it, as a summary per patient: what was asked and what the answer echoes back, whether the coverage is active, the plan’s figures in and out of network, each service type asked, and every benefit line by plan and then service type. Up to 50 patients and 3,000 benefit lines a call, and 3,000 rows as drawn: a benefit line is listed under each service type it names, so a line naming ten service types is ten rows (and each of its messages one more under each). The answer is your PDF as base64.
Claim status
POST
/v1/claim-status/276Write a 276
Up to one hundred claim status questions, from the JSON shape most clearinghouse APIs take, written as one 005010X212 interchange for you to send on your own connection. Each question is checked field by field first, and nothing is written if any would be refused. A test key writes test files only.
POST
/v1/claim-status/explainExplain a claim status answer
A 277 claim status answer, as X12 or as the JSON most clearinghouse APIs return, said per question: where the claim stands, whether money is coming and how much, what to do next and when to ask again. A claim the payer cannot find yet is never called a rejection, and a question the payer refused because of its own data is never asked again unchanged. A claim the payer’s search did not find comes back with the next questions to ask, in order, each built from the question you sent (asked.request) and what you hold (asked.known), never from a value you did not give; a payer whose system did not answer, with a retry schedule for the same day.
POST
/v1/claims/follow-upFollow up on open claims
Your open claims (up to 200) with the day each was sent, and your history of when each payer paid (up to 20,000 rows), or the 837Ps, 277CAs and 835s themselves. Each payer’s days to pay are learned from your history alone, with how many paid claims they rest on, and only from five up; below that the answer says there is not enough history. Each claim comes back with when its remittance is expected, whether it is late, and the next step: ask its status now, wait until a day, the next questions after a not found, or retries for a payer whose system did not answer. Your own follow-up codes come back as you sent them, never read. Nothing is stored, nothing is sent, and no payer is asked.
Remittances, denials and appeals
POST
/v1/remittances/explainExplain a remittance
An 835 claim by claim: what was paid, allowed and written off, the patient’s share, the verdict in words, each service line, every adjustment and remark code in words, and whether each check adds up to the claims it pays.
POST
/v1/denials/next-stepThe next step on a denial
An 835, or the claims as JSON: for each claim and each adjustment, what the payer did, whether the patient may be billed, the next step, the corrected claim fields to send, what to attach, and every deadline with where it comes from: a Medicare regulation with its section, the limits you send, or no date at all.
POST
/v1/appeals/letterWrite an appeal letter
An appeal letter written from the facts you send, by a fixed template for its kind. It never adds a code, an amount or a date you did not give. A fact the kind needs and you left out is named, and nothing is written.
POST
/v1/appeals/letter/checkCheck an edited letter
A letter you edited, held to the facts by the same rule: every code, amount and date it states, however it is written (a dollar sign or the word dollars, a written or numeric date, CO-50 or CARC 50, N115 or N 115), must be one the facts hold, and a placeholder left in is found.
POST
/v1/remittances/pdfPrint a remittance
An 835 laid out the way Medicare’s Standard Paper Remittance is read: each check, each claim and its service lines with what was billed, allowed, applied to the deductible and coinsurance, adjusted and paid, the check’s totals and provider-level adjustments, and a glossary of every code on the file. Up to 500 claims and 3,000 service lines a call. The answer is your PDF as base64.
POST
/v1/appeals/formFill a Medicare appeal form
CMS’s own redetermination request (CMS-20027) or reconsideration request (CMS-20033), the file CMS publishes, filled from the claim and the 835 that denied it: the beneficiary, the Medicare number, the codes of the lines the payer denied (a line it paid is never listed) and their dates of service, the notice date, and why you disagree in your words. Original Medicare only. Nothing is signed, dated or chosen for you, the deadline comes with its regulation, and the form is yours to review and send.
POST
/v1/remittances/from-imageRead a paper EOB into a draft 835
A photo, scan or PDF of a payer’s paper explanation of benefits (two pages at most, 1.4 MB), read by a model into a DRAFT: every figure, date and code with the reader’s confidence and where on the page it was read, the payments checked by arithmetic against the check total the page states, and an 835 written as a test file. It is never an ERA and nothing is posted: check every field against the paper. Off until Payerworks turns it on; test keys and invented documents only for now, at most 100 reads a UTC day for an account’s keys together; a call refused for the route being off, a live key or no file is not counted. A read has 25 seconds; past them the answer is 504 model_read_timed_out: send one page, or a crop of the part you need. Nothing is kept.
POST
/v1/remittances/duplicatesFind duplicate remittances
Send several 835s and get back every check that is one payment sent more than once: the same trace number (TRN02) from the same originator (TRN03), never the trace alone. Each duplicate says whether the copies are the same remittance or differ in content, and a check with no trace is listed apart. Nothing is stored.
POST
/v1/remittances/reassociateMatch deposits to remittances
Send 835s and the deposits from your bank (the amount and effective date, and the trace number or description when your bank gives them). Each deposit is matched to one check by its trace number first, then by a trace in the description, then by amount and date only when exactly one check and one deposit fit, and every match says what agreed and what did not. Unmatched deposits and checks are listed. No bank description is repeated back.
POST
/v1/remittances/splitSplit an 835 by payee or rendering provider
Send an 835 paid to one tax ID and get one file per payee (the provider the payer grouped the claims under) or per rendering NPI. Each file’s payment total is recomputed from its own claims and provider-level adjustments and read back to check it balances, the parts add up to the original check, and each keeps its trace. An adjustment that names no single group gets a file of its own, never a guessed one, and a value holding a delimiter is refused, never rewritten. Send the interchange control number you track for the first file (each further file takes the next): the original’s own number is never reused. The files are yours to load; nothing is sent.
POST
/v1/remittances/recoupmentsMatch recoupments to the claims they take back
Send a set of 835s. Every provider-level adjustment is named, and each takeback is matched to the paid claim its reference names in the files you sent: by payer claim number, then patient control number, then amount when only one earlier payment at the same payer fits. Unmatched takebacks are listed, with what each payer took back and what the files show still carried forward. It says what was taken and from which claim, never whether it should have been.
GET
/v1/filing-limitsPublished claim filing windows
The claim filing window each payer in Payerworks’ table publishes, read on its own pages on 09/10/2026, and Medicare fee-for-service’s from 42 CFR 424.44. Search with q, and add date_of_service to count the last day where the payer states one figure; a payer whose pages leave the window to its agreement says so, with no day. Your agreement with the payer governs over a published window, and nothing here is a payer’s answer about a claim.
GET
/v1/filing-limits/{payer}One payer’s filing window
One payer by its slug from GET /v1/filing-limits, or by its name, with every plan line it publishes a window for; add date_of_service to count the last day. A payer with no window on record is 404 no_filing_limit_on_record, never a window.
Analytics from your own files
POST
/v1/analytics/summarySummarize your claims
Send the claims with the 999s, 277CAs and 835s you hold for them (the same body as /v1/claims/track), or your 835s alone. You get first-pass acceptance, the rejection reasons ranked, the denial rate by payer and by group code, the dollars refused, the days from sending a claim to its remittance by payer (the median and quartiles), reversals, and what the refusals come down to by reason. Every figure says how many it counts, and a rate or a median is read as a pattern only from five; below that the answer says there is not enough history. Send month (YYYY-MM) for one calendar month: the current month counts to today. It reads only your files: there is no benchmark, and nothing is stored.
POST
/v1/remittances/varianceCheck payments against your fee schedule
Send your 835s and your fee schedule (payer_id, code, modifier, allowed, effective_from, effective_to). Each service line is judged against the rate in force on its date of service: the amount expected, the payer’s allowed amount, the difference, and the adjustment codes the payer gave. A line with no rate says so and is never read as zero, and a line the payer refused is a denial, not an underpayment. Add medicare (a state) to compare with Medicare’s October 2026 DMEPOS fee schedule where the API runs, labeled as Medicare’s rate and never as your contract. An underpaid line carries the facts for POST /v1/appeals/letter.
POST
/v1/claims/riskCheck a claim against your past denials
Send a claim (as /v1/claims/validate takes it) and your past 835s. For each line, every past refusal of the same payer, procedure code and modifiers, with its reason and remark codes, on how many claims, out of how many decided claims with a line of that shape, and the fix. A reason is called a pattern only from two refused claims: a claim counts once however many of its lines were refused, and a remittance sent twice counts once. Send the past claims too and the diagnosis is compared. It reads only your files and never says how a payer will decide.
POST
/v1/denials/prioritizeRank your open denials
Send the 835s holding your open denials, and if you have them, your appeal outcomes (payer_id, carc, outcome). Denials due in 14 days or fewer come first, soonest first; then each by its dollars, or its dollars times your own overturn rate for that payer and code once you have five decided appeals there. A passed deadline comes last. Without outcomes it ranks by deadline and dollars, and says so. No rate here is anyone’s but yours.
Prior authorization
POST
/v1/authorizations/278Write a 278
Up to one hundred prior authorization or referral requests, from JSON (the kind of review, the service type, each procedure code with its units and dates, the requester, the servicing provider, the patient and the diagnoses), written as one 005010X217 interchange for you to send on your own connection, or built in the shape of the Da Vinci PAS request bundle with output set to fhir. Each request is checked field by field first, and nothing is written if any would be refused. A test key writes test files only. Whether a payer takes a 278 at all is the payer’s choice: many take prior authorizations only on a portal, by fax or by phone.
POST
/v1/authorizations/explainExplain a 278 response
A payer’s 278 response, as X12, as Payerworks’ own JSON read of one, or as a PAS response, said per review: approved, approved in part, approved with changes, not approved, pended, waiting on more information, canceled, no review needed, or not reviewed at all, with the reason in words, the certification and reference numbers, the approved dates and units against what you asked, the claim field the number goes in, and the next step. A request the payer never reviewed is never called a denial.
POST
/v1/sandbox/authorizationsRun the prior authorization sandbox
The round trip on invented data: your requests written as a 278 and the sandbox payer’s 278 response, chosen by how each member ID ends, read back and explained in the same call. Any key may call it, every file is a test file, and nothing reaches a payer.
POST
/v1/authorizations/requiredDoes Medicare require it
Up to one hundred procedure codes checked against CMS’s published Medicare fee-for-service lists on a date of service: the DMEPOS Required Prior Authorization List and the hospital outpatient department list, each named and dated, with the state or the setting asked for where the list depends on it. A code on neither list is said as not on the lists held, never as not required, and for any other payer the answer is that no published list is held.
Patient estimates and money
POST
/v1/estimatesEstimate a patient’s share
A patient’s share and the plan’s, line by line, from a 271 (X12 or JSON) and the allowed amounts you send, at most 50 lines. You say the network level and, where a copay is stated, how it applies (copay_rule): a 271 says neither. The lines draw on the deductible and the out-of-pocket amount left in the order sent; for one member, a family amount left the 271 states bounds the member’s, and since a 271 does not say whether a family deductible is embedded or aggregate, the answer says which it read and what the other would mean. Where the out-of-pocket limit caps a line, its deductible and coinsurance are cut to add up to the share. Every input names where it came from: the 271, you, or not stated. A benefit the 271 did not state stops that line rather than reading as zero, unless you supply it, and no allowed amount is ever supplied for you: Payerworks holds no prices. The payer’s own processing decides what the patient owes.
POST
/v1/estimates/good-faithLay out a Good Faith Estimate
A Good Faith Estimate for an uninsured or self-pay patient, with every element 45 CFR 149.610 lists, in order and each named by its paragraph: the patient, the primary service, the items grouped by provider with their codes and expected charges, the amount at which the patient may dispute the bill, the disclaimers, and when it is due by the business days you state. It is your document to issue, give and keep; Payerworks issues, delivers and keeps nothing.
POST
/v1/dme/replacement-windowWhen equipment may be replaced
For each item you send (a HCPCS code and the day it was delivered, at most 100): the earliest replacement for wear under Medicare’s reasonable useful lifetime, five years from delivery unless you name a longer lifetime and its source, whether a planned day falls inside it (judged from the latest delivery you sent of the same code), and what to consider by reason (an Advance Beneficiary Notice with GA or GZ, or the RA modifier for a loss, a theft or irreparable damage), each cited. It does not check Medicare’s records: only the DME MAC shows whether a same or similar item was supplied.
POST
/v1/financial-assistance/fplIncome against the poverty guideline
Income as a percent of the 2026 HHS poverty guideline for the household’s size (Alaska and Hawaii on their own tables; a territory only when you say your program uses the contiguous table), and which of your policy’s tiers it falls in, compared in whole cents. Arithmetic only: no screening and no decision.
Hospital standard charges
POST
/v1/price-transparency/validateCheck a hospital standard charges file
A hospital’s standard charges file, as the tall or wide CSV or as JSON, checked against CMS’s version 3.0.0 template and schema: every required header and value, the valid values, and each conditional requirement, each problem with its row or path, the column and the rule it breaks. A file over 2 MB is checked in pieces, the first three rows sent again with each and row_offset set, so every row number is the file’s own. Nothing is hosted, published or kept.
Payer directory
GET
/v1/payersSearch the payer directory
Payers and plan programs from public federal files (Medicare Advantage and Part D contracts, Medicaid managed care plans, insurers’ loss ratio filings, employer plans’ Form 5500 carriers) and the Medicare contractors; not a list Payerworks can send to; a payer ID only where the payer publishes it, with the page and the day it was read. Search with q by name, a misspelling, a payer ID, a NAIC code or a CMS contract ID (a state named in q is read as state=); narrow with state and type; page with limit (up to 100) and starting_after. Every row says connection none, and an all-digit payer ID under five digits is warned about: leading zeros are part of it.
GET
/v1/payers/{id}One directory row
One row in full by its directory id (pwd_ and 16 hex): its names, where its sources say it serves (or Not stated), programs, the payer IDs it publishes with their sources, and the files it came from. A payer ID sent here is refused 400 not_a_directory_row_id and pointed to the search, never answered as not found.
POST
/v1/payers/from-cardLook up a payer from a card photo
A photo of an insurance card’s front (and back, if you have it), read twice by a model for the payer’s name, the member ID and its shape, the group number and the plan type the card itself states, and the payer looked up in the Payerworks payer directory. A match means the directory holds published facts about that payer, never that a claim can be sent to it; an electronic payer ID printed on the card is shown only where the payer publishes it. Off until Payerworks turns it on; test keys and invented cards only for now, at most 100 reads a UTC day for an account’s keys together; a call refused for the route being off, a live key or no file is not counted. A read has 25 seconds; past them the answer is 504 model_read_timed_out: send one page, or a crop of the part you need. Nothing is kept.
Providers and enrollment
POST
/v1/providersCreate a provider
A provider record: its name, billing NPI, tax id (an EIN, never a Social Security number) and contacts. One per NPI and tax id pair: a second is refused, naming the first. With a live key the answer also says what the NPI registry holds for the NPI: whether it is there, Type 1 or 2, and whether the provider’s name matches (its own name only, never a contact’s; a warning, never a refusal; a lookup that fails says so, and is never read as not found).
GET
/v1/providersList your providers
Newest first. Page with limit (up to 100) and starting_after, and filter with npi, tax_id or q (part of the name).
GET
/v1/providers/{id}One provider
The provider, and whether its NPI and tax id are locked because a request for it has gone to a payer.
POST
/v1/providers/{id}Change a provider
Only the fields you send change. The NPI and tax id can be corrected until a request for the provider has gone to a payer.
DELETE
/v1/providers/{id}Delete a provider
Its drafts go with it. A provider with a request that is open or live is kept, and the refusal names those requests. Deleting again answers the same.
POST
/v1/enrollmentsRequest an enrollment
One request per provider, payer and transaction: eligibility, claim_status, professional_claims or remittances. Kept as a draft, or handed to Payerworks with submit: true. Payerworks is not yet registered with any payer, so a live request waits with Payerworks, the answer carries a route_not_open warning, and nothing is sent.
GET
/v1/enrollmentsList your enrollments
Newest first, each with its status, whose turn it is and its open tasks. Filter with status, transaction, payer_id, provider_id, npi, waiting_on and the created and updated dates. A page may hold fewer than limit, so keep paging while has_more is true.
GET
/v1/enrollments/{id}One enrollment
Its status and whose turn it is, in words; its tasks in rank order, its documents, notes from Payerworks, a rejection with its remedy, and the history of every status change.
POST
/v1/enrollments/{id}Change an enrollment
The contact, the notice email, the effective date, the remittance grouping, the PTAN or your note, until the request is sent to a payer; while it is a draft, the payer and the transaction too.
DELETE
/v1/enrollments/{id}Delete an enrollment
A draft, a canceled or a rejected request. Deleting again answers the same.
POST
/v1/enrollments/{id}/submitSubmit a draft
The draft is checked again and handed to Payerworks.
POST
/v1/enrollments/{id}/cancelCancel an enrollment
In any open status. Before it is sent to a payer it is canceled at once; once it is with a payer, or live, the answer is 202 and Payerworks withdraws it.
POST
/v1/tasks/{id}Answer a task
Save a task’s values, text or an uploaded document, and finish it with complete: true, in rank order. A task held up by an earlier one is refused naming that task, its rank and who holds it, Payerworks included. complete: false reopens a task finished by mistake while nothing has gone to a payer.
POST
/v1/enrollments/{id}/documentsUpload a PDF for a task
A signed upload address for one PDF of up to 10 MB, good for 15 minutes. Post the file there, then confirm it, or finish the task with its document_id, which confirms it too.
POST
/v1/documents/{id}/confirmConfirm an upload
Checks that the file arrived and is a PDF of up to 10 MB. One that is not is deleted, and the document is marked failed.
GET
/v1/documents/{id}Download a document
A signed download address, good for 15 minutes, for a PDF you uploaded or a form Payerworks attached to a task.
DELETE
/v1/documents/{id}Delete a document
Refused while a finished task names it: reopen the task first. Deleting again answers the same.
GET
/v1/enrollment-requirementsPayers with published steps
Every payer Payerworks holds published enrollment steps for, the four Medicare DME MACs and CMS’s HETS, and the sandbox payer: per transaction, who files, the fields needed and each step, with its source and the day it was read.
GET
/v1/enrollment-requirements/{payer_id}What a payer asks
For any payer id, per transaction: whether Payerworks’ route is open, who files, the fields needed and each published step with its source. A payer Payerworks holds nothing for answers published: false, never not required. Add ?transaction= for one.
POST
/v1/sandbox/enrollments/{id}/advanceAdvance a sandbox enrollment
The sandbox payer’s next move on a test request, with the events it raised. A test key only; the last word of the provider’s name picks the scenario.
POST
/v1/enrollments/import/checkCheck an enrollment import
Send { csv } with the template’s header and up to 100 rows, one enrollment request a row. Each row is checked exactly as POST /v1/providers and POST /v1/enrollments check their bodies, each problem named by its column, and said as ready, invalid or duplicate (a request that already stands, or an earlier row asking the same). With a live key, each NPI is looked up in the NPI registry. Nothing is saved.
POST
/v1/enrollments/importImport enrollment requests
The same file, each valid row made into one request, and its provider when your key has none with that NPI and tax id. A provider you already have is used as it is, never changed. Each row answers created, invalid, duplicate, refused or not_run; rows not started in time are not_run from not_run_from_row, so send those again. A row is submitted only when its submit cell says true.
GET
/v1/enrollments/exportExport your enrollments
Every request matching the list’s filters, as CSV text in the answer, up to 5,000 rows: status, whose turn, provider, payer, transaction and the dates. No contact, note or full tax id. A cell a spreadsheet would run as a formula starts with an apostrophe. It takes no limit or starting_after.
GET
/v1/enrollments/durationsHow long your requests took
By payer and transaction, how long your own requests spent waiting on Payerworks, on the provider and with the payer, and from submitted to live: how many finished, and once at least five did, the median and the middle half in hours. Fewer than five says not enough history. From your key’s own history only: Payerworks has no other figures.
Codes in words
GET
/v1/codes/carc/{code}An adjustment reason code
A claim adjustment reason code in words, with its category, sent bare (45) or with its group (CO-45). A remark code sent here is named as one.
GET
/v1/codes/group/{code}An adjustment group code
A claim adjustment group code (CO, PR, OA, PI or CR) in words, and whether the patient may be billed an amount under it.
GET
/v1/codes/claim-status/{category}A claim status category
A claim status category code in words, read both ways: on a claim acknowledgment (277CA), whether it means accepted, rejected or denied, and in a claim status answer, the state the explainer gives it.
GET
/v1/codes/claim-status-code/{code}A claim status code
A claim status code in words, and whether it names an entity.
GET
/v1/codes/service-type/{code}A service type code
A service type code in words: its 005010 meaning where Payerworks has verified it, and the meaning later X12 versions gave a code they reassigned.
GET
/v1/codes/aaa/{code}A reject reason code
A reject reason from a 271 (its AAA segment) in words, whether asking again can help, and the request fields to correct. Add ?loop=2100D for a dependent.
Sandbox payers
POST
/v1/sandbox/claimsRun the sandbox payer
Your claims written as an 837P and answered by the sandbox payer in the same call: a 999 for the file, a 277CA for each claim and an 835 for each claim it adjudicated, every file explained, and claims[] with each claim’s warnings, its repairs and the checks that could not run. Any key may call it, every file is a test file, and nothing reaches a payer. How the member ID ends decides the answer. A member ID ending in RFAI is held for a document: a 277 request for additional information follows. Send the same claims again with the solicited 275 (attachments_275, at most five) and the claim is paid.
POST
/v1/sandbox/claims/837iRun the sandbox payer on an 837I
Your institutional claims checked as POST /v1/claims/837i checks them, written as an 837I and answered by the same sandbox payer as the professional claims, by the same rules: how the member ID ends decides the answer, and the paid amounts and payer claim numbers are made the same way. The answer is a 999 for the file, a 277CA for each claim and an 835 for each claim it adjudicated, and explain_with names the routes that explain them. A member ID ending in RFAI is held for a document, as on POST /v1/sandbox/claims: send the same claims again with the solicited 275 in attachments_275 and the claim is paid. Any key may call it, every file is a test file, nothing is posted to your webhook, and nothing reaches a payer.
POST
/v1/sandbox/claims/837dRun the sandbox payer on an 837D
Your dental claims and predeterminations, checked as POST /v1/claims/837d checks them and answered by the same sandbox payer, by the same member ID endings, with a 999, a 277CA and an 835. A predetermination is answered as one: CLP02 25, nothing paid, each line’s would-be payment as an OA 101 adjustment, and the estimate in the outcome. A member ID ending in RFAI is held for a document: send the same claims again with the solicited 275 in attachments_275 and the claim is paid. Any key may call it, every file is a test file, nothing is posted to your webhook, and nothing reaches a payer.
POST
/v1/sandbox/eligibilityRun the eligibility sandbox
Your eligibility questions written as a 270 and answered by the sandbox payer with a 271 in the same call, every answer explained. Any key may call it, every file is a test file, and how the member ID ends decides the answer.
POST
/v1/sandbox/claim-statusRun the claim status sandbox
Your claim status questions, as JSON or a 276, answered by the sandbox payer with a 277 in the same call and explained. It agrees with the claims sandbox: the same payer claim number and, for each claim, the same paid amount. How the member ID ends decides the answer.
POST
/v1/sandbox/claims/timelineRun the sandbox over days
The claims sandbox spread over simulated time: the 999 after five minutes, a 277CA saying each claim was received after an hour, the payer’s own 277CA after two days and the 835 after fourteen. Send the claims, a seed, the start and the moment to read as of; send a later moment and the cursor you were given to read what came next. Each event is the exact body a webhook sends, so you can post it to your own handler; Payerworks posts none of them. Add format: report for the 277CAs and 835 in the JSON field names most clearinghouse APIs use. Nothing is stored or sent: the same request gives the same answer.
Your keys, your account and your webhook
GET
/v1/healthHealth
Whether the API is answering. No key needed.
GET
/v1/accountYour account
The key’s account, label, mode and rate, the calls it made on each day of this month (counted by UTC day) and the calls refused before they ran, and its webhook if one is set, with what its last delivery did.
PUT
/v1/account/webhookSet your webhook
The https address Payerworks posts your events to. The first answer carries the signing secret, once. Setting the address again keeps the secret; send rotate: true for a new one, and the previous secret keeps signing beside it for overlap_hours (0 to 720, 24 by default).
DELETE
/v1/account/webhookRemove your webhook
No more events are sent. Removing a webhook that is not there is not an error.
POST
/v1/account/webhook/testSend a test event
One ping event queued for your webhook, signed like every other. The answer is 202 with the event’s id; GET /v1/events/{id} says a moment later what your server did with it.
GET
/v1/account/contactYour notice address
Where Payerworks emails you about this key: when one of its webhook destinations keeps failing, and when one of its enrollment requests moves without your doing it. A test key from this page starts with the address its link was sent to, the only address it may set.
PUT
/v1/account/contactSet your notice address
The address told when a destination has failed for 2 hours, for 24 hours, and when it was disabled after its last attempt, and sent one email for every enrollment request that moved without your doing it inside the same 15 minutes. Send email: null to remove it, and nothing is emailed. Only the address that asked for this account’s test key by email (any key of the account may set it), or null; any other address is refused 403 contact_not_proved. A key issued by Payerworks has its address set by us: write to us.
GET
/v1/keysYour account’s keys
Every key of your account in your key’s own mode (a test key lists the test keys, a live key the live ones): its name, mode, last four characters, when it was made, when it expires, when it was last rotated, until when its previous secret is accepted, whether it has expired, when it was revoked, and which one is the key asking, beside the account’s id. A revoked key is listed too. Never a key itself.
POST
/v1/keysMake a key
A named key of your key’s own mode and rate, shown once. expires_in_days (1 to 365) makes it stop on its own. A key never outlives the key that made it: its expiry is the sooner of expires_in_days and the making key’s own, and the making key’s when none is asked. An account holds 3 test keys or 25 live ones that are not revoked. Every key of an account may manage its keys, unless the account’s organization chose members only.
DELETE
/v1/keys/{id}Revoke a key
The key stops working within a minute, and its SFTP user, if it has one, is removed at once. Revoking a key that is already revoked is not an error.
POST
/v1/keys/{id}/rotateRotate a key
A new secret for the same key id, shown once, so its webhooks, events, usage and enrollments carry on. The old secret is still accepted for overlap_hours, 0 to 720, 24 by default, and stops at once if the key is revoked. Two rotations at once: the first finishes, and the other answers 409 key_rotation_conflict and makes nothing.
POST
/v1/mcpThe MCP server, hosted
The same tools as the downloadable MCP server, over MCP’s Streamable HTTP transport (2026-07-28, and the 2025 handshakes), with your key as the bearer. Each tool call is a call to its route under your key. With a live key no tool is called unless the request carries Payerworks-MCP-Allow-Live-Key: 1. Payerworks transmits nothing.
GET
/v1/audit-logYour account’s audit log
Every change to this account, newest first: keys made, rotated, revoked or expired; the webhook, destinations and the notice address; providers, enrollment requests, tasks and documents; the SFTP user and its SSH keys; batch jobs; the organization and its members. Each entry says what, when, who (a key, a member, Payerworks, or Payerworks automatically), through which door, the object’s id, and the fields that changed by name only, never a value; a web address by its host. limit 1 to 100 (25), type (an entry type, or its family before the dot), created_gte and created_lt, starting_after. Each entry is kept 365 days, then deleted, and never changed before then. Every key of the account reads the same log.
GET
/v1/audit-log/exportDownload the audit log
The same entries and filters as a CSV file in the answer (csv, filename, columns), at most 5,000 entries a call; next_cursor names where the rest start. No cell opens as a formula in a spreadsheet.
Events
GET
/v1/eventsList your events
Every webhook event queued for your key that is still kept (each is kept 30 days), newest first, each as it was sent with what delivering it did. Page with limit (up to 100) and starting_after (the next_cursor of the page before, or an event id), and filter with type, delivery_state (none: recorded for your webhook destinations only, with no account webhook set) and created_gte and created_lt.
GET
/v1/events/{id}One event
One event by its id, as it was sent, with its delivery: pending, delivered or failed, how many times it was posted, the last status and the last error.
POST
/v1/events/{id}/redeliverSend an event again
The event queued once more for the address your webhook has now, signed the same way and with the same id. The answer is 202, and the event’s delivery then says what your server did. It counts against your key’s six deliveries a minute. Send destination_id to send it to that webhook destination instead, as a manual attempt that starts its retries again from the first.
GET
/v1/events/{id}/attemptsEvery attempt for an event
Each post made for one event at your webhook destinations: its number, automatic or manual, the status your server answered and when. destination_id narrows it to one.
GET
/v1/webhook-destinationsList your webhook destinations
Every destination your key posts events to, besides your account webhook: up to ten, each with its name, address, the event types it takes, whether it is enabled, how many deliveries it takes at once, and the last four characters of its secret.
POST
/v1/webhook-destinationsAdd a webhook destination
A named https address with its own signing secret, shown once in this answer. event_types narrows what it receives (leave it out for every type), enabled turns it off and on, and concurrency (1 to 10, 2 by default) is how many deliveries it takes at once. Only events Payerworks makes are sent. A delivery that fails is tried again 5 minutes, 30 minutes, 2 hours, 8 hours, 24 hours and 48 hours later; after the seventh attempt fails, the destination is disabled.
GET
/v1/webhook-destinations/{id}One webhook destination
One destination, with when it started failing and the notices sent about it. Never its secret.
PUT
/v1/webhook-destinations/{id}Change a webhook destination
Change any of its fields, turn it on again after it was disabled, or send rotate: true for a new secret (shown once); the old one keeps signing beside it for overlap_hours, 0 to 720, 24 by default.
DELETE
/v1/webhook-destinations/{id}Remove a webhook destination
No more events are sent to it, and its retries already scheduled are skipped. Removing one that is not there is not an error.
POST
/v1/webhook-destinations/{id}/pingPing a webhook destination
One signed ping event to this destination alone, whatever event types it takes. The answer is 202 with the event’s id.
GET
/v1/webhook-destinations/{id}/deliveriesWhat a destination received
Each event’s delivery at this destination, newest first: pending, scheduled (failed, with the next automatic attempt’s time), delivered or failed, how many attempts, the last status and error. Filter with state, created_gte and created_lt; page with limit and starting_after.
SFTP file drop
GET
/v1/sftpYour SFTP user
The host, your user name, your two folders (/inbound and /outbound), your SSH keys by fingerprint, when a 999 is written, and what is kept and for how long. A key with no user answers 404 sftp_user_not_found.
PUT
/v1/sftpMake your SFTP user
Makes this key’s SFTP user, or changes when a 999 is written for a file you drop: acknowledgment_999 is always, errors_only (the default: only when a group or a set is not plainly accepted) or never. A test key’s user sends usage indicator T to receiver PAYERWORKSTEST; a live key’s sends P to PAYERWORKS (qualifier ZZ). Payerworks validates and acknowledges what you drop; nothing goes on to a payer.
DELETE
/v1/sftpRemove your SFTP user
The user can no longer sign in. Answers already in /outbound are deleted by the bucket 7 days after each was written.
POST
/v1/sftp/ssh-keysAdd an SSH key
One OpenSSH public key line (ssh-ed25519, ssh-rsa or ecdsa), as your .pub file holds it; at most 10. There are no passwords. A private key is refused and never kept; a key’s comment is dropped.
DELETE
/v1/sftp/ssh-keys/{id}Remove an SSH key
That key can no longer sign in.
Batch jobs
POST
/v1/jobsMake a batch job
For more than one call can carry: name the route (route, as POST /v1/claims/validate) and, if every call carries one, the query. You get the job and a signed upload form for its file: { calls: [ ... ] }, each entry exactly the body that route takes in one call, up to 1,000 calls and 50 MB, uploaded within an hour. Each call is answered as the route answers it and held to its limits (2 MB a call; a larger one is answered 413 too_large at its index). A job runs claim validation, 837P writing, remittance, eligibility and claim status explanations, analytics, payment variance and claim tracking. POST /v1/eligibility/270 as a job is different: its file is that route’s own body with up to 7,500 requests, written as one 270. A file whose requests would write more segments than one 270 is read in fails the job job_270_too_many_segments. A job keeps your file until it finishes and its result for 24 hours, which no other route does: use invented data with a test key. Off until Payerworks turns it on; test keys only for now; 50 jobs a UTC day for an account’s keys together. Nothing is sent to a payer.
GET
/v1/jobsYour batch jobs
This key’s jobs, newest first, at most 100: each one’s route, state and counts. A job whose result’s 24 hours are over is listed as expired, with nothing kept, until its record is removed.
GET
/v1/jobs/{id}One batch job
Its state (awaiting_upload, running, completed, partial, failed, canceled or expired), its calls by status, and once it has finished, a link to its result good for 15 minutes; ask again for a new one. The result is { answers: [ { index, status, body } ] }, each answer what that call would have answered. A job that ran out of time is partial: the answered calls are in the result and not_run_from is the first to send again. A job that failed says why in error. A job.completed or job.failed event reaches your webhook and destinations when it ends. A job whose file arrived but could not be started on our side within an hour fails with job_not_started.
POST
/v1/jobs/{id}/cancelCancel a batch job
Before its file is uploaded, nothing will run. While it runs, it stops between two calls and keeps no result. A finished job cannot be canceled: delete it.
DELETE
/v1/jobs/{id}Delete a batch job
Its file, its result and the job itself, now rather than after 24 hours. A running job must be canceled first.
Organizations and members
GET
/v1/org/rolesThe roles
The one role table: every role, every permission and the owner’s rules. Readable with a key or a sign-in.
GET
/v1/org/membershipsYour organizations
The organizations this sign-in belongs to, with its role in each. A sign-in only.
POST
/v1/orgMake an organization
Make an organization for an account: name it and give one key of the account (api_key). The key proves the account and is checked and not kept; you become its owner. An account belongs to one organization. A sign-in only.
GET
/v1/orgYour organization
The organization, with your role and what it lets you do. Name it in the Payerworks-Organization header when your sign-in belongs to several. A sign-in only.
PUT
/v1/orgChange your organization
Rename it, or choose who manages the account’s keys (key_management): keys_and_members, the default, where an API key of the account may still make, rotate and revoke its keys, or members_only, where only a signed-in member with the role for it may. An owner’s.
GET
/v1/org/membersThe members
Every member: the email address of the sign-in, the name, the role, when they joined and who invited them. A sign-in only.
PUT
/v1/org/members/{id}Change a member’s role
A new role: owner, admin, developer or viewer. Only an owner makes, changes or demotes an owner, and the last owner is never demoted.
DELETE
/v1/org/members/{id}Remove a member
The member loses access at once. revoke_keys=true, an owner’s choice, also revokes the keys they made or rotated in the console (each one whose secret they were shown); left out, those keys keep working and are listed in the answer. The last owner is never removed, and a member may remove themselves to leave.
GET
/v1/org/invitationsThe invitations waiting
Every invitation not yet accepted or canceled: the address, the role, who invited and until when the link works. A sign-in only.
POST
/v1/org/invitationsInvite a person
Invite a person by email with a role. Payerworks emails a link that works for 7 days, and only for a sign-in at that verified address; the email names neither the organization nor who invited. An organization sends at most 25 invitations a UTC day.
DELETE
/v1/org/invitations/{id}Cancel an invitation
The link stops working at once.
POST
/v1/org/joinAccept an invitation
Accept an invitation with the token from its link (the part after #join=). The sign-in’s verified email address must be the address the invitation was sent to. A sign-in only.
GET
/v1/org/keysThe account’s keys
Every key of the organization’s account, test and live, as GET /v1/keys lists them, with who made each: a member in the console, or a key. Never a key itself. A sign-in only.
How claims and files are read
Claims are JSON in the 837P’s own structure: submitter, receiver, billing, subscriber (and dependent when the patient is not the subscriber), rendering and referring providers, and claimInformation with its patientControlNumber, claimChargeAmount, placeOfServiceCode, claimFilingCode, healthCareCodeInformation and serviceLines. patientControlNumber is your own reference, at most twenty characters; every answer about the claim carries it back. An institutional (837I) or dental (837D) claim takes the same shape, with institutionalService or dentalService on its lines and the fields its own route names.
curl "$PAYERWORKS_BASE_URL/v1/claims/validate" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" \
-H "Content-Type: application/json" \
-d @claim.json- A file’s kind is read from its functional group and its implementation guide, so a 277CA claim acknowledgment is never read as a claim status 277. A kind Payerworks recognizes and does not read yet is named, never called unreadable.
- One kind of file a call: an interchange whose functional groups are of different kinds is refused, naming them, so no part of it goes unread.
- Every answer fits in one response; one that would not is refused 413 answer_too_large, so split the file.
- A path value with a broken percent escape is refused 400 invalid_path. A query parameter may be at most 200 characters, and no query is ever logged.
Retries
Safe to retry
Send an Idempotency-Key header with any POST, and with the webhook PUT, and a retry gets the first answer instead of a second run.
- Use a new key for each new request: a UUID is a good one. A key is remembered for 24 hours.
- A retry with the same key and the same request gets the first answer again, with the header idempotent-replayed: true. What the first request did is not done a second time, and the sandbox’s webhook events keep their ids, so your server can ignore one it already handled.
- If the first answer said its webhook events could not be recorded or queued, and to retry, the retry with the same key does that step instead of repeating the refusal.
- The same key with a different request is refused with 422. While the first request is still running, a retry is refused with 409 and retry-after.
- An answer that failed on our side (a 5xx) is not remembered, so a retry runs the request again.
- Payerworks keeps a fingerprint of the request and the moment it ran, so it can answer the same way again. It never keeps the request or the answer.
key=$(uuidgen) # one new key per request; reuse it only to retry that request
curl "$PAYERWORKS_BASE_URL/v1/sandbox/claims" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $key" \
-d @claims.jsonEvery answer carries an x-request-id header naming that request, and every refusal carries request_id inside the error: the same id, except on an Idempotency-Key replay, where request_id names the request that first answered and x-request-id names the retry. Quote either when you write to us.
Answers pushed to you
Webhooks
Set an https address in the console and every answer the 837P claims sandbox (POST /v1/sandbox/claims) makes, and every change to an enrollment request, is posted to it as an event, the moment it is made, signed with a secret only you and Payerworks hold. An event your server does not take is tried again, and every event is kept for 30 days, to list and to send again.
pingSent when you ask for a test event, from the console or POST /v1/account/webhook/test. It carries no claim.
sandbox.file.acknowledgedThe 999 for your file: its verdict, how many transaction sets were received and accepted, and the control numbers of the interchange, the group and each set.
sandbox.claim.acknowledgedOne for each claim, from the 277CA: your reference, the payer claim number, accepted or rejected, and every fix as a sentence.
sandbox.remittance.receivedOne for each check in the 835, once the payer adjudicated anything (an 835 pays each billing provider by its own check): the check number, its date and total, and each claim that check paid, with its reference, status and amount paid.
enrollment.updatedAn enrollment request changed: a status change no type below names, or its fields, tasks, documents or notes. It carries the status, the previous status, whose turn it is, the reason, the fields that changed and the sequence.
enrollment.liveThe payer confirmed the request: files of this kind may flow. It adds when, and the payer’s reference.
enrollment.rejectedThe request was refused. It adds the rejection: the payer’s words whole, and the remedy.
enrollment.canceledThe request was canceled, or you asked to cancel one that is with the payer or live (cancel_requested says which).
enrollment.endedA live route ended: the payer ended it, or a cancel you asked for while it was live went through (in the sandbox, at the next advance).
enrollment.task.assignedA task was added: its id, rank, who holds it, its title and its fields.
enrollment.task.completedA task was finished, by the provider, or by Payerworks for its own.
enrollment.task.deletedA task was replaced by another (superseded_by names it).
job.completedA batch job finished, every call answered or, when it ran out of time, partly: its id, route, state and counts, and until when its result is kept. Never the result or a link to it: ask GET /v1/jobs/{id} for the link.
job.failedA batch job failed, or was stopped: its id, route, state, counts and the failure’s code. Nothing of the file you uploaded.
submission.file.acknowledgedThe partner’s 999 for a file you sent with POST /v1/claims/submit: accepted, rejected or partial, with each refused claim’s fix in words.
submission.claim.acknowledgedThe partner’s 277CA verdict on one claim you sent, with the payer’s claim number when it gives one, and each fix in words.
submission.remittance.receivedAn 835 whose every claim you sent: the check, and each claim’s billed, paid and patient share. An 835 that also pays claims you did not send is held, and reaches no one.
Every event is a POST of JSON in one shape. In it, created is the time in Unix seconds, mode is the key’s (a sandbox event is always test), and id is the same each time the same event is sent, a retry and a resend included, so you can ignore one you already handled.
POST /your/webhook
Content-Type: application/json
User-Agent: Payerworks-Webhooks/1
webhook-id: evt_8a1c…
webhook-timestamp: 1792324800
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
Payerworks-Signature: t=1792324800,v1=5f0c…
{ "id": "evt_8a1c…", "type": "sandbox.claim.acknowledged", "created": 1792324800, "mode": "test",
"data": { "reference": "PW-SBX-REJ", "verdict": "R", "fixes": [ "The claim was rejected…" ] } }Each POST is signed under the Standard Webhooks specification, so any of its libraries checks it: webhook-id is the event’s id, webhook-timestamp the time it was signed in Unix seconds, and webhook-signature is v1, a comma and the signature in base64.
That signature is HMAC-SHA256 over the id, a period, the timestamp, a period and the raw body exactly as it arrived, keyed with your signing secret’s bytes: the part after whsec_, decoded from base64.
Each POST also carries the Payerworks-Signature header the first receivers were built on: t, the time it was signed in Unix seconds, and v1, HMAC-SHA256 keyed with your whole signing secret over the time, a period and the raw body, written in hex.
Check one of them before you trust the body: compute the same HMAC, compare the two in constant time, and refuse a time more than five minutes from your clock, so a captured event cannot be sent to you again.
Checking the Standard Webhooks signature, in three languages:
import crypto from "node:crypto";
// headers: the headers of the request (Node gives their names in lowercase);
// rawBody: the request body exactly as it arrived, before any JSON parsing.
export function verifyWebhook(secret, headers, rawBody, toleranceSeconds = 300) {
const id = headers["webhook-id"];
const timestamp = String(headers["webhook-timestamp"] || "");
if (!id || !/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.`).update(rawBody).digest();
return String(headers["webhook-signature"] || "").split(" ").some((entry) => {
const [version, signature] = entry.split(",");
const given = Buffer.from(signature || "", "base64");
return version === "v1" && given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}Checking the Payerworks-Signature header instead, for a receiver built on it:
import crypto from "node:crypto";
// rawBody: the request body exactly as it arrived, before any JSON parsing.
export function verifyPayerworks(secret, header, rawBody, toleranceSeconds = 300) {
const fields = { v1: [] };
for (const part of String(header || "").split(",")) {
const at = part.indexOf("=");
if (at < 1) continue;
const name = part.slice(0, at).trim();
const value = part.slice(at + 1).trim();
if (name === "v1") fields.v1.push(value); else fields[name] = value;
}
const t = fields.t || "";
if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
return fields.v1.some((v) => {
const given = Buffer.from(v, "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}- Events are queued when the call is answered and posted a moment later, so your call never waits on your server. The call’s answer says how many were queued. A key may queue six deliveries a minute.
- Answer with any 2xx within four seconds and the event is delivered. A redirect, a timeout or any other answer is not, and Payerworks tries again: twice more, one and then two minutes later, sending only the events not yet delivered. All of one try’s events share ten seconds, and an endpoint that could not be reached is not asked again in that try.
- An event can arrive more than once, so ignore one whose webhook-id you already handled.
- Your address must be https, at a hostname rather than an IP address, on the standard port, with no user name or password in it.
- The signing secret begins whsec_ and is shown once, when it is made (and again to a retry of that same request with the same Idempotency-Key, within 24 hours). Setting the address again keeps it. Ask for a new one with rotate: true; the previous secret then signs every event beside the new one for 24 hours, or the overlap_hours you send (0 to 720), so a header carries two signatures and your server can switch over without missing one.
- A secret made before 10/19/2026 is written in an older alphabet (base64url, without padding). It keeps working, and the samples below read both; rotate it for one every Standard Webhooks library reads as it is.
Setting the address, rotating its secret and sending a test event from your own system:
# Set the address. The first answer carries the signing secret once: store it.
curl -X PUT "$PAYERWORKS_BASE_URL/v1/account/webhook" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "url": "https://example.com/payerworks/events" }'
# Rotate the secret: a new one, shown once; the old one signs beside it for 24 hours.
curl -X PUT "$PAYERWORKS_BASE_URL/v1/account/webhook" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/payerworks/events", "rotate": true }'
# Queue one ping event for it (202, with the event id).
curl -X POST "$PAYERWORKS_BASE_URL/v1/account/webhook/test" \
-H "Authorization: Bearer $PAYERWORKS_API_KEY"
# A moment later: what the delivery did, as webhook.last_delivery.
curl "$PAYERWORKS_BASE_URL/v1/account" -H "Authorization: Bearer $PAYERWORKS_API_KEY"Webhook destinations
- Besides the one account webhook, a key may hold up to ten webhook destinations, each with its own name, https address and signing secret (shown once), the event types it takes and how many deliveries it takes at once (1 to 10, 2 by default). Only events Payerworks makes are sent, and a ping always reaches the destination it is sent to.
- A delivery a destination did not take is tried again 5 minutes, 30 minutes, 2 hours, 8 hours, 24 hours and 48 hours after the attempt before it, seven attempts in all. After the seventh fails, the destination is disabled; PUT /v1/webhook-destinations/{id} with enabled: true turns it on again.
- Each post to a destination carries Payerworks-Attempt-Number, Payerworks-Attempt-Type (automatic, or manual for a redelivery you asked for) and Payerworks-Destination-Id beside both signatures. GET /v1/events/{id}/attempts lists every attempt, and a destination’s deliveries say which are pending, scheduled, delivered or failed.
- Set a notice address with PUT /v1/account/contact, and Payerworks emails it when a destination has been failing for 2 hours, for 24 hours, and when it is disabled. Each notice goes with the first failed attempt past its mark, so the first comes with the attempt about 2 hours 35 minutes after the first failure. A notice names the destination by its id and its address’s host, never by its name.
- POST /v1/events/{id}/redeliver with destination_id sends an event to that destination as a manual attempt and starts its retries again from the first. A destination’s secret is rotated the way the account webhook’s is, with overlap_hours.
The event log
- GET /v1/events lists your key’s events newest first, each as it was sent with what delivering it did: pending, delivered or failed, how many times it was posted, the last status (null when your server gave no answer) and the last error. An event that could not be queued, whose webhook was removed before it went, or that no delivery reported on within 65 minutes of being queued is failed too, its last error saying why and how to send it again. Pass limit (up to 100), type, and starting_after (the next_cursor of the page before, or an event id) to read further.
- GET /v1/events/{id} reads one event, and POST /v1/events/{id}/redeliver sends it again to the address set now. An event not yet delivered that is queued again, this way or by a retry with the same Idempotency-Key, reads pending until that delivery is done.
- An event is kept for 30 days and holds only what the event says: the sandbox payer’s invented answer, your claim references and your file’s control numbers, never the claim you sent; an enrollment event holds ids, statuses and words, never a tax id, a contact, a note or a document.
- An account’s keys together record up to 2,000 events a UTC day. Past that, a run still answers in full, but its events are not recorded or sent, and the answer says so; events are recorded again from 00:00 UTC (retry the run with the same Idempotency-Key then to record and send them).
# The newest ten events, then the page after them.
curl "$PAYERWORKS_BASE_URL/v1/events?limit=10" -H "Authorization: Bearer $PAYERWORKS_API_KEY"
curl "$PAYERWORKS_BASE_URL/v1/events?limit=10&starting_after=$NEXT_CURSOR" -H "Authorization: Bearer $PAYERWORKS_API_KEY"
# Only one type, and one event by its id.
curl "$PAYERWORKS_BASE_URL/v1/events?type=sandbox.remittance.received" -H "Authorization: Bearer $PAYERWORKS_API_KEY"
curl "$PAYERWORKS_BASE_URL/v1/events/$EVENT_ID" -H "Authorization: Bearer $PAYERWORKS_API_KEY"
# Send it again to the address set now (202).
curl -X POST "$PAYERWORKS_BASE_URL/v1/events/$EVENT_ID/redeliver" -H "Authorization: Bearer $PAYERWORKS_API_KEY"Your key’s rate, its calls by day and your webhook are in the console. Open the console.
Download and build
Client libraries and an MCP server
Three files, generated from the same route list as this page and openapi.json, so every route here is a method there. Each is plain source with no dependencies: download it, read it, and keep it in your own repository.
JavaScript
An ES module for Node 18 or later, on your server: one method per route, typed in JSDoc, and uploadDocument for an enrollment task’s PDF. Your key is a bearer key, so never put it in browser code.
/sdk/payerworks.mjsPython
For Python 3.9 or later, the standard library only: one method per route.
/sdk/payerworks.pyMCP server
For an AI agent: run it with node over stdio, and each route that answers a question is a tool, with the reads of your account and your enrollment requests, except the routes that answer with a PDF, the two that read a picture with a model, and the enrollment export. Setting, removing or testing your webhook, sending an event again, and every change to a provider, an enrollment request, a task, a document, a key or a destination stay with you. One tool writes to your account: a sandbox claims run records its events and posts them to your webhook, when one is set.
/sdk/payerworks-mcp.mjs
- The JavaScript and Python clients each send an Idempotency-Key on every POST and PUT, and retry a 429, a 503 or a 409 idempotency_in_flight at most twice, after the wait retry-after names, with the same key. A refusal is a PayerworksError carrying the status, the code, the sentence and the request id.
- The two clients also each check a webhook’s signature, in the Standard Webhooks headers or the Payerworks-Signature header, in constant time.
- The MCP server makes its calls the same way: an Idempotency-Key on every POST its tools send, and the same retries with the same key, waiting at most 30 seconds. A refusal reaches the model as a tool error carrying the status, the code, the sentence and the request id.
- Both clients can keep your own request log: requestLog: true (request_log=True in Python) keeps the last 1,000 attempts in your process, a number keeps that many, and requestLogFile (request_log_file) appends one JSON line an attempt to a file of yours. Each entry is the method, the path, the status, the request id, the time it took and the Idempotency-Key, never a body, a query or a header. Payerworks keeps no such record of your calls.
- Each of the three calls only the base URL you give it, never follows a redirect, and sends nothing anywhere else. The one exception is the JavaScript client’s uploadDocument, which posts your PDF, without your key, to the https upload address the API answered for it. Download again when this page lists a new route: each file names the build it was made from.
Add the MCP server to your AI client’s configuration:
// Download /sdk/payerworks.mjs from this site into your project.
import { Payerworks, PayerworksError } from "./payerworks.mjs";
const pw = new Payerworks({ apiKey: process.env.PAYERWORKS_API_KEY, baseUrl: process.env.PAYERWORKS_BASE_URL });
try {
const { valid, claims } = await pw.postClaimsValidate({ claim });
const page = await pw.getEvents({ query: { limit: 10 } });
} catch (e) {
if (e instanceof PayerworksError) console.log(e.status, e.code, e.message, e.requestId);
}With a test key, send invented data only. With a live key, the AI model and its provider read the patient data each tool sends and receives, so the server refuses a live key unless you set PAYERWORKS_MCP_ALLOW_LIVE_KEY=1. Set it only where that is permitted, for example under a business associate agreement that covers your AI client.
The same tools are also served by the API itself: POST /v1/mcp, over MCP’s Streamable HTTP transport (revision 2026-07-28, and the 2025 handshakes), with your key as the bearer and nothing to download. Each tool call is a call to its route under your key, metered and rate-limited as that route. With a live key no tool is called unless the request carries Payerworks-MCP-Allow-Live-Key: 1.
Keys and limits
- Send your key on every call as `Authorization: Bearer <key>`.
- A test key (pw_test_…) is yours from the form on this page, for invented data only. It writes test interchanges and runs the sandbox payer, and nothing sent with it reaches a payer.
- A live key (pw_live_…) writes production files. Payerworks issues it once a business associate agreement is signed; ask from the form at the foot of the page.
- Each key has a rate in calls a minute: a test key from this page has 60, and a key Payerworks issues has 120 unless another rate is agreed. The rate is counted in a window that resets at the start of each UTC minute. Every answer to a route called with a valid key, an error included, carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset (the seconds until the window resets), and the same figures in the RateLimit and RateLimit-Policy fields of the IETF draft. Past the limit the answer is 429 with retry-after. An answer given before a key is read carries none of them: a missing or invalid key, an unknown path (404), a method the route does not answer (405), a path that does not decode (400), or GET /v1/health, which takes no key.
- A call the API turns away before any work is done is counted as refused, apart from your calls: over the rate, a body that is not JSON or is too large, a query it cannot read, or an Idempotency-Key it cannot read, already used for another request or still running.
- A revoked key stops working within a minute.
- An account is the keys one key started. POST /v1/keys makes a named key of your key’s own mode and rate, shown once, with an expiry if you want one (1 to 365 days); a key never outlives the key that made it: its expiry is the sooner of expires_in_days and the making key’s own, and the making key’s when none is asked. An account holds 3 test keys or 25 live ones that are not revoked. Every key of an account may list its keys, and make, rotate and revoke them unless the account’s organization chose that only its signed-in members do.
- POST /v1/keys/{id}/rotate gives a key a new secret under the same key id, so its webhooks, events, usage and enrollments carry on. The old secret is still accepted for overlap_hours (24 by default, up to 720) and stops at once if the key is revoked; a second rotation ends the first one’s overlap. Two rotations at once: the first finishes, and the other answers 409 key_rotation_conflict and makes nothing.
Errors
Every refusal has one shape:
{ "error": { "code": "invalid_claims", "message": "One or more claims would be refused; nothing was written.", "claims": [ … ] } }- 400 The request is malformed: no claim, bad JSON, a bad id, a path or a query that does not decode, or an Idempotency-Key that cannot be read.
- 401 No key, or a key that is not valid; on an organization route, a key where a member’s console sign-in is needed, or a sign-in that has ended.
- 403 The key may not do this, such as a test key asking for a production file, a live key on a route open to test keys only, or a route not open on this deployment yet (sftp_not_open); or a member whose role does not allow the call (org_role_forbidden).
- 404 No such route, or no such event for this key.
- 405 The route does not answer that method; the allow header names the ones it does.
- 409 Not now: a test event before a webhook address is set, a request with the same Idempotency-Key still running (retry after the seconds retry-after names), a webhook or a destination that changed while your request ran, or a limit reached (ten destinations, an account’s keys, ten SSH keys).
- 410 Gone: an invitation’s link is used within 7 days, and this one is older. Ask a member of the organization for a new one.
- 413 The body is over the size limit, or the answer would be larger than one response carries (answer_too_large): split the file, or send fewer claims or questions a call.
- 422 The input was read and would be refused (every claim’s or question’s errors are listed, and nothing was written), an interchange mixes kinds of file, or an Idempotency-Key was already used for a different request.
- 429 Over the key’s rate (wait the seconds retry-after names), over its webhook deliveries a minute, or past the events its account records in a UTC day, or past its account’s model reads a day (a test event is refused until 00:00 UTC).
- 500 Something failed on our side. Retry; nothing you sent was kept.
- 501 This route is not offered where you asked, and asking again answers the same: only the hosted API answers it, because it reads your key’s account (answered_by_the_door), a model read is switched off on this deployment (model_read_not_offered), or the sandbox timeline is not connected here (timeline_not_offered); organization sign-in is not open here (org_not_offered); or a claim cannot be sent to a payer yet (submit_not_offered, with each of its gates). Neither client retries a 501.
- 502 A model read gave no usable answer for the file you sent (model_read_failed): send a clearer copy, or try again.
- 503 We could not read or write your key’s records, or queue its events, just now; the message says what was not done. Retry in a moment; both clients retry a 503 twice by themselves. The same when the part of the API that draws a PDF or reads a picture could not be loaded (documents_unavailable, model_read_unavailable).
- 504 A model read did not finish inside one call (model_read_timed_out): send one page, or a crop of the part you need.
Every error code
Every refusal’s error.code is one of these: stable, snake_case, and always said with the same status. The list is read from the engine’s own catalog. A refusal that lists problems field by field names each one’s own code and field inside it.
All 347 codes
no_claim- 400
- The body has no claim: send `claim` (or `claims`) in the 837P shape the docs describe.
too_many_claims- 400
- At most 100 claims a call.
invalid_claims- 422
- One or more claims would be refused; nothing was written or run. Each claim's errors are listed.
duplicate_reference- 422
- Two claims carry the same reference (patientControlNumber, CLM01); each must be unique so its answers match back.
no_x12- 400
- Send the file's text as `x12`.
not_read_here- 422
- This file is a kind Payerworks recognizes but does not read here yet; the error names its kind.
unrecognized_x12- 422
- This is not an X12 file Payerworks reads; the message lists the kinds it reads.
not_an_835- 422
- This is not an 835 remittance.
not_an_acknowledgment- 422
- This is not a 999, a 277CA or a TA1.
invalid_code- 400
- An adjustment reason code is one to four digits, or a letter and digits (such as 45, A1 or B13), optionally after its group.
invalid_category- 400
- A claim status category is a letter and up to two letters or digits, such as A1 or F2.
invalid_path- 400
- The path holds a percent escape that does not decode (for example %E0 or a lone %). Encode the value with encodeURIComponent.
invalid_sandbox_attachments- 422
- A 275 sent back to the sandbox cannot be read; nothing was run. Each problem is listed with its field.
answered_by_the_door- 501
- This route reads your key's account, so only the hosted API answers it, never the engine run on its own (in a browser or a test).
invalid_sender_id- 400
- sender_id is your submitter id at the receiver: 2 to 15 letters, digits or spaces.
invalid_receiver_id- 400
- receiver_id is the receiver's interchange id: 2 to 15 letters, digits or spaces.
invalid_control_number- 400
- control_number is the interchange control number you track, a whole number from 1 to 999999999.
invalid_usage- 400
- usage is "T" (test) or "P" (production).
test_key_production_file- 403
- A test key writes test interchanges only (ISA15 = T). Use a live key for a production file.
sandbox_test_only- 400
- A sandbox payer answers test interchanges only: usage is "T" or left out.
invalid_query- 400
- A query parameter's name or value is over 200 characters, or holds a percent escape that does not decode.
answer_too_large- 413
- This answer is larger than one response can carry: send less in each call (split the file, or send fewer claims or requests).
file_too_large- 413
- This file holds more than one call can read: the error names what was counted (`unit`) and the most one call reads (`limit`). Split it and send each part in its own call.
mixed_functional_groups- 422
- This interchange holds functional groups of different kinds: send each kind in its own interchange, so no part of it goes unread.
no_request- 400
- The body has no eligibility request: send `requests` (a list), `request`, or the request itself in the JSON shape the docs describe.
too_many_requests- 400
- At most 100 eligibility requests a call.
invalid_requests- 422
- One or more eligibility requests would be refused; nothing was written. Each request's errors are listed.
duplicate_trace- 422
- Two requests in one file carry the same trace (controlNumber); each must be unique so its answer matches back.
no_answer- 400
- Send the 271 as `x12` (the file's text) or as `json` (the JSON answer).
not_a_271- 422
- This is not an X12 271 eligibility answer: send the 271 with its ISA envelope.
not_a_271_json- 422
- This JSON is not an eligibility answer: it holds no plans, benefitsInformation, errors or subscriber.
invalid_asked- 400
- asked is { service_type_codes: [codes], date_of_service: YYYYMMDD }, each optional.
invalid_service_type- 400
- A service type code is one to three letters or digits, such as 30 or 98.
invalid_aaa_code- 400
- A reject reason code (AAA03) is one or two letters or digits, such as 72 or T4.
invalid_loop- 400
- loop is the loop the reason was returned in, such as 2100C or 2110D.
invalid_include- 400
- include names what to add to the answer: answer (the normalized read of the 271).
invalid_shape- 400
- shape names how the answer that include=answer adds is written: normalized (the default) or plans (the grouped JSON shape).
no_inquiry- 400
- Send the claim status question as `inquiry` (or `inquiries`), in the JSON claim status shape.
too_many_inquiries- 400
- At most 100 claim status questions a call.
invalid_inquiries- 422
- One or more claim status questions would be refused; nothing was written. Each question's errors are listed.
duplicate_inquiry_trace- 422
- Two questions in one file carry the same trace (encounter.trackingNumber); each must be unique so its answer matches back.
no_claim_status_answer- 400
- Send the payer's claim status answer as `x12` (a 277) or `json` (the JSON answer).
ambiguous_answer- 400
- Send the answer once: as `x12` or as `json`, not both.
invalid_claim_status_json- 400
- `json` is the JSON claim status answer, an object with its `claims` list (or its errors).
not_a_claim_status_answer- 422
- This is not a claim status answer Payerworks reads: send a 277 (005010X212) or the JSON answer.
invalid_status_asked- 400
- `asked` describes the question you asked; one of its fields is not in the form it takes.
not_a_claim_status_question- 422
- This is not a claim status question: send a 276 (005010X212) as `x12`, or the questions as `inquiry` or `inquiries`.
invalid_status_code- 400
- A claim status code is one to five digits, such as 65 or 689.
ambiguous_question- 400
- Send the claim status questions once: as `inquiry` or `inquiries`, or as `x12` (a 276), not both.
invalid_payer_name- 400
- `payer_name` is the payer's name as the 276 writes it (2100A NM103): text of at most 60 characters.
invalid_submitter_name- 400
- `submitter_name` is your organization's name as the 276 writes it (2100B NM103): text of at most 60 characters.
no_remittance- 400
- Send an 835 as x12, or the claims as a list in claims.
two_inputs- 400
- Send either x12 (an 835) or claims (a list), not both.
invalid_claim_facts- 422
- One or more claims or facts are not usable; nothing was decided. Each problem is listed with its field.
invalid_letter_facts- 422
- One or more facts are not usable; nothing was written. Each is listed with its field.
unknown_letter_kind- 400
- kind is one of the letter kinds the answer lists, with the facts each requires.
missing_facts- 422
- The letter needs facts that were not sent; the answer lists them.
letter_refused- 422
- The letter was not written: a fact holds a placeholder in brackets, which the guard refuses.
invalid_letter- 400
- Send the letter's text as letter, at most 20000 characters.
invalid_group_code- 400
- A claim adjustment group code is two letters, such as CO or PR.
is_a_remark_code- 400
- This is a remittance advice remark code (RARC), not a claim adjustment reason code (CARC).
invalid_limit- 400
- limit is a whole number from 1 to 100.
invalid_cursor- 400
- starting_after is the next_cursor of an earlier page, or the id of one of this key's events.
invalid_event_type- 400
- type is one of the event types the developers page lists.
invalid_event_id- 400
- An event id is evt_ followed by 16 to 64 lowercase hexadecimal characters.
event_not_found- 404
- No event of this key has that id. Events are kept for 30 days.
invalid_delivery_state- 400
- The delivery state filter names one of the states the developers page lists.
invalid_created- 400
- created_gte and created_lt are instants written like 2026-10-19T14:00:00Z, and created_gte comes before created_lt.
no_claims_to_track- 400
- Send the claims to track as `claims` (a list) or `x12_837` (an 837P interchange, or a list of them).
ambiguous_claims- 400
- Send the claims once: as `claims` or as `x12_837`, not both.
invalid_track_request- 400
- A field of the request is not in the shape it takes; each problem is listed with its field.
invalid_tracked_claims- 422
- One or more claims cannot be tracked; nothing was read. Each problem is listed with its field.
not_a_tracked_file- 422
- A file is not a kind its list takes: x12_837 holds 837P claims, acknowledgments holds 999s, 277CAs and 277 requests for additional information, remittances holds 835s, attachments holds solicited 275s. The error names the file and its kind.
too_much_to_track- 413
- More was sent than one call tracks: the error names what was counted (`unit`) and the most one call takes (`limit`). Send fewer claims or files a call.
invalid_import- 422
- The CSV could not be read as an enrollment import, so no row was checked or saved. Each problem is listed; the columns are in the import template.
export_takes_no_paging- 400
- An export holds every request that matches its filters, so it takes no limit or starting_after.
export_too_large- 413
- Every request matching these filters does not fit in one export. Narrow the filters (a payer, a transaction, a created range) and export each part.
invalid_provider- 422
- One or more provider fields would be refused; nothing was saved. Each problem is listed with its field.
provider_exists- 409
- This key already has a provider with this NPI and tax id; the error names it.
provider_limit_reached- 409
- A key holds at most 500 providers (a test key 50). Delete one you no longer use.
invalid_provider_id- 400
- A provider id is prv_ followed by 24 lowercase hexadecimal characters.
provider_not_found- 404
- This key has no provider with that id.
provider_in_use- 409
- The NPI and tax id can no longer change: a request for this provider has gone to a payer. The error names those requests.
provider_has_enrollments- 409
- This provider has requests that are open or live; cancel them first. The error names them.
invalid_starting_after- 400
- starting_after is the next_cursor of an earlier page, or the id of an item on an earlier page.
invalid_npi_filter- 400
- npi is the provider’s 10-digit NPI, with a valid check digit.
invalid_tax_id_filter- 400
- tax_id is the provider’s 9-digit EIN, with or without the hyphen after the second digit.
q_too_long- 400
- q is at most 100 characters.
invalid_enrollment- 422
- One or more enrollment fields would be refused; nothing was saved. Each problem is listed with its field.
enrollment_exists- 409
- This provider already has an open request for this payer and transaction; the error names it and its status.
enrollment_limit_reached- 409
- A key holds at most 5,000 enrollment requests (a test key 500). Delete drafts and ended requests you no longer need.
invalid_enrollment_id- 400
- An enrollment id is enr_ followed by 24 lowercase hexadecimal characters.
enrollment_not_found- 404
- This key has no enrollment request with that id.
enrollment_sent- 409
- This request has gone to the payer, so its fields can no longer change. Cancel it and file a new one.
enrollment_ended- 409
- This request is canceled, rejected or ended; nothing more can change. File a new request.
enrollment_not_deletable- 409
- Only a draft, canceled or rejected request can be deleted. Cancel it first.
enrollment_not_draft- 409
- Only a draft is submitted; this request already was.
invalid_cancel- 422
- reason is text of at most 200 characters, with no run of eight or more digits.
invalid_status_filter- 400
- status is one or more of the eight enrollment statuses, separated by commas.
invalid_transaction_filter- 400
- transaction is eligibility, claim_status, professional_claims or remittances.
invalid_payer_filter- 400
- payer_id is 1 to 80 letters and digits.
invalid_provider_filter- 400
- provider_id is prv_ followed by 24 lowercase hexadecimal characters.
invalid_waiting_on_filter- 400
- waiting_on is you, provider, payerworks or payer.
invalid_date_filter- 400
- A date filter is a day, YYYY-MM-DD, or an instant, YYYY-MM-DDTHH:MM:SSZ.
invalid_task_id- 400
- A task id is tsk_, the enrollment’s 24 hexadecimal characters, an underscore and two digits.
task_not_found- 404
- This key has no task with that id.
task_not_open- 409
- This request is not waiting on the provider, so its tasks cannot change now.
task_blocked- 409
- A task ranked before this one is still open; the error names it, its rank and who holds it. Tasks are finished in rank order.
task_is_payerworks- 409
- This task is Payerworks’ to do, not the provider’s. It is shown so you can see what Payerworks is waiting on.
invalid_task_values- 422
- One or more task values would be refused; nothing was saved. Each problem is listed with its field.
invalid_document_request- 422
- Send task_id, field_key and filename (ending .pdf, at most 120 characters); each problem is listed with its field.
field_not_a_document- 422
- That field of the task does not take a document.
document_limit_reached- 409
- An enrollment request holds at most 20 documents. Delete one you no longer need.
upload_unavailable- 503
- No upload address could be made just now; nothing was saved. Retry in a moment.
invalid_document_id- 400
- A document id is doc_, the enrollment’s 24 hexadecimal characters, an underscore and 8 more.
document_not_found- 404
- This key has no document with that id.
upload_missing- 409
- Nothing has arrived at the upload address yet. Upload the PDF, then confirm again.
not_a_pdf- 422
- The uploaded file is not a PDF (it does not start %PDF-). It was deleted; upload the PDF again.
document_too_large- 422
- A document is at most 10 MB. It was deleted; upload a smaller PDF.
document_not_uploaded- 409
- This document has not been uploaded and confirmed, so there is nothing to download.
document_in_use- 409
- A completed task names this document. Reopen the task first (complete: false), then delete it.
invalid_payer_id- 400
- A payer id is 1 to 80 letters and digits.
sandbox_nothing_to_advance- 409
- The sandbox payer has no move to make now: the next move is yours, the request has ended, or this scenario never moves. The error says whose turn it is.
changed_meanwhile- 409
- This request changed while yours ran, so nothing was saved. Send it again.
invalid_edit_filter- 400
- A filter on GET /v1/edits is snip_type (1 to 7), severity (error or warning), kind (deterministic or code_set) or runs_in (edits, validateClaim, validateSecondaryClaim or correction-plan); the message names the one that is not.
invalid_edit_code- 400
- An edit code is snake_case letters, digits and underscores, at most 64 characters, such as future_date.
edit_not_found- 404
- No edit has this code: GET /v1/edits lists every edit Payerworks runs and the ones it does not.
invalid_change- 400
- change is "replace" (correct a claim) or "void" (cancel it).
invalid_payer_claim_number- 400
- payer_claim_number is the payer's own number for the claim, at most 50 characters and none of the X12 delimiters (~ * : ^).
payer_claim_number_needed- 422
- The payer processed the claim, so the plan needs its claim number: it is CLP07 on the 835, REF*1K on the 277CA, or the number a claim status answer gives.
invalid_medicare_mac- 400
- medicare_mac is true or false.
invalid_adjudicated- 400
- adjudicated is true or false: true says the payer processed the claim even when its number is not at hand.
claim_type_not_planned- 422
- claim_type is "professional" or "dental": an institutional claim carries its frequency in the type of bill, which this plan does not cover yet.
invalid_correction_claim- 400
- original and replacement are each one claim in the 837P JSON shape POST /v1/claims/837p takes.
not_an_837p- 422
- An x12 read as claims (original_x12 and replacement_x12 on POST /v1/claims/correction-plan, x12 on POST /v1/claims/validate) is an 837P interchange (GS08 005010X222A1).
several_claims_in_file- 422
- original_x12 and replacement_x12 each hold exactly one claim: split a bulk file with POST /v1/x12/parse first.
adjudication_unknown- 422
- Say whether the payer processed the claim: send payer_claim_number (CLP07 on the 835, REF*1K on the 277CA) when it did, or adjudicated: false when it was rejected before processing (a 999 or 277CA rejection). Sending a processed claim again as a new one is a duplicate.
not_an_837d- 422
- With claim_type dental, original_x12 and replacement_x12 are each a one-claim 837D interchange (GS08 005010X224A2).
dental_not_checked_here- 422
- A dental claim sent as an 837D is read where the API runs (POST /v1/claims/correction-plan), not on this page: send it as JSON here, or call the API.
payer_not_in_directory- 404
- The Payerworks directory holds no published facts about this payer. It lists payers and plan programs from public federal files, not every payer, and not the payers Payerworks can send to.
not_a_directory_row_id- 400
- GET /v1/payers/{id} takes a directory row id (pwd_ and 16 hex), never a payer ID: search GET /v1/payers?q= to find a payer by its payer ID or name.
invalid_payer_query- 400
- A search parameter is out of shape; the error names it in field (q, state, type, limit or starting_after).
payer_directory_unavailable- 503
- The payer directory is not loaded where this was asked; the Payerworks API answers it.
no_claims_to_follow- 400
- Send the open claims as `claims` (each with its reference, payer_id and sent_on), or the files as `files` (x12_837, acknowledgments, remittances).
ambiguous_follow_up- 400
- Send the claims once: as `claims` or as `files`, not both.
invalid_follow_up_request- 400
- A field of the request is not in the shape it takes; each problem is listed with its field.
too_much_to_follow- 413
- More was sent than one call takes: the error names what was counted (`unit`) and the most one call takes (`limit`).
no_remittances- 400
- Send the 835s as remittances: a list of X12 files, each the file's text.
too_many_remittances- 413
- More was sent than one call reads across its files (at most 50 files, 1000 checks, 5000 claims, 5000 provider-level adjustments and 2000 deposits): the error names the unit and the limit. Send fewer in each call.
deposits_required- 400
- Send the bank's deposits as deposits: a list of { amount, effective_date, trace_number?, originator?, description?, id? }.
invalid_deposits- 422
- One or more deposits are not usable; nothing was matched. Each problem is listed with its field.
invalid_split_by- 400
- by is "payee" (the 2000 loop's provider, TS301) or "rendering_npi" (each claim's rendering provider, NM1*82).
value_holds_a_delimiter- 422
- A value in the file holds one of the characters Payerworks writes as a delimiter (* : ^ ~), so it cannot be copied unchanged; nothing was written.
invalid_payer- 400
- Name the payer by its slug from GET /v1/filing-limits, or by its name.
invalid_date_of_service- 400
- date_of_service is a day, YYYY-MM-DD.
no_filing_limit_on_record- 404
- No published filing window is on record for this payer. Ask the payer, or read your agreement with it; GET /v1/filing-limits lists every payer that has one.
invalid_analytics_request- 400
- A field of the request is not in the shape this route takes; each problem is listed with its field.
too_much_to_analyze- 413
- More was sent than one call reads: the error names what was counted (`unit`) and the most one call takes (`limit`). Send fewer files a call.
nothing_to_summarize- 400
- Send the claims (claims or x12_837) with their acknowledgments and remittances, or the remittances alone.
invalid_fee_schedule- 422
- One or more fee schedule rows are not usable; nothing was judged. Each problem is listed with its field.
no_claim_to_check- 400
- Send the claim to check as claim, in the 837P JSON shape POST /v1/claims/validate takes.
invalid_risk_claim- 422
- The claim cannot be compared: each problem is listed with its field. Nothing was read.
invalid_outcomes- 422
- One or more appeal outcomes are not usable; nothing was ranked. Each problem is listed with its field.
no_identity_request- 400
- Send `request` (the eligibility request the payer refused, in the JSON POST /v1/eligibility/270 takes) and the reasons: `reasons` [{ code, loop }] or the refusing `answer` (x12 or json).
invalid_identity_request- 422
- The request would be refused as a 270 before any retry is planned: its errors are listed.
invalid_reasons- 400
- reasons is a list of { code, loop }: code an AAA reject reason (such as 75), loop where it was returned (such as 2100C), at most 20.
invalid_known- 400
- known holds only text facts the caller gives: legal_first_name, previous_last_name, dependent_member_id, each at most 80 characters.
no_reasons- 422
- Neither reasons nor the answer names a reject reason to plan from.
no_answer_to_read- 400
- Send the 271 as `x12` (the file’s text) or as `json` (either JSON eligibility answer).
answer_not_read- 422
- An answer could not be read as an eligibility answer: send an X12 271 with its envelope, or a JSON eligibility answer.
invalid_service_type_test- 400
- Send { baseline, specific, service_type_code, baseline_service_type }: two answers (x12 or json), the specific service type code asked, and the baseline asked (30, or 35 for dental).
invalid_coordination- 422
- The coordination question would not be answered as sent: each problem is listed.
invalid_discovery_verify- 400
- Send { patient: { first_name, last_name, date_of_birth, address?, previous_addresses?, gender? }, coverages: [answers, x12 or json], date_of_service? }.
too_many_coverages- 400
- Verify at most 10 coverages a call.
no_authorization_request- 400
- The body has no review request: send `requests` (a list), `request`, or the request itself in the JSON shape the docs describe.
too_many_authorization_requests- 400
- At most 100 review requests a call.
invalid_authorization_requests- 422
- One or more review requests would be refused; nothing was written. Each request's errors are listed.
duplicate_authorization_trace- 422
- Two review requests in one file carry the same trace (controlNumber); each must be unique so its response matches back.
invalid_output- 400
- output is "x12" (the default), "fhir" (the Da Vinci PAS bundle shape) or "both".
no_authorization_answer- 400
- Send the 278 response as `x12` (the file's text), as `json` (Payerworks' own read of one) or as `fhir` (a Da Vinci PAS response Bundle or ClaimResponse).
not_a_278- 422
- This is not an X12 278 request for review and response (005010X217): send the 278 with its ISA envelope.
not_a_278_response- 422
- This 278 holds requests only (BHT02 13): send the payer's response (BHT02 11).
not_a_278_json- 422
- This JSON is not a read 278: it holds no `reviews` with a decision or a rejection.
not_a_pas_response- 422
- This FHIR resource is not a PAS response: send a Bundle holding a ClaimResponse, or the ClaimResponse, with a reviewAction or an error.
invalid_asked_requests- 400
- asked is the review request, or the list of them, that the response answers.
invalid_explain_include- 400
- include names what to add to the answer: answer (the read of the 278).
no_procedure_codes- 400
- Send `procedure_codes`: a list of HCPCS or CPT codes, such as L1833 or 64490.
too_many_procedure_codes- 400
- At most 100 procedure codes a call.
invalid_procedure_codes- 400
- Each procedure code is a HCPCS Level II code (a letter and four digits) or a five-character CPT code.
invalid_payer_type- 400
- payer_type is medicare_fee_for_service, medicare_advantage, medicaid, commercial or other.
invalid_state- 400
- state is the two-letter state the patient lives in, such as CA.
invalid_setting- 400
- setting is hospital_outpatient or other (where the service is furnished), or left out.
no_estimate_lines- 400
- Send lines: the planned services, each with the allowed amount you expect (allowed_amount), at most 50 a call.
too_many_estimate_lines- 400
- At most 50 estimate lines a call.
invalid_estimate- 422
- The estimate request has fields that cannot be read; nothing was figured. Each problem is listed.
member_required- 422
- This 271 answers more than one patient: send member_index to say which one to estimate.
no_member- 422
- This 271 answers no patient to estimate.
invalid_good_faith_estimate- 422
- The Good Faith Estimate is missing elements 45 CFR 149.610 requires, or has fields that cannot be read; nothing was laid out. Each problem is listed.
no_dme_items- 400
- Send items: each { hcpcs, delivered_on }, at most 100 a call.
too_many_dme_items- 400
- At most 100 items a call.
invalid_dme_items- 422
- One or more items cannot be read; nothing was answered. Each problem is listed.
invalid_fpl_request- 422
- The request has fields that cannot be read; nothing was figured. Each problem is listed.
guideline_not_defined- 422
- The HHS poverty guidelines are not defined for Puerto Rico or the other territories; send use_contiguous_table: true if your program uses the contiguous table there.
guideline_year_not_held- 422
- Payerworks holds the 2026 HHS poverty guidelines only; send guideline_year 2026 or leave it out.
invalid_document_option- 400
- An option of this document is not one the route takes: the error names it and what it takes.
too_many_forms- 400
- These claims need more CMS-1500 forms than one call draws (200, at six service lines a form): send fewer claims in each call.
no_remittance_file- 400
- Send the 835 as x12 (the file's text).
invalid_attachments- 422
- The 275 cannot be written as asked; nothing was written. Each problem is listed with its field.
documents_unavailable- 503
- The PDF could not be drawn just now: the part of the API that draws it is not loaded. Retry in a moment. The engine run on its own, in a browser or a test, draws no PDF; the hosted Payerworks API, at the base URL shown with your key, does.
unknown_appeal_form- 400
- form is redetermination (CMS-20027) or reconsideration (CMS-20033).
invalid_appeal_form_input- 422
- One or more fields cannot be typed into the form; nothing was filled. Each problem is listed with its field.
claim_not_on_remittance- 422
- The 835 holds no claim payment with this reference (CLP01); send the reference the denying claim payment carries.
not_medicare_fee_for_service- 422
- These are Original Medicare's forms, and the 835 says this claim is not Medicare fee-for-service (CLP06 is not MA or MB): use the plan's own appeal, or POST /v1/appeals/letter.
form_asset_changed- 503
- The form file on our side is not the one CMS published that we pinned, so nothing was filled. This is ours to fix.
no_image- 400
- Send the file as base64: file_base64 for a paper EOB, front_base64 (and back_base64 if you have it) for an insurance card.
invalid_image_base64- 400
- The file is not base64: send the file's bytes base64 encoded (A-Z, a-z, 0-9, + and /, with = padding), with no data: prefix.
image_too_large- 413
- The file is larger than one call reads: at most 1,400,000 bytes in all, before base64. Send a smaller copy (a lower resolution photo, or fewer pages).
unreadable_image- 422
- The file could not be read as a picture or a PDF; the error says why (reason) and what to send instead.
too_many_pages- 413
- The PDF holds more pages than one call reads: the error names the most (limit). Send fewer pages in each call.
model_read_not_offered- 501
- Reading a document with a model is not offered on this host: Payerworks turns it on route by route. This is not a passing fault, so sending the request again answers the same.
model_read_test_keys_only- 403
- This route answers test keys only, with invented documents, until an agreement covering the model is in place for live data. Use a test key.
model_read_unavailable- 503
- The document reader could not be loaded just now, so nothing was read. Retry in a moment. The engine run on its own, in a browser or a test, has no reader; the hosted Payerworks API does.
model_read_timed_out- 504
- The read did not finish inside one call's time. Send less: one page, or a crop of the part you need.
model_read_failed- 502
- The reader gave no usable answer for this file, so nothing is answered from it. Send a clearer copy, or try again.
not_an_eob- 422
- The reader did not see a payer's explanation of benefits or remittance advice in this file, so no draft was written.
not_an_insurance_card- 422
- The reader did not see a health insurance card in this file, so nothing was looked up.
model_read_daily_limit- 429
- This account's keys have made the most model-read calls an account may make on this route today (100 a UTC day, its keys together). Try again tomorrow.
invalid_seed- 400
- seed is your own string for the run, 1 to 64 of A-Z, a-z, 0-9, _ and -: the same seed, claims and start give the same run.
invalid_instant- 400
- start and as_of are moments written in ISO 8601 with a time and a zone (such as 2026-10-19T09:00:00Z), from the year 2000 to 2099, and as_of is not before start.
invalid_timeline_cursor- 400
- cursor is the next_cursor an earlier timeline answer gave, unchanged.
cursor_other_run- 400
- This cursor belongs to another timeline run: the claims, the seed or the start differ from the run that made it.
invalid_timeline_limit- 400
- limit is a whole number from 1 to 500.
invalid_format- 400
- format is "x12", "report" or "both".
timeline_not_offered- 501
- The sandbox timeline is not offered where this was asked: it is not connected to the claim checks here, so nothing was run. This is not a passing fault, so sending the request again answers the same.
no_rules- 400
- Send the rules as `rules`, a list of one or more rule objects.
too_many_rules- 400
- At most 100 rules a call.
invalid_rules- 422
- One or more rules are not written in the rule format; nothing was applied. Each rule's problems are listed.
no_mrf- 400
- Send the file's text as `csv` (the tall or wide CSV template) or the file as `json` (an object, or its text).
mrf_too_large- 413
- The file is longer than 1990000 characters, or holds more than 50000 rows or 50000 items: check it in pieces, the first three rows sent again with each piece and row_offset set.
mrf_not_json- 422
- `json` is not a JSON document that can be read.
invalid_row_offset- 400
- row_offset is a whole number from 0 to 1000000000: the rows of the whole file before the first data row of this piece.
invalid_destination_id- 400
- A destination id is wd_ followed by 16 lowercase hexadecimal characters.
destination_not_found- 404
- This key has no webhook destination with that id.
destination_limit_reached- 409
- A key holds at most 10 webhook destinations. Remove one to add another.
invalid_destination_name- 400
- A destination's name is 1 to 80 characters.
invalid_event_types- 400
- event_types is a list of the event types the developers page lists; an empty list, or leaving it out, receives every type.
invalid_enabled- 400
- enabled is true or false, or left out.
invalid_concurrency- 400
- concurrency is a whole number from 1 to 10: how many deliveries this destination takes at once.
invalid_overlap_hours- 400
- overlap_hours is a whole number from 0 to 720: how long the old secret is still accepted after a rotation.
destination_disabled- 409
- This destination is disabled. Enable it with PUT /v1/webhook-destinations/{id} and `enabled: true` first.
destination_conflict- 409
- The destination changed while this request ran, so nothing was saved. Send the request again.
not_at_destination- 404
- That event was never sent to this destination (it was made before the destination was added, or its type is filtered out). A ping or a new event will be.
invalid_contact_email- 400
- email is an address of at most 254 characters, or null to remove it.
contact_not_proved- 403
- Notices go only to an address Payerworks has proved: the one that asked for this account's test key by email and opened the link we sent. Send that address, or null to stop the notices. To use another, write to us from it.
invalid_key_id- 400
- A key id is pwk_ followed by 16 to 40 letters and digits.
key_not_found- 404
- This account has no key with that id.
key_limit_reached- 409
- An account holds at most 3 test keys or 25 live keys that are not revoked. Revoke one to make another.
invalid_key_name- 400
- A key's name is 1 to 80 characters.
invalid_expires_in_days- 400
- expires_in_days is a whole number from 1 to 365, or left out for a key that does not expire.
key_revoked- 409
- That key is revoked, so it cannot be rotated. Make a new key with POST /v1/keys.
key_rotation_conflict- 409
- Another rotation of this key finished while this one ran, so this one made nothing: the secret that other request showed is the key's current one. List the keys with GET /v1/keys, and rotate again if that secret was lost.
key_settings_out_of_range- 409
- The key's stored rate or account name is outside what a key may hold, so nothing was made or rotated; reason says which. Ask Payerworks to correct the key: sending the request again answers the same.
sftp_not_open- 403
- The SFTP file drop is not open on this Payerworks deployment. Every API route still answers.
sftp_user_not_found- 404
- This key has no SFTP user. Make one with PUT /v1/sftp.
invalid_acknowledgment_999- 400
- acknowledgment_999 is "always", "errors_only", "never": when a 999 is written for a file you drop.
unknown_sftp_field- 400
- PUT /v1/sftp takes acknowledgment_999 and nothing else. An SFTP user signs in with an SSH key only: add one with POST /v1/sftp/ssh-keys.
invalid_ssh_public_key- 400
- public_key is one OpenSSH public key line (ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521), as the .pub file holds it.
private_key_sent- 400
- That is a PRIVATE key. Nothing was kept: send the matching public key (the .pub file), and keep the private key on your own machine.
ssh_key_limit_reached- 409
- An SFTP user holds at most 10 SSH keys. Remove one to add another.
ssh_key_exists- 409
- This SFTP user already holds that SSH key.
ssh_key_refused- 422
- The SFTP endpoint refused that SSH key, so nothing was added. Send another public key; an ssh-ed25519 key is the simplest.
invalid_ssh_key_id- 400
- An SSH key id is key- followed by 17 lowercase hexadecimal characters, as GET /v1/sftp lists it.
ssh_key_not_found- 404
- This SFTP user holds no SSH key with that id.
sftp_unavailable- 503
- The SFTP user could not be read or changed just now, so nothing changed. Send the request again.
jobs_not_offered- 501
- Batch jobs are not offered on this host: Payerworks turns them on when they are ready. This is not a passing fault, so sending the request again answers the same.
jobs_live_not_offered- 501
- Batch jobs answer test keys only for now, because a job keeps the request and its result for a while: use a test key, with invented data.
invalid_job_route- 400
- route names the one route the job runs, as "POST /v1/claims/validate". A job runs: POST /v1/claims/validate, POST /v1/claims/837p, POST /v1/remittances/explain, POST /v1/eligibility/explain, POST /v1/claim-status/explain, POST /v1/analytics/summary, POST /v1/remittances/variance, POST /v1/claims/track, POST /v1/eligibility/270.
invalid_job- 400
- A job is { route, query? }: route the one route it runs, query the query string every call carries (no leading ?), or left out.
invalid_job_id- 400
- A job id is job_ and 24 letters or digits, as POST /v1/jobs answered it.
job_not_found- 404
- No job of this key has that id. A finished job is kept 24 hours.
job_not_cancellable- 409
- This job has already finished, so there is nothing to cancel: DELETE it to forget it now.
job_running- 409
- This job is running: cancel it first (POST /v1/jobs/{id}/cancel), then delete it once it says canceled.
jobs_daily_limit- 429
- This account's keys have made the most jobs an account may make today (50 a UTC day, its keys together). Try again tomorrow.
no_information_request- 400
- Send the payer's 277 request for additional information as x12 (the file's text).
not_an_information_request- 422
- This file is not a 277 request for additional information (005010X213): the error names what it is, and the route that reads it.
no_requested_claims- 422
- This request holds no claim (no TRN in a patient loop), so there is nothing to say or check.
invalid_information_request_call- 400
- A field of the request is not in the shape this route takes; each problem is listed with its field.
no_attachment_response- 400
- Send the solicited 275 to check as x12_275: the file's text, or a list of them.
not_an_attachment_response- 422
- A file sent as x12_275 is not a 275 this route can read: the error names the file and why.
invalid_audit_type- 400
- type is one of the audit log's entry types, or the part before its dot (key, webhook, enrollment...), as the developers page lists them.
invalid_audit_cursor- 400
- starting_after is the next_cursor of an earlier page of this audit log.
org_not_offered- 501
- Organizations and member sign-in are not open on this deployment yet. Keys still work as before.
org_session_required- 401
- This route needs a member's console sign-in, not an API key. Sign in at payerworks.com/console.
org_session_invalid- 401
- The sign-in could not be checked, or it has ended. Sign in again.
org_session_not_accepted- 403
- A member's sign-in manages an account; it does not make this call. Call this route with an API key.
org_email_not_verified- 403
- Confirm this sign-in's email address first: the code was emailed when the sign-in was made.
org_choose- 400
- This sign-in belongs to several organizations: name one in the Payerworks-Organization header (GET /v1/org/memberships lists them).
org_not_found- 404
- No organization with that id has this sign-in as a member.
org_not_a_member- 403
- This sign-in belongs to no organization yet. Make one with POST /v1/org, or open an invitation's link.
org_role_forbidden- 403
- Your role in this organization does not allow that. GET /v1/org/roles says which role does.
org_owner_only- 403
- Only an owner may do that: invite or make an owner, change or remove an owner, revoke a removed member's keys, or change the organization's settings.
org_last_owner- 409
- An organization always keeps at least one owner. Make another member an owner first.
org_member_not_found- 404
- This organization has no member with that id.
invalid_org_member_id- 400
- A member id is pwm_ followed by 16 lowercase hexadecimal characters.
org_invitation_not_found- 404
- No pending invitation matches. It may have been accepted or canceled; ask for a new one.
invalid_org_invitation_id- 400
- An invitation id is pwi_ followed by 16 lowercase hexadecimal characters.
org_invitation_expired- 410
- An invitation's link works for 7 days. Ask a member of the organization for a new one.
org_invitation_email_mismatch- 403
- This invitation was sent to another address. Sign in with the address the email reached.
org_already_member- 409
- That person is already a member of this organization.
org_invitation_pending- 409
- An invitation to that address is already waiting. Cancel it to send another.
org_account_taken- 409
- That key's account already belongs to an organization. Ask one of its owners to invite you.
org_key_not_usable- 403
- The api_key given is not a usable key (unknown, revoked or expired), so it proves no account.
org_limit_reached- 409
- An organization holds at most 200 members and 50 pending invitations, and a sign-in belongs to at most 20 organizations.
org_invitations_daily_limit- 429
- An organization sends at most 25 invitations a UTC day. Send more tomorrow.
invalid_org_request- 400
- The request is not what this route takes: errors names each field and why.
org_acting_key_required- 400
- Name the key this change is for in the Payerworks-Key header (its key id; GET /v1/org/keys lists them).
org_acting_key_not_found- 404
- The organization's account has no usable key with that id.
keys_managed_by_members- 403
- This account's organization chose that only its signed-in members manage keys. Make, rotate or revoke the key at payerworks.com/console.
org_changed_meanwhile- 409
- The organization changed while this request was made. Read it again and send the change again.
submit_not_offered- 501
- Payerworks does not send this claim yet: `gates` names each gate and whether it is open. Nothing was written or sent; write the 837P with POST /v1/claims/837p and send it on the connection you have.
submit_envelope_assigned- 400
- On a submission Payerworks sets the interchange: leave out sender_id, receiver_id, the two qualifiers, control_number and usage.
invalid_partner_id- 400
- partner_id is a partner's id as GET /v1/partners lists it, or left out to send to the one partner taking the claims' payer.
reference_prefix_required- 422
- Every claim's patientControlNumber must begin with your account's reference prefix, so each answer can be matched to you alone; the error names the prefix and the claims.
payer_not_at_partner- 422
- A claim names a payer this partner does not take claims for; send it in a call of its own to the partner that does.
reference_in_flight- 409
- A claim with this patientControlNumber was already sent to this partner and has not been rejected; a corrected claim (frequency 7 or 8) may reuse it.
submit_not_sent- 503
- The file was written but the partner's connection did not take it just now, so nothing was sent (`transmitted: false`). Send the call again.
not_found- 404
- No such route. The routes are listed at payerworks.com/developers.
method_not_allowed- 405
- This route does not answer that method; the allow header names the ones it does.
no_key- 401
- Send your API key as `Authorization: Bearer <key>`.
invalid_key- 401
- That key is not valid. Keys are issued by Payerworks; a revoked key stops within a minute.
rate_limited- 429
- Over this key's calls a minute. Wait the seconds retry-after names.
too_large- 413
- A request body may be at most 2 MB.
invalid_json- 400
- The body must be a JSON object.
invalid_idempotency_key- 400
- An Idempotency-Key is 1 to 255 visible characters, with no spaces, quotes or backslashes. A UUID is a good one.
idempotency_key_reused- 422
- This Idempotency-Key was used in the last 24 hours for a different request. Send a new key for a new request.
idempotency_in_flight- 409
- The first request with this Idempotency-Key is still running. Retry after the seconds retry-after names.
idempotency_superseded- 409
- What this request set has since been changed or removed, so its answer cannot be given again. Send a new request with a new Idempotency-Key.
internal- 500
- Something failed on our side. Retry; if it keeps failing, write to us with the request_id.
store_unavailable- 503
- We could not read or write this key's records just now; the message says what was not done. Retry in a moment.
queue_unavailable- 503
- We could not queue the events for delivery just now. Retry in a moment.
invalid_webhook_url- 400
- A webhook address is https, at a public hostname, on port 443, with no login, at most 500 characters.
invalid_rotate- 400
- rotate is true or false, or left out.
webhook_conflict- 409
- The webhook changed while this request ran, so nothing was saved. Send the request again.
no_webhook- 409
- This key has no webhook yet: set one with PUT /v1/account/webhook first.
too_many_events- 413
- This run made more events than one delivery carries, so none were queued. Send fewer claims a call.
webhook_rate_limited- 429
- Over this key's webhook deliveries a minute, so nothing was queued. Wait for the next minute.
event_limit_reached- 429
- This account's keys have recorded the 2,000 events an account keeps in a UTC day, so no more are recorded or sent this UTC day. Events are recorded again from 00:00 UTC; an event refused before then is never sent by itself.
Talk to us
What happens next
- 1
A person at Payerworks reads what you sent, usually within a business day.
- 2
One reply by email names the next step: our trading partner details, where each of your payers stands, or a key for the API.
- 3
Nothing follows unless you answer. There is no sequence behind this form.
Or write to hello@payerworks.com.