API reference

Build filings against the IrisTaxFiling API.

Validate, prepare, submit, track and correct U.S. information returns over JSON. Everything below runs in test mode against a built-in IRS simulator.

Test mode onlyXML is provisionalAPI v0.1.0
Base URL https://api.iristaxfiling.com
Test mode only. Every filing goes to a built-in IRS simulator, not to the IRS. The XML is provisional (not IRS-schema XML) and live keys cannot submit (501 live_mode_unavailable). Do not rely on an accepted status as proof that the IRS accepted anything.

Overview

IrisTaxFiling validates, prepares, submits, tracks and corrects US information returns (the 1099 family and the other IRIS forms). JSON over HTTPS. Money is a decimal number in dollars with at most two decimals. Field names are snake_case.

Authentication

Send the key as a bearer token on every /v1/* request. /health and /openapi.json need no key.

curl https://api.iristaxfiling.com/v1/usage -H "Authorization: Bearer if_test_..."
KeyBehaviour
if_test_...Test mode. Filings go to the IRS simulator. Data is kept separate from live mode.
if_live_...Live mode. Not available: creating filings works, submitting returns 501 live_mode_unavailable.

A missing, malformed or unknown key returns 401 unauthorized.

Errors

Errors use application/problem+json:

{
  "type": "about:blank", "title": "...", "status": 422,
  "code": "invalid_request",
  "detail": "What went wrong, and what to do",
  "errors": [ { "path": "recipients[0].tin", "code": "tin_invalid", "message": "...", "fix": "..." } ]
}
StatuscodeMeaning
400invalid_jsonThe body is not valid JSON.
401unauthorizedMissing, malformed or unknown API key.
404filing_not_found, unknown_form, not_foundNo such filing (for this key and mode), form or route.
409idempotency_key_reused, request_in_progress, invalid_stateSame Idempotency-Key with a different body; the first request is still running; or the filing's status does not allow the action (for example submitting a blocked filing).
413payload_too_largeValidate and FIRE files are limited to 20 MB.
422invalid_requestThe body has the wrong shape (unknown or missing fields). errors[] lists each problem.
429rate_limitedToo many requests. Wait for Retry-After seconds.
501live_mode_unavailableLive submission is not available.
502transmission_failedThe IRS side could not be reached. Retryable: the filing becomes failed and can be submitted again.
500internal_errorOur fault. Nothing else is revealed.

Filing problems found by validation are not HTTP errors: the filing is created with status blocked and the problems are in issues[] (severity is blocker or warning, with a path, code, message and fix).

Rate limits and idempotency

Filing lifecycle

StatusMeaningNext
blockedValidation found blockers. Nothing can be submitted.Create a new filing with the fixes.
readyValid and XML generated (provisional).POST .../submit
submittedSent; waiting for the acknowledgement.Poll with GET or use webhooks. A background job also checks every minute.
acceptedAcknowledged with no errors.Final.
accepted_with_errors, partially_accepted, rejectedThe acknowledgement carries record errors (path points at the field).Fix and POST .../corrections; the original becomes corrected.
failedThe transmission could not be delivered.Submit the same filing again.
correctedSuperseded by a correction or replacement.Final.

Endpoints

GET /health

Returns {"status":"ok"}. No key.

POST /v1/filings

Creates a filing. Fileable forms: 1099-NEC, 1099-MISC, 1099-INT, 1099-DIV. Add ?mode=validate for a dry run that stores nothing and returns a validation report. Headers: Idempotency-Key (optional). Responses: 201 filing (ready or blocked), 200 dry-run result, 409, 422.

curl -X POST https://api.iristaxfiling.com/v1/filings \
  -H "Authorization: Bearer if_test_..." -H "Content-Type: application/json" \
  -H "Idempotency-Key: nec-2026-001" -d '{
  "form": "1099-NEC",
  "tax_year": 2026,
  "payer": { "name": "Acme Platforms Inc", "tin": "12-3456789", "phone": "555-010-0100",
             "address": { "line1": "1 Main St", "city": "Austin", "state": "TX", "zip": "78701" } },
  "recipients": [ {
    "name": "Jane Contractor", "tin": "123-45-6781", "tin_type": "ssn", "account_number": "C-1001",
    "address": { "line1": "22 Oak Ave", "city": "Denver", "state": "CO", "zip": "80202" },
    "nonemployee_compensation": 12500.50 } ]
}'
{ "id": "fil_...", "object": "filing", "status": "ready", "mode": "test", "form": "1099-NEC", "tax_year": 2026,
  "recipient_count": 1, "summary": { "blockers": 0, "warnings": 0 }, "issues": [],
  "created_at": "...", "updated_at": "..." }

For 1099-MISC, -INT and -DIV, put box amounts in recipients[].amounts and text boxes (foreign country, CUSIP) in recipients[].details. GET /v1/forms/{id} lists the boxes; irisfile sample 1099-INT prints a full example. Unknown fields are rejected (422), so typos do not silently disappear.

GET /v1/filings/{id}

Returns the filing. If the filing is submitted and the acknowledgement is ready, it is applied first.

POST /v1/filings/{id}/submit

Submits a ready (or failed) filing. 202 with the filing (in test mode the simulator usually answers at once, so the status is already accepted or rejected). 409 invalid_state, 501 (live key), 502 (retryable).

GET /v1/filings/{id}/acknowledgements

Raw acknowledgements: { "object": "list", "data": [ { "outcome", "errors": [ { "code", "message", "path" } ], "raw", "received_at" } ] }.

POST /v1/filings/{id}/corrections

Creates a correction (?type=correction, default) or replacement (?type=replacement) of a finished filing. Send a full filing body. 201: a new ready filing with correction_of set; the original becomes corrected once it is rejected, partially_accepted or accepted_with_errors. If the original is not found or has no final result yet, the new filing is created as blocked with the issue correction_target_not_found or correction_target_not_final.

GET /v1/forms   GET /v1/forms/{id}   GET /v1/forms/{id}/template

The catalogue of all 37 IRIS forms (?family= filter), one form with its IRS template columns and rules, and a CSV template with a header and an example row. Only the four forms above can be filed; the rest support the readiness check.

POST /v1/forms/{id}/validate

Bulk readiness check. Send the IRS-template CSV (Content-Type: text/csv) or JSON {"rows":[{...}], "tax_year": 2026} keyed by IRS column label. Stores nothing. Returns status, record counts, problems grouped by rule with row numbers and fixes, and unknown columns. Limit 20 MB.

POST /v1/imports/fire

Converts a FIRE flat file (IRS Pub 1220 layout, raw text body) into filings for 1099-NEC, -MISC, -INT and -DIV. By default nothing is stored and each filing is returned with its readiness. ?create=true creates the filings; re-sending the same file returns the same filings. Records it cannot represent (other forms, G/C corrections, foreign addresses, unmapped amount codes) are reported in issues[] and not guessed. The importer also checks the "C" record payee count and control totals.

curl -X POST "https://api.iristaxfiling.com/v1/imports/fire?create=true" \
  -H "Authorization: Bearer if_test_..." -H "Content-Type: text/plain" --data-binary @fire.txt

POST /v1/tin/check

Body {"tin": "123-45-6789", "tin_type": "ssn|ein|itin|atin"} (tin_type optional). Checks the format only (not whether the IRS knows the TIN) and returns the TIN masked.

GET /v1/usage   GET /v1/audit

usage?period=YYYY-MM (default: this month) returns metered filings for the key's mode. audit lists actions on the account (filing created, submitted).

Webhooks

POST /v1/webhooks   GET /v1/webhooks   GET /v1/webhooks/deliveries

Register an http/https URL and the events to receive (["*"] or omitted for all). The signing secret (whsec_...) is returned once, on creation. There is no delete endpoint yet.

Events
filing.created filing.validated filing.ready filing.submitted filing.accepted filing.rejected filing.failed correction.required filing.corrected
{ "id": "evt_...", "type": "filing.accepted", "created": "2026-10-10T12:00:00.000Z", "livemode": false,
  "data": { "filing": { "id": "fil_...", "status": "accepted", ... } } }

Verify the signature. Each request has IrisFile-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 (hex) of "<t>." + raw body using the secret. Compare in constant time and reject timestamps more than 5 minutes old. Other headers: IrisFile-Event, IrisFile-Delivery.

const t = header.match(/t=(\d+)/)[1], v1 = header.match(/v1=([0-9a-f]+)/)[1];
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) && Math.abs(Date.now()/1000 - t) < 300;

Delivery and retries. Respond with any 2xx. Otherwise the delivery is retried (up to 6 attempts, waiting about 10 s, 1 min, 5 min, 30 min, then 2 h; retries run from a one-minute job) and then marked failed. Deliveries are at least once, so de-duplicate on id.

Test values (simulator)

Put this in your filingYou get
recipient TIN 123-45-6781accepted
recipient TIN 123-45-6782rejected, error at recipients[i].tin; then correct it
recipient TIN 123-45-6783accepted_with_errors
a mix of the above in one filingpartially_accepted
payer name containing SLOW ACKstays submitted for about 15 seconds
payer name containing IRS DOWN502 transmission_failed, status failed

Data handling

Known limitations