ee1099fAPI reference
v1 Β· live
e1099f Β· REST API

File 1099s, W-2s and more β€” from your code.

REST over HTTPS, JSON in and out, OAuth 2.0. Every write is idempotent, every environment is app-scoped, and nothing reaches the IRS from sandbox β€” under one IRS-authorized transmitter, LEIRAD GROUP LLC.

πŸ“„ FormsπŸ’³ PaymentsπŸ”” WebhooksπŸ›‘οΈ Extensions

Base URLs

πŸ§ͺ Sandbox
https://sandbox.e1099f.com/v1
Deterministic. Free. Never transmits. Keys sk_sandbox_…
πŸš€ Production
https://api.e1099f.com/v1
Real filings under our TCC, billed per form. Keys sk_live_…
iThe environment is the host β€” no test-mode flag, no data path between the two. A credential minted for one side refuses to work on the other.
OpenAPI 3.1 specificationEvery endpoint, schema and scope β€” machine-readable. Import it into Postman, Insomnia, or generate a typed client.

How filing works

Intake β†’ cart β†’ pay β†’ IRIS
Forms
POST …/{id}/formsscheme JSON
FIRE file
POST …/{id}/firePub 1220
Ready to file
saved Β· $0no charge
Cart
POST /cart/itemspick a service
Pay
POST /cart/payone order
IRISauto-submitted on pay

Two ways in β€” the Forms API (scheme JSON) or a FIRE file β€” both land as ready_to_file forms under a payer. Nothing is charged until you move them to the cart, choose a service, and pay; that one payment kicks off IRIS submission automatically.

1
IntakeCreate forms and bring them to ready_to_file. Forms or a FIRE file. Nothing is filed or charged.
β†’
2
PricesRead the services and their prices for those forms. Payments.
β†’
3
CartMove forms into the cart, each with a chosen service β€” { form_id, service }.
β†’
4
PayCharge once, then every form goes through the e-file pipeline.
Getting started

Quickstart

One recipient, one 1099-NEC, filed end to end against sandbox β€” five calls, nothing reaches the IRS. Swap sk_sandbox_… for sk_live_… (and the host) when you're ready for real.

StepCall
1 Β· TokenPOST /oauth/token β€” exchange your app's client credentials.
2 Β· Form shapeGET /v1/forms/1099-NEC β€” the scheme JSON to fill.
3 Β· FilePOST /v1/payers/{payerId}/forms β€” fill the scheme, add to cart.
4 Β· ReviewGET /v1/cart/details β€” confirm the line item and price.
5 Β· PayPOST /v1/cart/pay β€” charge once, transmit.
iNeed a payer id first? GET /v1/payers lists the ones your app can file under. In sandbox, your company comes pre-seeded.
The whole flow
# 1 Β· get a token
TOKEN=$(curl -s https://sandbox.e1099f.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$CLIENT_ID -d client_secret=$CLIENT_SECRET \
  | jq -r .access_token)

# 2 Β· get the form's shape (fill it from here)
curl -s https://sandbox.e1099f.com/v1/forms/1099-NEC \
  -H "Authorization: Bearer $TOKEN"

# 3 Β· file one form under a payer, straight into the cart
curl -s https://sandbox.e1099f.com/v1/payers/$PAYER_ID/forms \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "reference": "inv-1041", "addToCart": true,
    "forms": [{ "form": "1099-NEC", "tax_year": 2025,
      "jsonForm": {
        "RecipientDetail": {
          "TIN": "412557890", "TINSubmittedTypeCd": "INDIVIDUAL_TIN",
          "PersonName": { "PersonFirstNm": "Jordan", "PersonLastNm": "Ellis" },
          "MailingAddressGrp": { "USAddress": {
            "AddressLine1Txt": "88 Palm Ave", "CityNm": "Miami",
            "StateAbbreviationCd": "FL", "ZIPCd": "33101" } } },
        "NonemployeeCompensationAmt": 18400 } }] }'

# 4 Β· review the cart
curl -s https://sandbox.e1099f.com/v1/cart/details \
  -H "Authorization: Bearer $TOKEN"

# 5 Β· pay β€” the only call that transmits
curl -s https://sandbox.e1099f.com/v1/cart/pay \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
Getting started

Sandbox

A parallel world with its own database. Free, deterministic, and it never reaches the IRS β€” so you can assert on outcomes in your tests.

Deterministic rules

FilingA payer or recipient reference containing reject simulates an IRS rejection; everything else is accepted.
TIN matchAn all-zeros TIN returns not_issued; a TIN whose last digit is even returns match; odd returns mismatch.

Reset

POST/v1/sandbox/resetsandbox:reset

Wipes the account's sandbox filings back to a clean slate β€” useful between CI runs. Sandbox keys only.

Simulate a rejection
curl …/v1/payers/9ead482d-…/forms -H "Content-Type: application/json" \
  -d '{ "forms": [{ "form": "1099-NEC", "tax_year": 2025,
        "jsonForm": {
          "RecipientDetail": { "TIN": "412557890",
            "PersonName": { "PersonFirstNm": "reject-me", "PersonLastNm": "Ellis" }, … },
          "NonemployeeCompensationAmt": 100 } }] }'
POST /v1/sandbox/reset β†’ 200
{ "object": "sandbox_reset", "status": "reset", "cleared": 128 }
Conventions

