The flow
Two reads stand outside this sequence. The compliance calendar tells you when a report is due, and the annual-report reads below tell you where any report stands at any point.
Requirements: what to collect
This endpoint is read-only. It describes what to collect; it neither starts a filing nor stores anything.
The full schema is in the Compliance section of the API reference. A Wyoming LLC, abbreviated:
Field groups
Every entry carries agroup, and the two groups behave differently. Read the group rather than guessing from the fieldId.
Questions are listed first, then the company details.
required tells you whether the filing is rejected without the field. A COMPANY_DETAIL with required: false is data doola already holds, so you do not have to ask the customer for it. A COMPANY_DETAIL with required: true is data we do not hold and cannot file without; today that is the phone number and physical address of a Delaware C-corp.
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.
This endpoint tells you what to collect; sending it back is the submission, which accepts exactly the fields published here, keyed by
fieldId.Do not derive the requirements from the compliance calendar, or the other way round. The two answer different questions from different sources: whether doola files an annual report for the state, and whether an obligation is currently tracked for the company. For a small number of states they do not line up, so a tracked due date is not a promise that this endpoint returns a field list, and a
422 here is not a promise that the calendar reports the state as unsupported.State fees: what it costs
This endpoint is read-only. It reports a price; it neither starts a filing, stores anything, nor charges the customer.
Amounts are cents so there is no decimal to round.
priceInCents is never null; a state that charges nothing reports 0.
Two flags decide how to read that amount, and they answer different questions:
isFilingRequired— is there an annual report to file for this company at all?isMinimum— is this the final charge, or the state’s floor?
priceInCents. A zero on its own is ambiguous, and so is a number.
The amount here is a quote, read off what doola holds about the company. The amount charged 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 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 setsisMinimum: true:
authorizedShares in companyDetails, and the result comes back on it as priceInCents.
A formula is not the same as a floor. Delaware computes a C-corp’s fee from its authorized share count: when doola holds that count the amount is exact and isMinimum is false. When it does not, the formula falls to its lowest bracket and isMinimum is true — the amount is real, but the company’s own fee is at least that and often more. Send the count as authorizedShares in companyDetails when you submit, and the report is priced from it rather than from the bracket. Computed and provisional are different things; only isMinimum separates them.
A state joining this list is not a breaking change —
isMinimum simply comes back true for more companies. Branch on the flag, never on the state code.Which answers move the fee
affectsFee on a requirements field marks the fields the state computes its charge from. It is false on every field for almost every company, because most states price the report from data doola already holds — the answers are for the filing, not for the price. Where it is true, the state’s formula reads that field: the Wyoming asset figures and the authorized share count on a Delaware CCorp. The price fixed when the report is submitted follows the values you send for them, and comes back as priceInCents.
affectsFee and isMinimum answer different questions. affectsFee says the state’s formula reads that field. isMinimum says the quoted amount is still only the state’s floor, which happens when doola does not hold the figure the formula needs — always for Wyoming, and for a Delaware CCorp with no share count on file. A Delaware CCorp whose shares doola already holds is quoted exactly, so isMinimum is false while affectsFee stays true on the share count. One with no count on file is quoted the floor, and priced exactly once you send the count with the report.
Use affectsFee to tell the customer which figures affect what they will 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 theLLC is exempt — a CCorp in either files and pays a fixed fee.
Both reads publish the same isFilingRequired for the same company, from the same source, so they never disagree. The requirements answer 200 with an empty fields list:
200 with priceInCents: 0 and isFilingRequired: false. 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.
isFilingRequired: false is a normal answer, not an error — on the two reads. The submission is the one call where it is an error, 422 E_STATE_NOT_SUPPORTED: there is nothing to submit.
Submitting the report
This call records the report and fixes its state fee. Confirming it is a separate step, on your own timing, and charges the
priceInCents returned here.
Every key in
companyDetails means “use this instead of what doola has”. Leaving a key out means “keep what doola has” — you cannot clear a value. A corrected address replaces the one on file whole, so a partial address is rejected rather than merged.
isReplaced added, so one generated type covers the submission, the payment confirmation and both reads.
What is accepted
Only what the requirements published for this company. The check runs against the same listGET /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 gate is only demanded, and only accepted, while the gate is open. Wyoming’s
Q16(“do you hold assets?”) gates its asset figures: answernoand none of them are asked for — send one anyway and it is refused. - Every
requiredfield that applies has to be present. For a Delaware C-corp that isphoneNumberandphysicalAddress, the two details doola does not hold. - Values are typed by the field’s
type:NUMBERandAMOUNTare non-negative decimals with at most two places,SELECTandRADIOcome from the publishedoptions,DATEisyyyy-MM-dd. authorizedSharesandissuedSharesare counts, so they have to be whole numbers.perValuedSharesis 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. A second submission for the same year replaces the first while nothing has been paid for it and no payment is in progress — the answers are replaced whole, the report keeps itsannualReportId, its price is fixed again from the new figures, and the response is 200 rather than 201, with isReplaced: true. That is how you correct a mistake before paying, at no cost, and how a Wyoming filing converges on its exact amount from the floor.
Once a payment for the report is in progress, or it has been paid for, filed, or is being worked on, it is not overwritten: a second submission answers 409 E_ANNUAL_REPORT_ALREADY_EXISTS.
There is no deadline on the unpaid state. A report sits at FILLED indefinitely, and resubmitting is what frees the year’s slot.
Confirming the report
This call hands the report to doola to file. Nothing is charged: doola settles the state fee with you outside this API, so confirming is you committing to the filing. Call it when your own customer has paid you — before or after is your decision.
IN_PROGRESS, at which point doola opens the filing task and sends company_annual_report_submitted. From then on the report can no longer be replaced.
A priceInCents of 0 does not skip this call. The confirmation is still yours to make, and the filing starts either way.
Calling confirm twice is safe. Once the report has moved past FILLED the second call is a no-op that answers 200 with the report unchanged.
Reading reports back
These two endpoints are read-only. They show what the submission put on file and where doola has taken it since.
annualReportId the submission returned.
Listing a company’s reports
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
answers: what was answered for this report. Most states ask no questions, so an empty list is the common case.
Status
One vocabulary across every annual-report response, so a report reads back under the status it was returned with.
There is no rejected status. A report the customer filed themselves, directly with the state, is reported as
FILING_COMPLETED: it is filed, and hiding it would let you think the year is unfiled and file it a second time.
There is no channel yet for supplying what
AWAITING_INFORMATION is waiting on. When one lands it will be a required action, which is how doola already asks a partner for something during formation, but no annual-report action code exists today, so do not poll for one.Webhooks
You do not have to poll a report to completion. Two webhooks mark the ends of the flow:company_annual_report_submitted once you confirms and doola starts the filing, and company_annual_report_completed once the state has the report, which is terminal. document_annualreport_uploaded fires separately when the filed report is stored and downloadable — independently of the two above, so do not assume an order.
Errors
404 E_NOT_FOUND is deliberately undiscriminating: a report that does not exist, one belonging to another company, and one in a state you are not shown are all the same answer. Telling them apart would confirm that an id you may not read is nonetheless real.
422 should not occur for a company created through this API. Every US state and DC is covered for both LLC and CCorp, so it is declared for completeness — a state added before its pricing lands, an entity type outside this API’s reach, or a pricing row carrying no amount. The last is a fault on doola’s side, and only the state fees can hit it: that read refuses rather than quoting 0, which would be indistinguishable from a state that files at no charge, while the requirements never read an amount. A company that simply files no annual report is not a 422 on either read: they answer 200 with isFilingRequired: false. On the submission it is, because there is nothing to submit.400 on the state fees: the request carries nothing to validate.
Ownership is enforced the same way across the Partner API: a company that exists but isn’t yours returns 404, never 403, so existence cannot be probed across partners. See Errors.