Quarterly VAT filing
See how an app can help someone choose a taxpayer, prepare a quarterly VAT filing, check the amount due, and submit it with their permission.
The Quarterly VAT API lets an app help someone prepare and submit their quarterly VAT filing. It supports original filings in US dollars for the person signed in or a business they currently represent and have agreed to share with the app.
What each VAT API does
Each API handles one part of the filing process:
| API | What it does for the user |
|---|---|
| Choose the taxpayer and quarter | Shows the registered person or business name, their RPN, and open or overdue quarters to choose from. It also provides the declaration the person must accept before filing. The system checks that the chosen quarter is eligible when preparing and submitting the filing. |
| Start or resume a draft | Starts a working copy for the chosen taxpayer and quarter, or brings back an existing draft with its saved figures. Starting a draft does not submit a return. |
| Save figures and calculate VAT | Saves the reported income, tax credit, and tax already withheld, then calculates the amount due using the same calculation as the portal. Saving does not submit the filing or take a payment. |
| Check progress and confirmation | Shows the saved figures, amount due, and whether payment or a tax-credit review is needed. After submission, it shows the filing reference and submission date as confirmation. |
| Submit the filing | Files the reviewed figures after the person accepts the declaration and the required checks pass, including payment and any tax-credit approval. It returns confirmation of the submitted filing. |
For example, someone can select their business and quarter, enter their figures, check the calculated VAT, and confirm the declaration in a connected app. If no tax is due and all checks pass, they can submit without making a payment. If tax is due, the app directs them to the portal to pay, then checks for confirmation; completing payment may also submit the filing.
Identification and permission
The app uses the name and 14-digit RPN already registered with e-Próspera to show whose tax return is being prepared. This does not perform a new identity check or require another ID-document upload. Someone without a registered RPN must finish registration in the portal first.
The person must sign in and explicitly allow the app to prepare and submit VAT. For a business, they must also select that business and remain an active representative. Knowing someone's RPN alone does not give access to their taxes.
What still happens in the portal
- Payments: a filing with tax due uses the existing portal payment process. A filing with no tax due needs no invoice or payment.
- Tax-credit approval: a claim for a positive tax credit needs finance review through the portal before submission.
- Other filing types: Bitcoin filings, amendments to an existing return, and annual income-tax submissions remain portal workflows in this release.
Apps can also show outstanding filing periods, past returns, and downloadable tax documents using the tax information APIs. Those information APIs require separate permission to read tax records.
The sections below explain how developers connect these steps to an app.
Before you start
You will need:
- An e-Próspera account with a registered RPN, and an active representative role for any business you want to file for.
- An OAuth client enabled for VAT filing in the environment you are using. Request client setup or additional permissions through the Help Center chat.
- An access token issued after the person signs in and grants the required permissions. Standard Keys, Agent Keys, Partner Keys, and OIDC ID tokens do not authorize these VAT endpoints.
- For the terminal walkthrough,
curlwith--fail-with-bodysupport andjq.
Start in staging with an authorized test account. Staging accounts and credentials are separate from production. Follow that guide for OAuth discovery: the staging issuer may use a different hostname from the API base URL.
The examples below use fictional names, IDs, dates, and figures. Replace them with
values from your own authorized responses. Run one step at a time and stop if a
request fails; --fail-with-body preserves the error response and returns a
nonzero exit code. Creating a draft saves a real record in the selected environment.
export BASE_URL='https://stg.portal.eprospera.com'
export ACCESS_TOKEN='<your-staging-oauth-access-token>'Keep tokens and financial responses out of shared logs. The examples hold JSON
responses in shell variables rather than writing them to public files. Do not use
shell tracing (set -x) while handling credentials.
Authorize the taxpayer
Use the OAuth authorization-code flow with PKCE. Request only the permissions your app needs:
| Taxpayer | Required for all five VAT endpoints | Also needed to download assessments or read filing history |
|---|---|---|
| Signed-in person | eprospera:person.tax.vat.write | eprospera:person.tax.read |
| Selected business | eprospera:entity.tax.vat.write | eprospera:entity.tax.read |
For example, an app that files VAT and downloads assessments for selected businesses would request these scopes alongside its sign-in scopes:
openid profile eprospera:entity.tax.vat.write eprospera:entity.tax.readAn administrator must enable the VAT scopes on the OAuth client's allowlist; they are not default permissions. After enablement, obtain fresh user consent. Existing read-only grants do not gain filing permission. The user selects which businesses to share during consent, and current representation is checked on every request. Revocation, account deactivation, or removal of the client's VAT scope stops subsequent VAT access.
VAT context provides the identity needed to choose a taxpayer. If your integration separately needs personal profile data or verified ID images, request the corresponding identity permissions. An RPN does not indicate that identity-verification images are available. No ID-document upload is required by the VAT workflow.
Endpoint map
All endpoints return JSON wrapped in data and use an OAuth access token in the
Authorization: Bearer header. {filingId} is the ID returned when a draft is
created or resumed.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/me/tax/vat/context | Choose the taxpayer and quarter; get the declaration. |
| POST | /api/v1/me/tax/vat/filings | Create or resume a draft. |
| PUT | /api/v1/me/tax/vat/filings/{filingId} | Save all figures and calculate VAT. |
| GET | /api/v1/me/tax/vat/filings/{filingId} | Read a draft or submission confirmation. |
| POST | /api/v1/me/tax/vat/filings/{filingId}/submit | Submit the reviewed filing. |
Identify the taxpayer and quarter
Step 1: get the taxpayers available to this app and the candidate quarters for each one using VAT context:
VAT_CONTEXT="$(curl --fail-with-body -sS \
"$BASE_URL/api/v1/me/tax/vat/context" \
-H "Authorization: Bearer $ACCESS_TOKEN")"
printf '%s\n' "$VAT_CONTEXT" | jq .Example response for an app authorized for one business:
{
"data": {
"subjects": [
{
"subject": {
"type": "legal_entity",
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example Harbor LLC",
"residentPermitNumber": "80123456789012"
},
"periods": [
{
"type": "vat",
"period": { "year": 2026, "quarter": 3 },
"dueDate": "2026-10-15T23:59:59.999Z",
"status": "open",
"filingId": null
}
]
}
],
"supportedCurrencies": ["usd"],
"declaration": {
"version": "vat-v1",
"text": "I am authorized to file for this taxpayer and confirm that I have reviewed these figures and that they are complete and accurate to the best of my knowledge."
}
}
}Display the name, RPN, and quarter so the person can choose explicitly. Use the
returned due date; it can reflect a reviewed filing requirement. Quarters are
numbered 1–4. filingId: null means no draft ID was returned for that period.
| Value | How to use it |
|---|---|
subject=personal | Select the signed-in person's taxes. Do not send their person UUID instead. |
subject=<entity-uuid> | Select a business using its returned subject.id. |
| RPN | Display the registered taxpayer number. It is not the business UUID or an access credential. |
subjects: [] | No taxpayers are accessible under the current grant. Check the requested permissions and business selection. |
periods: [] | No candidate VAT quarters were returned for that taxpayer. Check the portal if a filing was expected. |
You can narrow the context request with ?subject=personal or
?subject=<entity-uuid>. Choose an actual taxpayer and period from your response:
VAT_SUBJECT='11111111-1111-4111-8111-111111111111'
TAX_YEAR=2026
TAX_QUARTER=3For a personal filing, use VAT_SUBJECT='personal'. These sample values are not a
suggestion to file that quarter for every account. Eligibility is checked again
on create, save, and submission. A personal quarter can still be rejected when
it must be filed through a represented business. Missing RPNs must be resolved
through registration in the portal.
Create or resume a draft
Step 2: create or resume the chosen
quarter. jq builds the JSON body without manually interpolating values:
VAT_DRAFT="$(jq -n \
--arg subject "$VAT_SUBJECT" \
--argjson year "$TAX_YEAR" \
--argjson quarter "$TAX_QUARTER" \
'{subject: $subject, year: $year, quarter: $quarter}' | \
curl --fail-with-body -sS -X POST \
"$BASE_URL/api/v1/me/tax/vat/filings" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @-)"
printf '%s\n' "$VAT_DRAFT" | jq .
FILING_ID="$(printf '%s' "$VAT_DRAFT" | jq -er '.data.id')"
REVISION="$(printf '%s' "$VAT_DRAFT" | jq -er '.data.revision')"A new draft returns 201; resuming an existing draft returns 200. Both
responses have the same filing shape.
New drafts have status: "draft", incomplete figures, taxLiability: null, and
readyToSubmit: false. Existing drafts retain their saved figures.
Keep the returned id and revision. A revision identifies the figures you last
read; send it back when saving or submitting so a stale screen cannot overwrite
someone else's changes. Treat it as an opaque value, not a timestamp or counter.
Repeating create for the same taxpayer, year, and quarter resumes the draft without overwriting it. This combination provides retry protection; no separate idempotency-key header is required. An already submitted quarter, multiple drafts, or an existing BTC/amendment filing produces 409 and requires portal handling.
Save figures and get the calculation
Step 3: save the complete figures. This example uses a zero-income test quarter to demonstrate submission without payment. Use the taxpayer's actual figures when building a real filing.
VAT_SAVED="$(jq -n --arg revision "$REVISION" '{
revision: $revision,
currency: "usd",
accountingMethod: "cash",
grossIncome: "0.00",
taxCredit: "0.00",
taxWithheld: "0.00"
}' | curl --fail-with-body -sS -X PUT \
"$BASE_URL/api/v1/me/tax/vat/filings/$FILING_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @-)"
printf '%s\n' "$VAT_SAVED" | jq .
REVISION="$(printf '%s' "$VAT_SAVED" | jq -er '.data.revision')"| Field | What to send |
|---|---|
revision | The latest revision returned by create, read, or save. |
currency | "usd" in this release. |
accountingMethod | "cash" or "accrual", matching the taxpayer's reporting method. |
grossIncome | Reported gross income for the quarter, as a decimal string. |
taxCredit | The claimed tax-credit amount. A positive claim requires finance review. |
taxWithheld | Tax already withheld, as a decimal string. |
Send all three monetary fields, including explicit zeros. Valid amounts
include "0", "1000.00", and "12.5". JSON numbers (1000), negative amounts,
currency symbols, commas, and more than two decimal places are rejected. The
maximum is 12 integer digits and 2 decimal places. Unknown body fields are
rejected; do not supply taxpayer names, calculated liability, or payment status.
Example 200 response after saving the zero-income figures:
{
"data": {
"id": "22222222-2222-4222-8222-222222222222",
"subject": {
"type": "legal_entity",
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example Harbor LLC",
"residentPermitNumber": "80123456789012"
},
"period": { "year": 2026, "quarter": 3 },
"status": "draft",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"currency": "usd",
"accountingMethod": "cash",
"reported": { "grossIncome": "0", "taxCredit": "0", "taxWithheld": "0" },
"taxLiability": "0.00",
"readyToSubmit": true,
"creditReviewRequired": false,
"creditReviewStatus": null,
"paymentRequired": false,
"invoice": null,
"submittedAt": null,
"links": {
"portal": "/file/tax/22222222-2222-4222-8222-222222222222/confirm",
"assessment": null
}
}
}The sample revision is illustrative; always reuse your returned value. Monetary
responses are decimal strings and can omit trailing zeros. A null figure means
missing data, not zero. Display the server's taxLiability instead of calculating
it independently. Saving figures neither submits the filing nor takes payment.
Submitted filings and filings with processed invoices cannot be edited. Changing figures voids an unprocessed invoice and invalidates any earlier tax-credit approval. Read the returned state to determine the next action.
Check progress and choose the next action
Step 4: read the filing after resuming work, returning from the portal, or recovering from an uncertain response:
VAT_CURRENT="$(curl --fail-with-body -sS \
"$BASE_URL/api/v1/me/tax/vat/filings/$FILING_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN")"
printf '%s\n' "$VAT_CURRENT" | jq .
REVISION="$(printf '%s' "$VAT_CURRENT" | jq -er '.data.revision')"Read the flags together, starting with status. They are not separate filing
statuses: a draft can require both a credit review and payment.
| Returned state | What your app should show or do |
|---|---|
status: "submitted" | Show confirmation. Stop saving or submitting; readyToSubmit is false after submission. |
taxLiability: null | Ask for the missing figures or accounting method, then save. |
creditReviewRequired: true | Open the portal so the user can request finance review, then refresh after the decision. |
paymentRequired: true | Open the portal to prepare and complete payment, then read the filing again. |
readyToSubmit: true | Show the taxpayer, quarter, current figures, calculated tax, and declaration for explicit confirmation. |
Draft with complete figures but readyToSubmit: false | Inspect the review/payment flags and portal state. Do not force submission. |
readyToSubmit covers figures, credit review, and payment readiness. Permissions,
period eligibility, and the declaration are still checked during submission.
For portal handoff, resolve data.links.portal against the same BASE_URL used
for the API, for example:
https://stg.portal.eprospera.com/file/tax/22222222-2222-4222-8222-222222222222/confirmOpen the link in the user's browser; they may need to sign in. Do not append the
access token to it. creditReviewStatus reports the current finance review state;
use creditReviewRequired to determine whether approval still blocks submission.
Common scenarios
| Scenario | Expected workflow |
|---|---|
| Zero income, zero credit, zero withheld tax | Save figures, review the declaration, and submit when ready. No invoice or payment is needed. |
| Tax is due | Save, open the portal to pay, and refresh. Payment completion may already have submitted the filing. |
| A positive credit is claimed | Request finance review through the portal even if the calculated amount due is zero. Refresh after approval and complete any remaining payment. |
| Figures changed in another session | Read the latest filing and have the user review it again before saving or submitting. |
| The quarter was already filed | Find it in filing history. Use the portal if an amendment is needed. |
Review, pay when needed, and submit
Step 5: after any portal actions, read the latest filing as above. If it is
already submitted, use that confirmation. Otherwise, display its name/RPN,
quarter, accounting method, figures, and calculated liability alongside
VAT_CONTEXT.data.declaration.text.
Ask the person to confirm those exact figures and accept the declaration. Do not pre-accept it during onboarding or reuse confirmation after figures change. Use the revision they reviewed and the declaration version returned by context.
This next request submits the return
Run it only after the person has reviewed the current filing and explicitly accepted the declaration. A successful save or a zero amount due is not consent to submit.
DECLARATION_VERSION="$(printf '%s' "$VAT_CONTEXT" | jq -er '.data.declaration.version')"
VAT_SUBMITTED="$(jq -n \
--arg revision "$REVISION" \
--arg version "$DECLARATION_VERSION" \
'{revision: $revision, declaration: {version: $version, accepted: true}}' | \
curl --fail-with-body -sS -X POST \
"$BASE_URL/api/v1/me/tax/vat/filings/$FILING_ID/submit" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @-)"
printf '%s\n' "$VAT_SUBMITTED" | jq '.data | {id, status, subject, period, taxLiability, submittedAt}'Example output from that jq selection (the full response uses the same filing
shape as save/read):
{
"id": "22222222-2222-4222-8222-222222222222",
"status": "submitted",
"subject": {
"type": "legal_entity",
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example Harbor LLC",
"residentPermitNumber": "80123456789012"
},
"period": { "year": 2026, "quarter": 3 },
"taxLiability": "0",
"submittedAt": "2026-10-04T18:00:00.000Z"
}status: "submitted" plus the filing ID and submittedAt is the completion
signal. Keep the taxpayer, quarter, and figures with the confirmation so the user
can identify what was filed. The person, connected app, reviewed revision, and
accepted declaration version are also recorded in the tax audit trail.
Repeating a successful submission with the same revision returns the existing confirmation. Do not create a new draft to recover from a lost response.
Download the assessment
Step 6: a submitted filing includes data.links.assessment. Fetch that path
with the matching personal or business tax read permission; VAT write
permission alone does not authorize PDF downloads.
The following example uses the submitted response from step 5. If payment submitted the filing in the portal, read it again and use that response's assessment link instead.
ASSESSMENT_PATH="$(printf '%s' "$VAT_SUBMITTED" | jq -er '.data.links.assessment')"
umask 077
DOWNLOAD_DIR="$(mktemp -d)"
curl --fail -sS "$BASE_URL$ASSESSMENT_PATH" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-o "$DOWNLOAD_DIR/vat-assessment.pdf"On success, the response is a PDF (Content-Type: application/pdf), not JSON.
The example saves it in a new private directory to avoid overwriting another
file. Check the command's exit status before opening or sharing it. VAT offers an
assessment PDF; the separate filed-return PDF is currently for annual income tax.
See tax document downloads for details.
Recover from errors and interrupted requests
Check the HTTP status before reading data. VAT errors contain error and may
also contain message or validation details. For example, a stale revision
returns 412:
{
"error": "PRECONDITION_FAILED",
"message": "Filing changed. Read it again and review the current figures."
}| Status or situation | How to recover |
|---|---|
400 invalid request | Check JSON, required fields, decimal strings, and the declaration version. Read details when present. Resolve missing figures or unpaid tax before submission. |
401 unauthorized | Refresh an expired access token if your grant supports it. If access was revoked or refresh fails, reconnect through OAuth. |
403 insufficient permission | Check the requested personal/business scope, the client's enabled scopes, and fresh user consent. Repeating the same request cannot add permission. |
404 taxpayer or filing unavailable | Verify the returned ID, selected business, current representation, and environment. It may be missing or outside the user's authorization. |
409 conflict | Read the message and refresh context/filing. Missing RPNs, duplicate drafts, completed quarters, BTC filings, and amendments need portal handling; a concurrent change needs a fresh read and review. |
412 changed figures or approval | Read the current filing, show changed figures, and obtain fresh review before retrying with its revision. If credit approval changed, resolve the review through the portal. Do not silently overwrite figures. |
429 rate limit | Wait the number of seconds in Retry-After before retrying. The VAT limit is 60 requests per minute per OAuth client and user. |
Timeout, connection failure, or 500 | The write may have completed. Recover using the operation-specific rules below instead of assuming nothing changed. |
| Interrupted operation | Recovery rule |
|---|---|
| Create | Repeat with the same taxpayer, year, and quarter. This resumes an existing draft without replacing its figures; handle any returned conflict. |
| Save | Read the filing first and compare all returned figures with the intended changes. If they were saved, continue from the returned revision. Otherwise, have the user review the current state before saving again. |
| Submit | Read the same filing ID. If submitted, show its confirmation. If still a draft, recheck requirements and retry only with the current, reviewed figures and declaration. |
| Portal payment | Read the filing before another payment or submission attempt. Portal payment may already have submitted it. |
Responses are private and non-cacheable. Scope any application storage to the connected user and business, and remove access when the connection is revoked.
Check your integration in staging
Use an authorized test account and quarters that its context actually returns:
- Confirm the taxpayer name/RPN and selected quarter before every submission.
- Create twice for the same taxpayer/quarter and confirm the same draft is resumed.
- Save zero-income figures and verify submission works without payment.
- Save a filing with tax due, complete portal payment, and read its final status.
- Test a positive credit claim through finance review; changing the figures must require review again.
- Save once, then attempt another save with an older revision and handle
412. - Recover a lost submission response by reading the same filing ID.
- Confirm another user's filing, an unselected business, and a removed representative cannot access or modify the filing.
- Revoke the app and verify it requires reconnection.
- Download an assessment with tax read permission and handle its absence gracefully when only VAT filing permission was granted.
Use the staging guide for account setup and portal payment testing. BTC filings, amendments, annual income-tax writes, and direct API payment initiation remain portal workflows in this release.