Errors

One envelope, a machine-readable type. Branch on type, not the message.

422invalid_request β€” malformed or missing parameters
401unauthorized β€” missing, expired, or revoked credential
403insufficient_scope β€” token lacks the required scope
403ip_not_allowed β€” production source IP isn't in the account's allowlist
402payment_required β€” production filing not enabled / no funds
404not_found β€” no such resource in this environment
429rate_limited β€” wait for the Retry-After interval
Error envelope
{
  "error": {
    "type": "invalid_request",
    "message": "`tax_year` must be a 4-digit year."
  }
}
Conventions

Rate limits, paging & idempotency

600 requests / minute, per credential. The limit counts requests, not forms β€” a 1,000-form batch is one request. A 429 carries Retry-After in seconds.

Pagination

GET lists take ?limit= (default 100, max 500) and ?starting_after=<id>. Loop while next_cursor is non-null β€” cursors, not page numbers, because these collections are written to while you walk them.

Idempotency

Send Idempotency-Key on filing POSTs. The key is claimed before the work starts, so a retry that races an in-flight request gets 409 request_in_progress rather than filing twice.

List envelope
{
  "object": "list",
  "data": [ /* … */ ],
  "has_more": true,
  "next_cursor": "9f2a4c1e-4b0f-4a01-9fbd"
}
Idempotency header
Idempotency-Key: 6f1e9c0a-6a1d-4f0e-9a11-2f2b1f0f6d55
Conventions

Versioning

The API is versioned by date. Pin a version with the API-Version header and every response tells you which version served it. There is one active version today β€” 2026-06-15.

How a request resolves its version
API-VersionheaderSend a dated version, e.g. API-Version: 2026-06-15. An unrecognized value is rejected with 400 invalid_request β€” never a silent downgrade.
account defaultsettingA production request with no header uses your account's default version, set in developer console β†’ Settings.
currentfallbackWith neither of the above, the request runs the current version. Sandbox always runs current.

Every response carries an API-Version header naming the version that served it, so you can assert on it in tests.

Backwards-compatible changes

We add to the current version without cutting a new one. Non-breaking and shipped to everyone: a new endpoint, a new field on a response, a new optional request parameter, a new enum value, a new webhook event, or support for a new form or tax year. Write your parser to ignore unknown fields and new enum values so these never break you.

When we cut a new version

Removing or renaming a field, changing a field's type, or tightening validation is breaking β€” that only ever lands in a new dated version, never in one you're already on. Pin the version you built against (header or account default) and a newer version can't move under you until you deliberately adopt it.

Pin a version
curl …/v1/forms/1099-NEC \
  -H "Authorization: Bearer sk_live_…" \
  -H "API-Version: 2026-06-15"

# every response echoes it back:
#   API-Version: 2026-06-15
Unknown version β†’ 400
{
  "error": {
    "type": "invalid_request",
    "message": "Unknown API version `2020-01-01`. Supported: 2026-06-15."
  }
}
Safe to add, never breaking
+ new endpoint
+ new response field
+ new optional parameter
+ new enum value / webhook event
+ new form or tax year
Authentication

Authentication

Every request carries a bearer token β€” a server-side API key, or an OAuth 2.0 access token minted for one of your customers.

On behalf of customers (OAuth 2.0)

Authorization-code flow with PKCE (S256 required). The customer signs in, picks a single payer you may act for, approves your scopes. Codes are single-use, 5-minute; access tokens live 1 hour.

Scopes

filings:writeCreate and e-file returns
filings:readRead filings and status
payers:readList payers
recipients:writeCreate and update recipients
tin:matchRun IRS TIN matches
webhooks:manageManage webhook subscriptions
Authenticated request
curl https://sandbox.e1099f.com/v1/payers \
  -H "Authorization: Bearer sk_sandbox_9f2a4c1e77b0d31a"
const res = await fetch(base + "/v1/payers", {
  headers: { Authorization: "Bearer " + process.env.E1099F_KEY }
});
r = requests.get(base + "/v1/payers",
    headers={"Authorization": f"Bearer {key}"})
Authentication

OAuth 2.0

File on behalf of your customers. The authorization-code flow with PKCE (S256) lets a customer approve your app for one of their payers without ever handing you a password.

1 Β· Authorize

GET/oauth/authorize

