Skip to main content
Nearly every US state requires a company to file an annual report to stay in good standing: the state’s yearly check that the company still exists and that the details it holds are current. What it asks, what it costs, and whether it is required at all vary by state and by entity type. Missing one is expensive — states charge late fees, and a company that stays delinquent can eventually be dissolved. doola tells you what the state asks and what it charges, you collect the answers and the fee from your customer, and doola files the report with the state the company was formed in. One report per company per fiscal year, in six endpoints: Every path sits under /v1/partner/companies/{companyId}/compliance.
doola charges nothing through this API. priceInCents is the state fee doola settles with you outside it. Submitting and confirming are two separate calls so you can collect from your own customer on your own timing, in between.

The flow

While a report is still FILLED, calling POST …/annual-reports again replaces it whole. Resend every answer and every companyDetails correction: anything you leave out is dropped, not carried over. See Submitting twice.
The compliance calendar tells you when a report is due. The endpoints on this page tell you what to collect, what it costs, and where a report stands.

Requirements: what to collect

This endpoint is read-only. It describes what to collect; it neither starts a filing nor stores anything.
An annual report asks for two kinds of information: questions the state puts to the company, and company details doola already holds. This endpoint returns both as one ordered list, so you render the list you are given rather than deriving it from the state yourself. Most states ask no questions at all. For those companies the response carries only the company details, which is the common case rather than an edge one.
A Wyoming LLC, abbreviated. The full schema is in the Annual Report Filing section of the API reference.

Field groups

Every field carries a group. Read the group rather than guessing from the fieldId. Questions come first, then company details.

Rendering a field

Treat the list as data. New type values and new fields are added over time, so a client that switches on a closed set of ids will break; one that renders whatever it is handed will not.
Do not derive the requirements from the compliance calendar, or the other way round. They answer different questions from different sources, and for a small number of states they do not line up: a tracked due date does not promise a field list here, and the reverse holds too.

State fees: what it costs

This endpoint is read-only. It reports a price; it neither starts a filing, stores anything, nor charges anyone.
There is nothing to send and nothing to collect first: one call, one amount.
No body, no query parameters. The fee does not vary by year today.
Read both flags before acting on priceInCents:
  • isFilingRequired: is there a report to file at all? A zero on its own is ambiguous.
  • isMinimum: is this the exact fee, or the state’s floor?
This amount is a quote, read off what doola holds about the company. The price that counts is fixed when the report is submitted, from the figures the submission carries, and comes back on it as priceInCents. Where doola already holds every figure the state’s formula needs, the two are the same number.

A floor is not a quote

Some reports cannot be priced exactly before the figures exist: Wyoming’s, from its asset worksheet, and a Delaware C-corp’s when doola holds no authorized share count. Rather than refusing to answer, the endpoint reports the state’s floor and sets isMinimum: true:
Wyoming’s floor is the state’s own minimum plus its fixed processing fee, so it is a real charge and the final amount is never below it. The submission settles the exact amount: send the Wyoming asset answers, or a Delaware C-corp’s authorizedShares in companyDetails, and the report is priced from them. A Delaware C-corp whose share count doola already holds is priced exactly, so isMinimum is false even though the fee is computed from a formula. Branch on isMinimum, never on the state code: a state joining this list is not a breaking change.

Which answers move the fee

affectsFee: true on a requirements field marks a figure the state computes its fee from: the Wyoming asset answers, and the authorized share count on a Delaware C-corp. It is false on every field for almost every company, because most states price the report from data doola already holds. affectsFee and isMinimum answer different questions. affectsFee says the state’s formula reads that field. isMinimum says doola does not hold the figure yet, so the quote is only the floor. A Delaware C-corp with its share count on file has affectsFee: true on the share count and isMinimum: false on the quote. Use affectsFee to tell the customer which answers change what they pay. Do not use it to decide whether to collect a field; that is required.

When no report is filed at all

Whether a report is filed depends on the state and the entity type together. Alabama and Ohio require none from either, while in New Mexico and South Carolina only the LLC is exempt: a CCorp in either files and pays a fixed fee. Both reads answer 200 and publish the same isFilingRequired: false, so they never disagree. The requirements come back with an empty fields list:
The state fees come back with priceInCents: 0. Some states file an annual report and charge nothing for it; others file no annual report at all. Both report 0, and only isFilingRequired tells them apart.
Branch on isFilingRequired, never on priceInCents === 0. Treating a no-report company as free to file would have you collect answers for a filing that cannot be made. Submitting for such a company answers 422 E_STATE_NOT_SUPPORTED.

Submitting the report

