> ## Documentation Index
> Fetch the complete documentation index at: https://www.finta.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tax uploads

> Record an income tax return or Delaware franchise tax report the company already filed outside Finta

If your company filed its taxes somewhere else, for example with a previous provider, you can record the filed return in Finta with its PDF. This is the same as clicking **I've already filed** on a tax year's Taxes page in the Finta app, and it gives the same result: the year's record is marked filed, the PDF is attached to it, and it shows on the Taxes page exactly as an upload made there would.

There are two uploads:

* [`POST /taxes/income_uploads`](/docs/api-reference/taxes/create-income-upload) records the filed **income tax return**.
* [`POST /taxes/franchise_uploads`](/docs/api-reference/taxes/create-franchise-upload) records the filed **Delaware franchise tax and annual report**.

Each takes a PDF you upload first, so recording a return takes two steps: upload the PDF to get a `file_id`, then call the tax upload with it. Uploading is one call with [`POST /files`](/docs/api-reference/files/create-file), or two with a direct upload: [`POST /file_uploads`](/docs/api-reference/files/create-file-upload), then a PUT of the PDF to storage. All four endpoints on this page require **API access: Manage** and **Taxes: Manage** for the key owner.

<Note>
  For a company with no records for a year, upload the income tax return first, then the Delaware report. The income upload sets up the year's records, including the Delaware one the report attaches to.
</Note>

## Step 1: upload the PDF

There are two ways to upload a PDF. Both return a file with a `file_` ID that you pass to a tax upload.

### With a multipart request

[`POST /files`](/docs/api-reference/files/create-file) takes the PDF as `multipart/form-data` in a field named `file`:

```bash theme={null}
curl https://app.finta.com/api/v1/files \
  -H "Authorization: Bearer finta_your_key_here" \
  -F "file=@2025 Form 1120.pdf;type=application/pdf"
```

```json theme={null}
{
  "id": "file_8dJ2kq9XbT4mNa",
  "object": "file",
  "filename": "2025 Form 1120.pdf",
  "size": 482113,
  "created": 1791961935,
  "expires_at": 1792048335
}
```