Send the customer here. They sign in on the apex (a sandbox host never renders a password field), pick a single payer, and approve your scopes. You get a code back at your redirect_uri β€” single-use, expires in 5 minutes. Redirect URIs must match a registered value exactly and be HTTPS (http://localhost excepted).

2 Β· Token

POST/oauth/token

Exchange the code for tokens. Supported grants: authorization_code, refresh_token, client_credentials (server-to-server, acting under your own account). Access tokens live 1 hour.

Refresh & revoke

Refresh rotates: the presented refresh token is revoked and a fresh pair minted, so a leaked, already-used token is inert. A customer can revoke your app per payer from Connected apps β€” the token endpoint then refuses with invalid_grant, and access ends when the current access token expires. Grants are per (app, account, payer): revoking one payer doesn't touch the others.

OAuth
GET https://sandbox.e1099f.com/oauth/authorize
  ?response_type=code
  &client_id=clnt_7Kd2p9Xa
  &redirect_uri=https://app.example.com/cb
  &scope=filings:write%20payers:read
  &state=<csrf>
  &code_challenge=<BASE64URL(SHA256(verifier))>
  &code_challenge_method=S256
curl https://sandbox.e1099f.com/oauth/token \
  -u clnt_7Kd2p9Xa:csec_sb_9f2a… \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d redirect_uri=https://app.example.com/cb \
  -d code_verifier=<verifier>
Token response
{
  "access_token": "eyJhbGciOiJ…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_9f2a4c1e…"
}
{
  "error": "invalid_grant",
  "error_description": "Authorization was revoked by the account owner."
}
Forms

Forms

Intake: file forms under a payer (one or many per call), import a FIRE file, or correct an accepted return β€” all brought to ready_to_file. These endpoints do not file and do not charge β€” pricing, the cart and payment live under Payments.

Forms

File under a payer

File one or many forms under a single payer. Each form is a filled scheme JSON β€” the exact object GET /v1/forms/{form} hands you β€” so you work in the IRS's own vocabulary and never touch internal box names. Saved as ready_to_file; pass addToCart: true to stage them in the cart.

POST/v1/payers/{payerId}/formsfilings:write
iIntake only. This never files and never charges β€” it returns a form id per row that you price and pay for through the cart.
iThe flow: list your payers with GET /v1/payers β†’ get the form's shape from GET /v1/forms/{form} β†’ fill that scheme (recipient + amounts) β†’ POST it here under the payer's id. Every form in the call publishes under {payerId}.

Body

FieldDescription
formsarrayrequiredOne or more { form, tax_year, jsonForm }. jsonForm is the filled scheme β€” its RecipientDetail is the payee (matched-or-created under the payer), the element values are the amounts.
referencestringoptionalYour id, echoed back on each row
addToCartbooleanoptionalStage the saved forms in the cart β€” rows come back status: "in_cart".
servicestringoptionalOnly honored with addToCart: true; otherwise the cart's default service applies.
physical_mailbooleanoptionalOnly honored with addToCart: true. Overrides what service implies for the mail add-on; omit to keep service's default.
portal_accessbooleanoptionalOnly honored with addToCart: true. Overrides what service implies for billed portal access; omit to keep service's default.
iNo internal ids and no boxes: the payer is the route id (an account has many payers), the recipient rides inside the scheme, and there is nothing to learn about our box names β€” the scheme tells you exactly what to send. A payer-scoped app can only file under payers granted to it.
!Default service at the cart = efile_print_mail β€” e-file to the IRS + print & mail the paper copy + billed recipient portal access. Corrections default to efile_print_mail too β€” a correction can be e-filed, e-filed + printed & mailed, or print & mail only, exactly like an original. Use physical_mail/portal_access for combinations the three service names can't express.
Request
curl …/v1/payers/9ead482d-…/forms \
  -H "Authorization: Bearer sk_sandbox_9f2a…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1e9c0a-…" \
  -d '{
    "reference": "inv-1041", "addToCart": true,
    "forms": [{
      "form": "1099-NEC", "tax_year": 2025,
      "jsonForm": {
        "RecipientDetail": {
          "TIN": "412557890", "TINSubmittedTypeCd": "INDIVIDUAL_TIN",
          "PersonName": { "PersonFirstNm": "Jordan", "PersonLastNm": "Ellis" },
          "MailingAddressGrp": { "USAddress": {
            "AddressLine1Txt": "88 Palm Ave", "CityNm": "Miami",
            "StateAbbreviationCd": "FL", "ZIPCd": "33101" } }
        },
        "NonemployeeCompensationAmt": 18400
      }
    }]
  }'
await client.payers("9ead482d-…").filings.create({
  reference: "inv-1041", addToCart: true,
  forms: [{
    form: "1099-NEC", tax_year: 2025,
    jsonForm: {
      RecipientDetail: {
        TIN: "412557890", TINSubmittedTypeCd: "INDIVIDUAL_TIN",
        PersonName: { PersonFirstNm: "Jordan", PersonLastNm: "Ellis" },
        MailingAddressGrp: { USAddress: {
          AddressLine1Txt: "88 Palm Ave", CityNm: "Miami",
          StateAbbreviationCd: "FL", ZIPCd: "33101" } }
      },
      NonemployeeCompensationAmt: 18400
    }
  }]
});
Response
{
  "object": "payer_forms_intake",
  "payer": "9ead482d-…", "created": 1, "failed": 0,
  "livemode": true,
  "forms": [{
    "reference": "inv-1041#0", "id": "6a1d2e8a-…",
    "status": "in_cart", "added_to_cart": true,
    "cart_item_id": "c71f-…", "service": "efile_print_mail"
  }]
}
{
  "object": "payer_forms_intake",
  "payer": "9ead482d-…", "created": 0, "failed": 1,
  "forms": [{
    "reference": "inv-1041#0", "id": null, "status": "failed",
    "error": { "type": "invalid_request",
      "message": "`RecipientDetail.TIN` is required." }
  }]
}
Forms

FIRE import

The migration door off the retiring IRS FIRE system. Upload a Pub 1220 fixed-width file; we parse every payer, recipient and amount and bring the forms to ready_to_file. It parses and imports β€” it never files and never charges.

POST/v1/payers/{payerId}/firefilings:write

