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_..."
| Key | Behaviour |
|---|---|
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": "..." } ]
}
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 401 | unauthorized | Missing, malformed or unknown API key. |
| 404 | filing_not_found, unknown_form, not_found | No such filing (for this key and mode), form or route. |
| 409 | idempotency_key_reused, request_in_progress, invalid_state | Same 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). |
| 413 | payload_too_large | Validate and FIRE files are limited to 20 MB. |
| 422 | invalid_request | The body has the wrong shape (unknown or missing fields). errors[] lists each problem. |
| 429 | rate_limited | Too many requests. Wait for Retry-After seconds. |
| 501 | live_mode_unavailable | Live submission is not available. |
| 502 | transmission_failed | The IRS side could not be reached. Retryable: the filing becomes failed and can be submitted again. |
| 500 | internal_error | Our 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
- Rate limit: 600 requests per minute per account and mode. Every response carries
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds). - Idempotency: send
Idempotency-Key: <unique string>onPOST /v1/filingsand corrections. Repeating the same key and body returns the first response withIdempotent-Replayed: true; the same key with a different body is409 idempotency_key_reused.
Filing lifecycle
| Status | Meaning | Next |
|---|---|---|
blocked | Validation found blockers. Nothing can be submitted. | Create a new filing with the fixes. |
ready | Valid and XML generated (provisional). | POST .../submit |
submitted | Sent; waiting for the acknowledgement. | Poll with GET or use webhooks. A background job also checks every minute. |
accepted | Acknowledged with no errors. | Final. |
accepted_with_errors, partially_accepted, rejected | The acknowledgement carries record errors (path points at the field). | Fix and POST .../corrections; the original becomes corrected. |
failed | The transmission could not be delivered. | Submit the same filing again. |
corrected | Superseded 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 filing | You get |
|---|---|
recipient TIN 123-45-6781 | accepted |
recipient TIN 123-45-6782 | rejected, error at recipients[i].tin; then correct it |
recipient TIN 123-45-6783 | accepted_with_errors |
| a mix of the above in one filing | partially_accepted |
payer name containing SLOW ACK | stays submitted for about 15 seconds |
payer name containing IRS DOWN | 502 transmission_failed, status failed |
Data handling
- Filing payloads, generated XML and webhook secrets are encrypted at rest (AES-256-GCM). TINs are never echoed in full by the API (
/v1/tin/checkmasks them). - Webhook payloads contain the filing view (ids, status, counts, issues), not TINs.
- Test and live data are separate per key.
Known limitations
- No request has reached the IRS. The simulator is not the IRS and the XML is provisional until the IRS schema package is loaded.
- Fileable forms: 1099-NEC, -MISC, -INT, -DIV. 1099-B, -K, -R and the other forms are readiness-check only.
- No list endpoint for filings, no delete for webhook endpoints, no API-key management endpoint yet.
- The hosted instance is a development deployment; do not send real taxpayer data to it.