The content is checked as it arrives. A file that is not a PDF is refused with [`400 invalid_file`](/docs/api-reference/errors#invalid_file), whatever its name or declared type.

### With a direct upload URL

Where a multipart request isn't practical, such as an AI agent that can run `curl` in a terminal, use [`POST /file_uploads`](/docs/api-reference/files/create-file-upload). Send the file's name, its size in bytes, and its MD5 checksum in base64:

```bash theme={null}
wc -c < "2025 Form 1120.pdf"                     # size
openssl md5 -binary "2025 Form 1120.pdf" | base64  # checksum
```

```bash theme={null}
curl https://app.finta.com/api/v1/file_uploads \
  -H "Authorization: Bearer finta_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"filename": "2025 Form 1120.pdf", "size": 482113, "checksum": "r8Hq2wJ5bNf0uYk3cX7d1A=="}'
```

The response has a `url` to PUT the PDF's bytes to, straight to Finta's file storage, and the `headers` to send with the PUT:

```json theme={null}
{
  "object": "file_upload",
  "file_id": "file_8dJ2kq9XbT4mNa",
  "url": "https://files.example.com/upload?signature=example",
  "method": "PUT",
  "headers": {
    "Content-Type": "application/pdf",
    "Content-MD5": "r8Hq2wJ5bNf0uYk3cX7d1A==",
    "Content-Disposition": "inline; filename=\"2025 Form 1120.pdf\"; filename*=UTF-8''2025%20Form%201120.pdf"
  },
  "expires_at": 1791962544
}
```

PUT the file with **every** header in `headers`, exactly as given, before `expires_at` (10 minutes):

```bash theme={null}
curl --fail -X PUT --upload-file "2025 Form 1120.pdf" \
  -H "Content-Type: application/pdf" \
  -H "Content-MD5: r8Hq2wJ5bNf0uYk3cX7d1A==" \
  -H "Content-Disposition: inline; filename=\"2025 Form 1120.pdf\"; filename*=UTF-8''2025%20Form%201120.pdf" \
  "https://files.example.com/upload?signature=example"
```

Storage accepts only bytes that match the `size` and `checksum` you sent, so the URL can't be used to store a different file. Each call returns a new URL and a new file. Treat the URL as a secret until it expires.

The PDF check for a direct upload happens when a tax upload attaches the file: a file whose bytes were never PUT, or that is not a PDF, is refused then with [`400 invalid_file`](/docs/api-reference/errors#invalid_file).

### Rules for files

* A file belongs to the company of the key that uploaded it. Another company's `file_id` returns [`404 resource_missing`](/docs/api-reference/errors#resource_missing).
* A file attaches to **one** tax record, **once**. Attaching it again returns [`409 file_already_attached`](/docs/api-reference/errors#file_already_attached); upload the PDF again to attach it to another record.
* A file nothing attaches within **24 hours** of its creation expires (`expires_at` on a file from `POST /files`). It then returns `404 resource_missing`, and Finta deletes it. An attached file stays with its tax record.

## Step 2: record the return

### Income tax return

[`POST /taxes/income_uploads`](/docs/api-reference/taxes/create-income-upload) takes four required fields:

| Field | Meaning |
| - | - |
| `tax_year` | The tax year the return covers. Only **last year or the year before** (2025 or 2024 during 2026), the years the Taxes page offers. Any other year is refused with `400 invalid_request`. |
| `tax_firm` | The firm or person who prepared the return, such as `"Fondo"`. Required, as on the Taxes page. |
| `states_filed` | USPS codes of the states the return was filed in, such as `["CA"]`; `DC` is accepted. Send `[]` when no state return was filed. |
| `file_id` | A file from step 1 that is not yet attached. |

```bash theme={null}
curl https://app.finta.com/api/v1/taxes/income_uploads \
  -H "Authorization: Bearer finta_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"tax_year": 2025, "tax_firm": "Fondo", "states_filed": ["CA"], "file_id": "file_8dJ2kq9XbT4mNa"}'
```

The response is the year's income [tax record](/docs/api-reference/tax-filings#understand-one-tax-record):

```json theme={null}
{
  "id": "tax_income_example1",
  "object": "tax",
  "type": "income",
  "year": 2025,
  "jurisdictions": ["US", "US-CA"],
  "unmapped_jurisdictions": [],
  "filing_status": "filed",
  "filed_at": 1791961935,
  "filed_elsewhere": true,
  "extension_filed_at": null,
  "documents": [
    {
      "type": "filed_return",
      "filename": "2025 Form 1120.pdf",
      "content_type": "application/pdf",
      "download_url": "https://files.example.com/signed-return.pdf",
      "expires_at": 1791962235
    }
  ],
  "created": 1791961935,
  "updated": 1791961935
}
```

What the upload does:

* The PDF is checked first. A Delaware franchise tax report (a PDF whose text contains "Annual Franchise Tax Report") is refused with [`400 wrong_document_type`](/docs/api-reference/errors#wrong_document_type) and nothing is recorded; send it to the franchise upload instead.
* The year's income record is created, or **overwritten** if one exists. It is marked filed, with `filed_elsewhere: true`, and the PDF becomes its `filed_return` document with its original file name. `filed_at` is when you uploaded it, not the date the return was actually filed.
* The record's `jurisdictions` are `US` plus the states you sent.
* The year's Delaware franchise record is set up too. It is marked filed only if Finta's Delaware status check already shows the report filed; attach the report with the franchise upload.

### Delaware franchise tax report

[`POST /taxes/franchise_uploads`](/docs/api-reference/taxes/create-franchise-upload) takes two required fields:

| Field | Meaning |
| - | - |
| `tax_year` | The year the report covers, not the year it was filed: `2025` for the report filed in early 2026. Any four-digit year is accepted. |
| `file_id` | A file from step 1 that is not yet attached. |

```bash theme={null}
curl https://app.finta.com/api/v1/taxes/franchise_uploads \
  -H "Authorization: Bearer finta_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"tax_year": 2025, "file_id": "file_Q7rLm2ZcV9pWx3"}'
```

The year's Delaware record (the one that year's Taxes page shows, created if missing) is marked filed if it is not already, with `filed_elsewhere: true`. The PDF becomes its `filing_confirmation` document, replacing any earlier one, and is renamed `[<tax_year>] <company name> Delaware Confirmation.pdf`, as the Taxes page names it. The response is the year's franchise tax record.

## Retry safely

A tax upload that fails changes nothing and leaves the file unattached, so you can retry with the same `file_id` while it is valid. A tax upload that succeeded cannot be repeated with the same file; it returns `409 file_already_attached`. Uploading a new file for the same year replaces that year's PDF, as uploading again on the Taxes page does.

## Handle errors

| Status and code | What to do |
| - | - |
| `400 invalid_request`, `parameter_missing` | Fix the field named by `error.param`. |
| `400 invalid_file` | Upload a PDF. For a direct upload never PUT, PUT the bytes to the URL with every returned header before it expires, then retry; once it expires, create a new upload. |
| `400 wrong_document_type` | The PDF is a Delaware franchise tax report. Send it to `POST /taxes/franchise_uploads`. |
| `403 insufficient_permission` | The key owner needs API access: Manage and Taxes: Manage. |
| `404 resource_missing` | The file does not exist, belongs to another company, or expired. Upload the PDF again. |
| `409 file_already_attached` | The file is already attached. Upload the PDF again to attach it elsewhere. |
| `500 tax_upload_failed` | Nothing changed and the file is still unattached. Retry with the same `file_id`. |

```http theme={null}
POST /api/v1/taxes/income_uploads HTTP/1.1
Host: app.finta.com
Authorization: Bearer finta_your_key_here
Content-Type: application/json

{"tax_year": 2023, "tax_firm": "Fondo", "states_filed": [], "file_id": "file_8dJ2kq9XbT4mNa"}

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_request",
    "message": "tax_year must be 2025 or 2024: only last year's return or the year before's can be uploaded. Got 2023.",
    "param": "tax_year",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#invalid_request"
  }
}
```

See [Errors](/docs/api-reference/errors) for every code.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.