Raw request body (the file bytes), up to 12 MB. Pass addToCart: true to stage imported forms with the default efile_print_mail service.

POST /v1/payers/{payerId}/fire
curl "…/v1/payers/{payerId}/fire?addToCart=true" \
  -H "Authorization: Bearer sk_sandbox_9f2a…" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @IRSTAX.txt
200 Β· imported, ready to file
{
  "object": "fire_file", "bytes": 48210,
  "rows_parsed": 42, "forms_imported": 40,
  "forms_added_to_cart": 40, "service": "efile_print_mail",
  "form_ids": ["6a1d2e8a-…", "1271f344-…"],
  "cart_item_ids": ["c71f-…", "c8a0-…"],
  "forms": [
    { "form": "1099-NEC", "payer": "Cedar & Oak LLC",
      "recipient": "Jordan Ellis", "status": "in_cart" } ],
  "notes": [ "2 rows skipped: unsupported form 1099-DA." ],
  "next": "Added to the cart with service `efile_print_mail`. Pay with /v1/cart/pay."
}
Forms

Corrections

A material change after the IRS accepted a return β€” a new return that supersedes it, at the correction price. Distinct from a retransmission of a rejected filing, which is free.

POST/v1/filings/{id}/correctionsfilings:write
FieldDescription
{id}path Β· uuidrequiredThe original accepted filing being corrected. Must be accepted or accepted_with_errors β€” a rejected filing is re-filed, not corrected (404 if unknown). Payer, recipient, form and year are inherited from it.
boxesobjectrequiredJust the fields that changed, keyed by the same IRS IRIS element names the scheme uses (e.g. NonemployeeCompensationAmt). The original's payer, recipient and unchanged values carry over β€” a correction is a targeted edit to a known filing, so it takes only the delta, not a whole scheme.
addToCartbooleanoptionalStage with the default efile_print_mail (e-file + print&mail + portal). A correction is delivered like any form β€” you may choose efile or print_mail instead.
iA correction can be e-filed, e-file + print&mail, or print&mail only β€” default e-file + print&mail + portal. Price = the correction fee (set in the pricing schedule) plus the delivery you choose (mail, portal). To e-file a correction we must have filed the original (its IRS id is required) β€” corrections of forms we did not file are refused.
!A retransmission of a rejected filing is free β€” it was never on record. Only a change to an accepted return is a paid correction.
POST …/corrections
{
  "object": "correction", "id": "fil_c88",
  "corrects": "fil_a91", "form": "1099-NEC",
  "tax_year": 2025, "status": "queued",
  "amount_charged_cents": 794
}
{
  "error": { "type": "not_found", "message": "No such filing." }
}
{
  "error": { "type": "not_correctable",
    "message": "That filing was rejected, so it is not on file β€” submit it again instead of correcting it." }
}
Forms

Void a filing

Cancel a filing that hasn't gone anywhere yet and get the money back. Once it has left for the IRS the remedy is a correction.

DELETE/v1/filings/{id}filings:write
DELETE /v1/filings/fil_a91
{
  "object": "filing", "id": "fil_a91d20c7",
  "status": "voided", "amount_refunded_cents": 503
}
{
  "error": { "type": "not_cancellable",
    "message": "That filing is submitted and can no longer be cancelled. File a correction instead." }
}
Payments

Payments

Once forms are ready_to_file: read the prices, add each form to the cart with a chosen service, then pay. Payment charges once and sends every form through the e-file pipeline. Volume pricing is progressive and per order β€” one payment is one order.

Payments Β· step 1

Prices & services

Read the services you can buy for a form and what each costs, so you can choose one per form before adding it to the cart. Brackets are progressive; the quote is the price, not an estimate.

GET/v1/pricesfilings:read
GET/v1/prices/quotefilings:read

/quote?form=1099-NEC&service=efile_print_mail returns what this account pays for its next filing, itemized β€” the same method checkout charges with.

GET /v1/prices
{
  "object": "rate_card", "currency": "usd",
  "services": [
    { "id": "efile", "label": "E-file",
      "unit_cents": 269, "online_access_cents": 49 },
    { "id": "efile_print_mail", "label": "E-file + print & mail",
      "unit_cents": 269, "print_mail_cents": 185, "online_access_cents": 49 },
    { "id": "print_mail", "label": "Print & mail only",
      "print_mail_cents": 185, "online_access_cents": 49 } ],
  "brackets": [
    { "forms": "1-10", "efile_cents": 269 },
    { "forms": "11-100", "efile_cents": 169 },
    { "forms": "5001+", "efile_cents": 69 } ]
}
Payments Β· step 2

Add to the cart

Move ready_to_file forms into the account cart, each with its chosen service β€” a list of { form_id, service }.

POST/v1/cart/itemsfilings:write
GET/v1/cart/valuefilings:read
GET/v1/cart/detailsfilings:read
iNothing is filed or charged until you pay. The cart holds intent β€” change your mind without leaving a return behind.
iEach form entry also takes optional physical_mail/portal_access booleans β€” additive overrides on top of service. Omit them to get exactly what service has always implied; set them for a combination the three legacy service names can't express on their own.
POST /v1/cart/items
curl …/v1/cart/items -H "Content-Type: application/json" \
  -d '{ "forms": [
    { "form_id": "6a1d2e8a-…", "service": "efile" },
    { "form_id": "1271f344-…", "service": "efile_print_mail" },
    { "form_id": "d92e6a11-…", "service": "efile", "physical_mail": true }
  ] }'
