DEV Community

Angelo Revelo (Gelo)
Angelo Revelo (Gelo)

Posted on

Checking a Philippine BIR 2307 payload with an API call before you fill it

Disclosure: I made PhilForms. I drafted this post with an AI assistant (Claude) and ran every request below myself against the live API. This post uses its public API. Not affiliated with BIR, SSS, PhilHealth, Pag-IBIG or any government agency.

PhilForms fills Philippine government forms from data: you send values keyed by parameter name, and they land in the form's printed boxes. The fill call itself needs an API key (the docs are at philforms.com/developers). I am not showing a fill response here, because I did not run one for this post. What needs no key, and what I did run, is the part that saves you the most time: finding the form, reading its contract, and dry-running your payload.

Everything below is a real response from https://philforms.com, run on 2026-11-01 with made-up sample data. Long arrays are trimmed where I say so.

1. Find the form

curl -s "https://philforms.com/api/v1/engine/forms?q=2307"
Enter fullscreen mode Exit fullscreen mode

The form array in the response:

[{"id":"bir-2307","name":"BIR 2307 · Certificate of Creditable Tax Withheld at Source","agency":"bir","parameter_count":31,"page_count":2}]
Enter fullscreen mode Exit fullscreen mode

2. Read the form's contract

curl -s https://philforms.com/api/v1/engine/forms/bir-2307
Enter fullscreen mode Exit fullscreen mode

Each entry in parameter has a name, type, format and an example. This is the one for the payee's TIN:

{"name":"payee_tin","label":"Taxpayer Identification Number (TIN)","type":"tin","id_kind":"tin","expected":"a 9-digit TIN, optionally followed by a 3, 4 or 5-digit branch code (12, 13 or 14 digits in all; NNN-NNN-NNN-NNNNN)","is_core":true,"is_required":true,"field_number":"2","section":"Part I – Payee Information","format":"NNN-NNN-NNN-NNNNN","example":"123-456-789-00000","cell_count":14}
Enter fullscreen mode Exit fullscreen mode

3. Dry-run a payload

POST /api/v1/engine/check/{id} takes the same body a fill would, and returns the issues a fill would raise. No key, no PDF. My sample body (all names, addresses and numbers invented):

{"period_from":"01/01/2026","period_to":"03/31/2026","payee_tin":"123-456-789-00000","payee_name":"DELA CRUZ, JUAN SANTOS","payee_registered_address":"123 Sample St., Brgy. Sample, Pasig City","payee_zip_code":"1605","payor_tin":"987-654-321-00000","payor_name":"SAMPLE TRADING CORP.","payor_registered_address":"456 Example Ave., Makati City","payor_zip_code":"1200"}
Enter fullscreen mode Exit fullscreen mode
curl -s -X POST https://philforms.com/api/v1/engine/check/bir-2307 \
  -H 'content-type: application/json' -d @body.json
Enter fullscreen mode Exit fullscreen mode

Response (the missing list, which names every blank parameter such as the income table and signatures, is trimmed):

{"form_id":"bir-2307","is_valid":true,"issue":[],"matched_count":10,"unknown_key":[],"missing_core":[],"core_field":["payee_tin","payor_tin","payee_name","payee_registered_address"],"warning":[],"copy_count":1,"annex_count":0,"can_fit_row":false}
Enter fullscreen mode Exit fullscreen mode

4. What a bad value looks like

The part I like as a developer: a value the form cannot hold is refused with a reason, not quietly "cleaned". Here is a TIN with a letter in it and a 2-digit ZIP:

curl -s -X POST https://philforms.com/api/v1/engine/check/bir-2307 \
  -H 'content-type: application/json' \
  -d '{"payee_tin":"123a44","payee_zip_code":"16"}'
Enter fullscreen mode Exit fullscreen mode

Trimmed to the useful keys:

{"form_id":"bir-2307","is_valid":false,"error":"A fill would refuse 2 values — see issue","code":"invalid_value","issue":[{"field":"payee_tin","code":"has_letter","message":"\"123a44\" has letters — expected a 9-digit TIN, optionally followed by a 3, 4 or 5-digit branch code (12, 13 or 14 digits in all; NNN-NNN-NNN-NNNNN)"},{"field":"payee_zip_code","code":"wrong_length","message":"\"16\" has 2 digits — expected a 4-digit ZIP code"}],"missing_core":["payee_name","payee_registered_address","payor_tin"]}
Enter fullscreen mode Exit fullscreen mode

Branch on code (invalid_value, has_letter, wrong_length), not on the message sentence.

Where this fits

Put the check in front of your own form or import step, show the user the issue messages, and call fill only when is_valid is true. The result is a PDF that is ready to print, sign and submit yourself. Tax questions go to your accountant or the agency, not to an API.

There is also a hosted MCP server at https://philforms.com/mcp if you would rather let an AI client do these calls, with a client repo at github.com/philforms/philforms-mcp. The OpenAPI spec is at https://philforms.com/api/v1/openapi.json.

Not affiliated with BIR, SSS, PhilHealth, Pag-IBIG or any government agency.

Top comments (0)