This call records the report and fixes its price. It does not start the filing: that is confirming, a separate call on your own timing.
Read the requirements first. This call takes the answers to the questions they published, plus any corrections to the company details doola holds.
A Wyoming LLC with no assets in the state, also correcting its phone number:
A key in companyDetails means “use this instead of what doola has”. Leaving it out means “keep what doola has”; you cannot clear a value. An address replaces the one on file whole, so it takes the full address object (line1, line2, city, state, postalCode, country), and a partial one is rejected rather than merged. The response is 201 with the report on file:
This is the same report shape the reads return, plus isReplaced, so one type covers submitting, confirming and reading.

What is accepted

Only what the requirements published for this company. The check runs against the same list GET …/requirements returns, so the two cannot disagree.
  • An answer to a field the company was not offered is refused with 400, naming the field. It is never dropped silently.
  • A field behind a dependsOn gate is only demanded, and only accepted, while the gate is open. Answer Wyoming’s Q16 with No and none of its asset questions are asked for; send one anyway and it is refused.
  • Every required field that applies has to be present. For a Delaware C-corp that is phoneNumber and physicalAddress, the two required details doola does not hold.
  • Values are checked against the field’s type: NUMBER and AMOUNT are non-negative decimals with at most two places, SELECT and RADIO take one of the published options, DATE is yyyy-MM-dd.
  • authorizedShares and issuedShares are whole numbers. perValuedShares is money per share and keeps up to eight decimal places, enough for a $0.0001 par value; a ninth is refused rather than rounded away.

Submitting twice

One report per company per fiscal year. While the report is still FILLED, a second submission for the same year replaces it: the answers are replaced whole, the report keeps its annualReportId, its price is fixed again from the new figures, and the response is 200 with isReplaced: true. Company details reset too, so resend any correction you made the first time. That is how you correct a mistake, and how a Wyoming report moves from the floor to its exact amount. Once the report has moved past FILLED (you confirmed it, or doola is already working on or has filed that year) it is not overwritten: a second submission answers 409 E_ANNUAL_REPORT_ALREADY_EXISTS. A FILLED report has no deadline. It waits until you confirm it or replace it.

Confirming the report

This call hands the report to doola to file. Nothing is charged: confirming is you committing to the filing. Call it when your own customer has paid you, or whenever suits your flow.
No body. It answers 200 with the same report shape the submission returns. The report moves to IN_PROGRESS, doola starts the filing, and you receive company_annual_report_submitted. From then on the report can no longer be replaced.
  • Confirm every report, including one priced at 0. A state that charges nothing still has a report to file, and nothing happens until you confirm.
  • Confirming twice is safe. A report already past FILLED is returned unchanged, and doola never files it twice.
  • The report must belong to the company in the path. Any other report id answers 404 E_NOT_FOUND.

Reading reports back

These two endpoints are read-only. They show what the submission put on file and where doola has taken it since.
A company has at most one annual report per fiscal year. The list tells you which reports doola holds and where each stands; the single read adds the answers on record for one of them.

Listing a company’s reports

Newest fiscal year first. Every key is always present; a value doola does not hold is null, never dropped. An empty list means doola holds nothing to show for the company, not that the company does not exist. A company that does not exist, or is not yours, is a 404.

Reading one report

The same fields as one entry of the list, plus answers. Most states ask no questions, so an empty list is the common case.

Status

One vocabulary across every response on this page. A report the customer filed themselves, directly with the state, reads as FILING_COMPLETED: it is filed, and hiding it would let you think the year is unfiled and file it a second time. Every report you submitted is listed under one of these statuses. The one report you will not see is one the customer opened in doola’s own app that nobody has acted on yet: it is absent from the list and not readable by id either, so the two reads never disagree. New statuses may be added, so treat one you do not recognise as informational.
There is no channel yet for supplying what AWAITING_INFORMATION is waiting on. When one lands it will be a required action, but no annual report action exists today, so do not poll for one.

Webhooks

You do not have to poll a report to completion. Three webhook events cover it: The first two carry doolaCompanyId only: list the company’s reports to see which year moved. document_annualreport_uploaded arrives independently of the other two, so do not assume an order.

Errors

404 E_NOT_FOUND is deliberately undiscriminating: a report that does not exist and one that belongs to another company get the same answer, so an id you may not read is never confirmed to exist. Likewise a company that exists but is not yours is 404, never 403.
422 should not occur for a company created through this API: every US state and DC is covered for both LLC and CCorp. A company whose state files no annual report is not a 422 on the two reads. They answer 200 with isFilingRequired: false.
See Errors for the error envelope and the codes shared by every endpoint.