200 Β· cart with a live quote
{
  "object": "cart", "id": "crt_44b1", "item_count": 2,
  "total_cents": 821, "currency": "usd",
  "charge_lines": [
    { "type": "FederalEFile", "amount_cents": 538 },
    { "type": "RecipientPortal", "amount_cents": 98 } ],
  "pricing_segments": [
    { "pricing_group": "FederalInformationReturns",
      "bracket": "1-10", "from_position": 1, "to_position": 2,
      "quantity": 2, "unit_cents": 269, "amount_cents": 538 } ]
}
Payments Β· step 3

Pay the cart

Charge the whole cart as one order, then send every form through the e-file pipeline β€” the same path every form in the app takes. Every item is validated and prepared before your card is touched.

POST/v1/cart/payfilings:write
A bad row costs nothing422 cart_not_payable lists each problem β€” nothing charged, nothing filed.
Retrying is safePaid cart β†’ receipt with already_paid; racing an in-flight pay β†’ 409.
Paid means filedA charged form that can't transmit returns its money to your balance; the rest stay filed.
POST /v1/cart/pay
{
  "object": "cart_payment", "cart": "crt_44b1",
  "status": "paid", "filed": 2, "failed": 0,
  "amount_charged": 821, "currency": "usd",
  "filings": [
    { "reference": "inv-1041", "id": "fil_a91",
      "status": "submitted", "amount_charged": 318 },
    { "reference": "inv-1042", "id": "fil_a92",
      "status": "submitted", "amount_charged": 503 } ]
}
{
  "error": { "type": "cart_not_payable",
    "message": "1 of 2 filings can't be submitted. Nothing was charged and nothing was filed." },
  "problems": [
    { "item": "…", "reference": "inv-1042",
      "type": "invalid_request",
      "message": "Recipient is missing a mailing address." } ]
}
Payments Β· receipts

Orders & receipts

A paid cart becomes an order β€” the receipt of that payment. List them, read one with its full charge breakdown, or fetch the IRS acknowledgment for everything it filed.

GET/v1/ordersfilings:read
GET/v1/orders/{id}filings:read
GET/v1/orders/{id}/ackfilings:read
GET/v1/orders/{id}/copy-bfilings:read

The receipt carries stable charge_lines (Membership, FederalEFile, RecipientPortal, PrintAndMail, StateFiling, Correction, Discount…) and pricing_segments β€” the exact progressive brackets the order was priced at, stored so an old receipt never re-prices when rates change.

GET /v1/orders
{
  "object": "list", "data": [
    { "id": "ord_71c2", "number": "E1F-2025-000148",
      "payment_status": "paid", "forms": 2,
      "created_at": "2025-05-01T05:14:00Z" } ]
}
GET /v1/orders/ord_71c2
{
  "object": "order", "id": "ord_71c2",
  "number": "E1F-2025-000148", "payment_status": "paid",
  "created_at": "2025-05-01T05:14:00Z",
  "charge_lines": [
    { "type": "FederalEFile", "label": "Federal e-file", "amount_cents": 538 },
    { "type": "PrintAndMail", "label": "Print & mail", "amount_cents": 370 },
    { "type": "RecipientPortal", "label": "Online access", "amount_cents": 98 } ],
  "pricing_segments": [
    { "pricing_group": "FederalInformationReturns", "bracket": "1-10",
      "from_position": 1, "to_position": 2, "quantity": 2,
      "unit_cents": 269, "amount_cents": 538 } ],
  "grand_total_cents": 1006, "currency": "usd",
  "forms": [
    { "id": "fil_a91", "form": "1099-NEC", "recipient": "Jordan Ellis",
      "status": "accepted" } ]
}
Payments

Prepaid balance

A wallet you can pre-fund; filing charges draw it before the card. The ledger is append-only and every line carries the running balance.

GET/v1/balancefilings:read
POST/v1/balance/top-upsfilings:write
GET/v1/balance/entriesfilings:read
GET /v1/balance
{
  "object": "balance", "available_cents": 24500, "currency": "usd",
  "auto_replenish": { "enabled": true, "threshold_cents": 5000, "topup_cents": 20000 },
  "recent": [
    { "kind": "topup", "amount_cents": 20000, "balance_after_cents": 24500 },
    { "kind": "filing", "amount_cents": -503, "balance_after_cents": 4500 } ]
}
Add-ons

Add-ons

The compliance services around a filing β€” TIN matching and recipient statements. Priced per use, with membership allotments applied automatically.

Add-ons

TIN matching

Check a recipient's name + TIN against IRS records before filing. Asynchronous by necessity β€” Pub 2108-A caps interactive matching at 999/24h on one platform User ID.

POST/v1/tin-matchtin:match
GET/v1/tin-match/{id}tin:match
iSandbox is deterministic: all-zeros TIN β†’ not_issued; even last digit β†’ match; odd β†’ mismatch.
Response
{ "object": "tin_match_run", "id": "tmr_5c19",
  "status": "processing", "count": 2 }
{
  "object": "tin_match_run", "id": "tmr_5c19",
  "status": "completed",
  "results": [
    { "recipient": "rcp_5f1c…", "result": "match" },
    { "recipient": "rcp_8a20…", "result": "mismatch" } ]
}
Add-ons

Recipient statements (Copy B)

The recipient's copy as a branded PDF, per filing, per batch, or per order. Available once the IRS accepts the return.

GET/v1/filings/{id}/copy-bfilings:read
GET/v1/orders/{id}/copy-bfilings:read
GET/v1/batches/{id}/copy-bfilings:read
GET …/copy-b
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="1099-NEC-copyb.pdf"
{
  "error": { "type": "not_ready",
    "message": "Copy B is available once the IRS accepts the filing." }
}
Status & events

Status & events

How a filing's state is reported, what the IRS said, and how to be told the moment it changes.

Status

Filing status vocabulary

Stable, and deliberately not our internal enum names β€” the pipeline can grow without breaking your code.

queuedAccepted by us, not yet transmitted.
submittedSent to the IRS; awaiting acknowledgment.
furnishingA print-&-mail-only return β€” we furnish the recipient copy but don't e-file it (you filed elsewhere). E-file + print&mail returns transmit and furnish, and report submitted β†’ accepted.
acceptedThe IRS acknowledged it, clean.
accepted_with_errorsAcknowledged with correctable issues.
rejectedNot on record β€” re-file it (free).
voidedCancelled before transmit; refunded.
supersededReplaced by a correction.
Status

Acknowledgments

The IRS decision for a filing, with its own rule ids and messages passed through unmodified.

GET/v1/filings/{id}/ackfilings:read
GET/v1/batches/{id}/ackfilings:read
GET/v1/orders/{id}/ackfilings:read

Batch and order acks return every filing's acknowledgment in one response β€” the raw IRS rule ids, severity, xpath and message, passed through unmodified.

GET …/ack
{
  "object": "acknowledgment", "filing": "fil_a91",
  "status": "accepted_with_errors",
  "receipt_id": "1095720250000123",
  "errors": [
    { "code": "F1099-032", "severity": "warning",
      "xpath": "/Return/…/RecipientNm",
      "message": "Recipient name control mismatch." } ]
}
Events

Webhooks

Signed delivery with retries β€” filing.accepted, filing.rejected, tin_match.completed, cart.paid.

POST/v1/webhookswebhooks:manage
GET/v1/webhookswebhooks:manage
PATCH/v1/webhooks/{id}webhooks:manage
DELETE/v1/webhooks/{id}webhooks:manage
POST/v1/webhooks/{id}/rotate-secretwebhooks:manage
GET/v1/webhooks/{id}/deliverieswebhooks:manage

Register, list, update the URL or events, disable, delete, rotate the signing secret, and inspect recent deliveries (status, attempts, last error). The secret is returned once β€” on create and on rotate.

Verifying the signature

Every delivery carries E1099-Signature: t=<unix>,v1=<hex>. The v1 digest is HMAC-SHA256 over "{t}.{rawBody}" β€” the timestamp is signed, so a captured delivery can't be replayed. Verify against raw bytes, compare in constant time, reject anything older than your tolerance.

filing.accepted Β· payload
{
  "id": "evt_9c0a", "type": "filing.accepted",
  "created": 1753008421,
  "data": { "id": "fil_a91d20c7", "form": "1099-NEC",
    "tax_year": 2025, "status": "accepted",
    "receipt_id": "1095720250000123" }
}
Verify E1099-Signature
const [t, v1] = header.split(",").map(p => p.split("=")[1]);
const expected = crypto.createHmac("sha256", secret)
  .update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(
  Buffer.from(expected), Buffer.from(v1))
  && (Date.now()/1000 - t) < 300;
var p = header.Split(',').Select(x => x.Split('=',2))
  .ToDictionary(x => x[0], x => x[1]);
var expected = Convert.ToHexString(HMACSHA256.HashData(
  key, Encoding.UTF8.GetBytes($"{p["t"]}.{rawBody}")))
  .ToLowerInvariant();
var ok = CryptographicOperations.FixedTimeEquals(
  Encoding.UTF8.GetBytes(expected),
  Encoding.UTF8.GetBytes(p["v1"]));
Compliance

Compliance

Extensions of time, and the payer & recipient records every filing points at.

Compliance

Extensions of time (8809)

When the deadline is close and the data isn't, ask the IRS for more time. Free β€” a request to the IRS, not a product.

POST/v1/extensionsfilings:write
GET/v1/extensionsfilings:read
GET/v1/extensions/{id}filings:read
FieldDescription
payeruuidrequiredOne request covers one payer
tax_yearintegerrequired
formsarrayrequired1099, 5498, 1042S, 1099QA, 5498QA, 5498ESA, 5498SA
reasonsobjectTY2026+The IRS requires a reason from TY2026 (rule F8809016)
!Asking twice for the same payer/year/families returns the original with already_requested: true β€” a duplicate 8809 is a rejection, not a longer extension.
POST /v1/extensions
curl …/v1/extensions -H "Content-Type: application/json" \
  -d '{ "payer": "9ead482d-…", "tax_year": 2026,
        "forms": ["1099", "5498"],
        "reasons": { "payee_statements_not_received": true } }'
201 Β· accepted
{
  "object": "extension", "id": "ext_71c2",
  "payer": "9ead482d-…", "tax_year": 2026,
  "forms": ["1099", "5498"], "status": "accepted",
  "receipt_id": "8809-2026-000045", "livemode": true
}
Compliance

Payers & recipients

The entity whose EIN goes on the return, and the payees it files for. A payer-scoped OAuth grant may not create payers.

POST/v1/payerspayers:write
PATCH/v1/payers/{id}payers:write
GET/v1/payerspayers:read
POST/v1/recipientsrecipients:write
PATCH/v1/recipients/{id}recipients:write
GET/v1/recipientspayers:read

A PATCH changes only the fields it names. A payer's TIN can't be changed through the API (it identifies the entity on returns already filed); a recipient's TIN can't be edited once they have filings β€” file a correction instead.

POST /v1/recipients β†’ 201
{
  "object": "recipient", "id": "rcp_5f1c9a",
  "payer": "9ead482d-…", "name": "Jordan Ellis",
  "tin_type": "ssn", "tin_last4": "4417",
  "state": "FL", "delivery_method": "electronic"
}
Schemas

Objects & models

Every request and response is built from the objects below. A field marked required must be present when you create the object; optional fields may be omitted. Write-only fields are accepted on input but never returned; response fields are server-assigned and ignored on input.

iA full Taxpayer Identification Number is write-only everywhere. Once stored, only its last four digits (tin_last4) are ever returned β€” the raw tin never leaves your app.
Conventions
{
  "id":         "uuid, server-assigned",
  "object":     "the type name, e.g. \"payer\"",
  "tin":        "write-only  Β· digits only",
  "tin_last4":  "response    Β· last 4 digits",
  "state":      "2-letter USPS code, upper-cased",
  "country":    "defaults to \"US\" when omitted"
}
Schemas

Payer

The business that files the return β€” the entity in the top-left box of every 1099. Created by POST /v1/payers, referenced by id everywhere else.

FieldDescription
iduuidresponseServer-assigned identifier. object is "payer".
legal_namestringrequiredLegal name of the filing business.
tinstring Β· 9 digitsrequired Β· write-onlyEIN or SSN, digits only. Never returned.
tin_typeenumrequiredein or ssn.
tin_last4stringresponseLast four digits of the stored TIN.
address_line1stringrequiredStreet address.
address_line2stringoptionalSuite, unit or PO box.
citystringrequiredCity.
statestring Β· 2 charrequiredUSPS state / territory code, upper-cased on save.
postal_codestringrequiredZIP or ZIP+4.
countrystringoptionalTwo-letter country code. Defaults to US.
contact_namestringrequiredPerson the IRS can contact about the filing.
contact_emailstringrequiredContact email.
contact_phonestring Β· digitsrequired Β· write-onlyContact phone; stored as digits.
is_main_companybooleanresponse Β· list onlyReturned by GET /v1/payers. true for the account's own company β€” the business the account itself is, as opposed to a client it files on behalf of. At most one payer is flagged, and many accounts flag none; filing works identically either way.
payer
{
  "object": "payer",
  "id": "9ead482d-3b7c-4a1e-…",
  "legal_name": "Northwind Freight LLC",
  "tin_type": "ein",
  "tin_last4": "4821",
  "address_line1": "410 Cedar St",
  "address_line2": "Suite 300",
  "city": "Austin",
  "state": "TX",
  "postal_code": "78701",
  "country": "US",
  "contact_name": "Dana Reyes",
  "contact_email": "ap@northwind.example"
}
Schemas

Recipient

The person or business that receives the statement β€” the payee. Belongs to exactly one payer. Created by POST /v1/recipients, or inline inside a filing (see below).

FieldDescription
iduuidresponseServer-assigned. object is "recipient".
payeruuidrequiredThe payer this recipient belongs to (from GET /v1/payers).
namestringrequiredRecipient name.
tinstring Β· 9 digitsrequired Β· write-onlySSN or EIN, digits only.
tin_typeenumoptionalssn or ein. Omit to match a recipient of any TIN type.
tin_last4stringresponseLast four digits of the stored TIN.
address_line1stringrequiredStreet address.
address_line2stringoptionalSuite, unit or PO box.
citystringrequiredCity.
statestring Β· 2 charrequiredUSPS state / territory code.
postal_codestringrequiredZIP or ZIP+4.
countrystringoptionalDefaults to US.
emailstringconditionalRequired when delivery_method is electronic.
delivery_methodenumoptionalpaper (default) or electronic.
account_numberstringoptionalYour account number for this recipient; printed on the statement.
iInline recipient. POST /v1/payers/{payerId}/forms also accepts these fields as an inline object in place of a recipient id β€” { tin, tin_type?, name, address_line1, address_line2?, city, state, postal_code, country? }. It is matched to an existing recipient under the payer by TIN, or created.
recipient
{
  "object": "recipient",
  "id": "rcp_5f1c9a",
  "payer": "9ead482d-…",
  "name": "Jordan Ellis",
  "tin_type": "ssn",
  "tin_last4": "4417",
  "address_line1": "88 Palm Ave",
  "city": "Miami",
  "state": "FL",
  "postal_code": "33101",
  "country": "US",
  "email": "jordan@example.com",
  "delivery_method": "electronic",
  "account_number": "A-2043"
}
Schemas

Filing

One information return for one recipient. Created by POST /v1/payers/{payerId}/forms as ready_to_file; it becomes billable only once it is in the cart and paid. Sandbox ids are prefixed fil_sb_.

FieldDescription
objectstringresponse"filing".
iduuidresponseFiling identifier.
formstringresponseForm type, e.g. 1099-NEC.
tax_yearintegerresponseFour-digit tax year.
statusenumresponseLifecycle state β€” see Filing status under Enumerations below.
payeruuidresponseThe filing business.
recipientuuidresponseThe payee.
serviceenumresponseDelivery service β€” efile, efile_print_mail or print_mail.
filing
{
  "object": "filing",
  "id": "6a1d2e8a-4b4b-4d20-…",
  "form": "1099-NEC",
  "tax_year": 2025,
  "status": "accepted",
  "payer": "9ead482d-…",
  "recipient": "rcp_5f1c9a",
  "service": "efile_print_mail"
}
Schemas

Correction intake

Returned by POST /v1/filings/{id}/corrections. A correction is a filing like any other β€” its correction fee comes from the form type, not the service β€” so it defaults to efile_print_mail and can be delivered any way an original can.

FieldDescription
objectstringresponse"correction_intake".
iduuidresponseThe correction form's id.
correctsuuidresponseId of the original filing. You must have filed the original through e1099f β€” the IRS needs its receipt id.
formstringresponseInherited from the original.
tax_yearintegerresponseInherited from the original.
statusenumresponseready_to_file, or in_cart when addToCart was set.
currencystringresponseusd.
added_to_cartbooleanresponseWhether it was staged in the cart in the same call.
cart_item_iduuid | nullresponseSet when added_to_cart is true.
serviceenumresponseDefaults to efile_print_mail.
correction_intake
{
  "object": "correction_intake",
  "id": "b2c9…",
  "corrects": "6a1d2e8a-…",
  "form": "1099-NEC",
  "tax_year": 2025,
  "status": "ready_to_file",
  "currency": "usd",
  "added_to_cart": false,
  "cart_item_id": null,
  "service": "efile_print_mail"
}
Schemas

Webhook endpoint

A subscription that receives event callbacks for the environment it was created in. The signing secret is returned in full exactly once β€” on create and on rotate-secret; afterward only its prefix is shown.

FieldDescription
iduuidresponseEndpoint identifier.
urlstring Β· httpsrequiredHTTPS URL that receives the event POSTs.
eventsstring[]requiredEvent types to subscribe to, or ["*"] for all.
statusenumoptionalactive or disabled.
signing_secretstringresponse Β· oncewhsec_…. Verify each delivery's signature with it. Shown in full only on create / rotate.
!Store the signing_secret when you first receive it. It cannot be read back β€” if you lose it, call POST /v1/webhooks/{id}/rotate-secret for a new one.
webhook Β· on create
{
  "id": "e8b1…",
  "url": "https://app.example.com/hooks/e1099f",
  "events": ["*"],
  "status": "active",
  "signing_secret": "whsec_3nZ8…"
}
Schemas

Cart item

A ready-to-file form staged for payment. Added with POST /v1/carts/{id}/items; the cart is billed as one order by POST /v1/cart/pay.

FieldDescription
form_iduuidrequiredA ready_to_file form's id.
serviceenumoptionalefile, efile_print_mail or print_mail. Omitted or unrecognised falls back to the form's default service (efile_print_mail).
cart item Β· request
{
  "form_id": "6a1d2e8a-…",
  "service": "efile_print_mail"
}
Schemas

Enumerations

The closed sets of string values used across the objects above.

FieldValues
tin_typeein Β· ssn
delivery_methodpaper Β· electronic
serviceefile Β· efile_print_mail Β· print_mail

Filing status

ValueMeaning
ready_to_fileSaved by intake, not yet in the cart. Not billed.
in_cartStaged in the cart, awaiting payment.
queuedPaid; waiting to transmit / furnish.
submittedTransmitted to the IRS; awaiting acknowledgement.
furnishingPrint-&-mail-only: the recipient copy is being furnished, with no IRS transmission for this form.
acceptedAccepted by the IRS.
accepted_with_errorsAccepted, but the IRS flagged issues to correct.
rejectedRejected by the IRS.
voidedCancelled before transmission.
supersededReplaced by a later filing.
status flow
ready_to_file
  β†’ in_cart
    β†’ queued  ── pay
      β”œβ”€ submitted β†’ accepted
      β”‚           β†’ accepted_with_errors
      β”‚           β†’ rejected
      └─ furnishing        (print & mail only)

voided       (before transmit)
superseded   (replaced later)
Schemas

Error

Every 4xx and 5xx response carries the same envelope. The HTTP status carries the class; error.type is the machine-readable code and error.message the human-readable reason.

FieldDescription
error.typestringresponseStable code, e.g. invalid_request, not_found, conflict.
error.messagestringresponseHuman-readable explanation of what to fix.

Status codes

HTTPTypical type
400Malformed request.
401Missing or invalid access token.
403ip_not_allowed β€” a production credential used from outside the account's IP allowlist.
404not_found.
409conflict β€” e.g. a correction already in flight, or a duplicate original.
422invalid_request β€” a field failed validation.
error Β· 422
{
  "error": {
    "type": "invalid_request",
    "message": "`tin` must be nine digits."
  }
}