# Business Made · Government forms

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /business-made/gov-forms/renewal-settings

**Form renewal periods (org setting)**

`operationId: GovFormsController_renewalSettings`

Stored as the `gov_forms_renewal` setting. `renewalMonths` per form: only `w9` and `contractor-agreement` (tax-form expiry is set by law). Absent = never expires.

#### Signature

```http
GET /business-made/gov-forms/renewal-settings () -> { renewalMonths: { w9?: number, "contractor-agreement"?: number } }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { renewalMonths: { w9?: number, "contractor-agreement"?: number } } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /business-made/gov-forms/renewal-settings

**Set form renewal periods**

`operationId: GovFormsController_setRenewalSettings`

Whole months 1–120, or null to remove. Signed forms of that kind are re-dated at once (good through the day before the anniversary of signing), so readiness moves them to due soon / expired on the new schedule.

#### Signature

```http
PUT /business-made/gov-forms/renewal-settings (body) -> { renewalMonths, restamped }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | RENEWAL_SETTINGS_INVALID | The renewal settings have problems | Unknown form or months out of range; `errors` lists each. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "renewalMonths": {
    "w9": 12,
    "contractor-agreement": 24
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { renewalMonths, restamped } |
| `400` | The renewal settings have problems — Unknown form or months out of range; `errors` lists each. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/gov-forms/expire-due

**Expire forms past their date now**

`operationId: GovFormsController_expireDue`

Runs the same pass as the daily readiness run (`GovFormsService.expireDueForms`). Idempotent.

**Expiry.** A W-4 claiming exemption is good through February 15 of the year after it is signed (IRS Pub 15); state certificates whose exempt claim must be renewed each year carry their own date (e.g. MN W-4MN, NE W-4N, MD MW507 line 3: Feb 15). W-9 and the contractor agreement expire only if the org sets a renewal period (`PUT /business-made/gov-forms/renewal-settings`). The date is stamped on the document (`expirationDate`) when the employee signs, so readiness shows the requirement due soon ahead of it. The day after, the form becomes `expired`: readiness re-reads the evidence (the requirement goes due / overdue), `journeys.evidence` is emitted, and payroll readiness exceptions list it. An expired exempt W-4 switches the payroll profile to the IRS default — Single / MFS with no Step 2–4 entries (Pub 15 §9; an older non-exempt W-4 is not revived) — and says so. An expired state exemption turns `stateTax.exempt` off. Expiry runs on read (this endpoint, the exceptions list) and daily via `GovFormsService.expireDueForms(orgId)`.

#### Signature

```http
POST /business-made/gov-forms/expire-due () -> { asOf, checked, expired: [{ employeeId, documentId, formKey, jurisdiction, expiresOn, basis: exempt_claim\|org_renewal, payrollEffect? }] }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `400` | INVALID_AS_OF | asOf must be a date (YYYY-MM-DD) | `asOf` is not YYYY-MM-DD. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `asOf` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { asOf, checked, expired: [{ employeeId, documentId, formKey, jurisdiction, expiresOn, basis: exempt_claim\|org_renewal, payrollEffect? }] } |
| `400` | asOf must be a date (YYYY-MM-DD) — `asOf` is not YYYY-MM-DD. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/gov-forms/{employeeId}/{formKey}

**An employee’s form (HR view)**

`operationId: GovFormsController_getForm`

#### Signature

```http
GET /business-made/gov-forms/{employeeId}/{formKey} (employeeId: string, formKey: string) -> FormView
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `400` | UNKNOWN_FORM | Unknown form "w5" | `formKey` is not a known form; the body lists `allowed`. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `formKey` | path | "w4" \| "state-withholding" \| "i9-section1" \| "w9" \| "contractor-agreement" | yes |  |
| `variant` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | FormView |
| `400` | Unknown form "w5" — `formKey` is not a known form; the body lists `allowed`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/gov-forms/{employeeId}/{formKey}/countersign

**Countersign a contractor agreement**

`operationId: GovFormsController_countersign`

#### Signature

```http
POST /business-made/gov-forms/{employeeId}/{formKey}/countersign (employeeId: string, formKey: string, body) -> FormView
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `400` | SIGNATURE_INVALID | The signature is incomplete | A signature was sent without consent, or with neither a typed name nor an image. | — |
| `409` | NOT_AWAITING_COUNTERSIGN | The contractor has not signed yet, or it is already countersigned | The agreement is not pending a countersignature. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `formKey` | path | "w4" \| "state-withholding" \| "i9-section1" \| "w9" \| "contractor-agreement" | yes |  |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | FormView |
| `400` | The signature is incomplete — A signature was sent without consent, or with neither a typed name nor an image. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `409` | The contractor has not signed yet, or it is already countersigned — The agreement is not pending a countersignature. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/i9/due

**I-9s needing attention**

`operationId: GovFormsController_due`

Section 1 missing on/after day 1, Section 2 due or late (3 business days after the first day, federal holidays excluded), and reverification due within `withinDays` (default 90) or overdue.

#### Signature

```http
GET /business-made/i9/due (withinDays?: integer, asOf?: string, location?: string) -> { asOf, withinDays, data: [{ employeeId, name, startDate, code, section1Status, section2Status, section2DueDate, daysLate, reverifyBy, daysToReverify }], counts, total }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `withinDays` | query | integer | — |  |
| `asOf` | query | string | — | YYYY-MM-DD, default today |
| `location` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { asOf, withinDays, data: [{ employeeId, name, startDate, code, section1Status, section2Status, section2DueDate, daysLate, reverifyBy, daysToReverify }], counts, total } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/i9/{employeeId}/section2

**I-9 Section 2**

`operationId: GovFormsController_getSection2`

#### Signature

```http
GET /business-made/i9/{employeeId}/section2 (employeeId: string) -> { employeeId, name, startDate, section1: { status, signedAt, data }, section2: { status, data, dueDate, late, daysLate, signedAt }, reverifyBy, supplementB, schema, attestation }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { employeeId, name, startDate, section1: { status, signedAt, data }, section2: { status, data, dueDate, late, daysLate, signedAt }, reverifyBy, supplementB, schema, attestation } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /business-made/i9/{employeeId}/section2

**Save or sign I-9 Section 2**

`operationId: GovFormsController_putSection2`

List A, or List B + List C, with issuing authority, number and expiry; first day of employment; employer representative. Signing needs Section 1 signed and sets the reverification date for time-limited work authorisation. The representative cannot complete their own I-9.

#### Signature

```http
PUT /business-made/i9/{employeeId}/section2 (employeeId: string, body) -> As GET
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | I9_SECTION1_UNSIGNED | Section 1 must be signed first | Signing Section 2 before Section 1. | — |
| `403` | I9_SELF_REVIEW | You cannot complete the employer section of your own I-9 | Caller is the employee. | — |
| `400` | FORM_BODY_REQUIRED | Send { data } to save, and { signature: { typedName\|image, consent: true } } to sign | The body has neither `data` nor a signature. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |

### Request body

```json
{
  "data": {
    "filingStatus": "single"
  },
  "signature": {
    "typedName": "Ana Ruiz",
    "consent": true
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | As GET |
| `400` | Send { data } to save, and { signature: { typedName\|image, consent: true } } to sign — The body has neither `data` nor a signature. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You cannot complete the employer section of your own I-9 — Caller is the employee. |
| `409` | Section 1 must be signed first — Signing Section 2 before Section 1. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/i9/{employeeId}/reverification

**Reverify (Supplement B)**

`operationId: GovFormsController_reverify`

#### Signature

```http
POST /business-made/i9/{employeeId}/reverification (employeeId: string, body) -> As GET section2
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | As GET section2 |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/everify/{employeeId}/case

**Create an E-Verify case**

`operationId: GovFormsController_createCase`

Builds the E-Verify create-case payload from the signed I-9. The employer company ID comes from the org's E-Verify connection (body `clientCompanyId` overrides). A complete payload is `queued` and, when the org is connected, created and submitted in E-Verify at once; the response carries the result. Without a connection the case stays `queued` with `transmission.message: "E-Verify not connected"` — not an error — and is sent automatically when the connection is saved. An incomplete payload is `draft` with `missing`.

Signing I-9 Section 2 does this automatically for an org that has an E-Verify connection.

**Status refresh:** after submission a delayed `everify-refresh` job on `schedule-queue` checks the case (2 h while E-Verify is verifying, daily while a TNC, referral, continuance or employer action is open) until it closes, up to 90 checks. A temporary failure (5xx, 429, network) is retried with backoff; a rejected case (4xx) is not.

**Evidence:** `queued` and `employment_authorized` record readiness evidence; every status change emits `journeys.evidence`.

Employment Authorized cases are closed with `EMPLOYMENT_AUTHORIZED` automatically unless the connection sets `autoCloseAuthorized: false`.

#### Signature

```http
POST /business-made/everify/{employeeId}/case (employeeId: string, body) -> The case (payload masked): { caseId, status, caseNumber, eligibility, closureReason, missing, payload (masked), transmission: { connected, message, detail, environment, remote, attempts, lastError, submittedAt, lastCheckedAt, nextCheckAt, polls }, tnc, photo, history: [{ at, status, by, source, note, caseNumber, raw }] }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | I9_INCOMPLETE | Both I-9 sections must be signed | I-9 incomplete. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case (payload masked): { caseId, status, caseNumber, eligibility, closureReason, missing, payload (masked), transmission: { connected, message, detail, environment, remote, attempts, lastError, submittedAt, lastCheckedAt, nextCheckAt, polls }, tnc, photo, history: [{ at, status, by, source, note, caseNumber, raw }] } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `409` | Both I-9 sections must be signed — I-9 incomplete. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/everify/{employeeId}

**E-Verify cases for an employee**

`operationId: GovFormsController_getCases`

#### Signature

```http
GET /business-made/everify/{employeeId} (employeeId: string) -> { employeeId, name, startDate, deadline, late, i9, canCreate, missingFromI9, connection: { connected, message?, detail?, environment?, companyId? }, latest, cases, statuses, closureReasons }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { employeeId, name, startDate, deadline, late, i9, canCreate, missingFromI9, connection: { connected, message?, detail?, environment?, companyId? }, latest, cases, statuses, closureReasons } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/everify/{employeeId}/case/{caseId}/status

**Record an E-Verify result by hand**

`operationId: GovFormsController_updateCase`

For cases worked in the E-Verify web portal: record a status (and case number / closure reason), or `{ rebuild: true }` a draft from the current I-9 (the company ID comes from the connection). A case that becomes `queued` is transmitted when connected.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/status (employeeId: string, caseId: string, body) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |
| `409` | EVERIFY_CASE_CLOSED | Case is closed | The case is in a terminal status. | — |
| `400` | EVERIFY_STATUS_INVALID | status must be one of … | `status` is not an E-Verify case status. | — |
| `422` | EVERIFY_PAYLOAD_INCOMPLETE | The payload is incomplete | Queuing a case whose payload misses fields; the body lists `missing`. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `400` | status must be one of … — `status` is not an E-Verify case status. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `409` | Case is closed — The case is in a terminal status. |
| `422` | The payload is incomplete — Queuing a case whose payload misses fields; the body lists `missing`. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/everify/{employeeId}/case/{caseId}/submit

**Submit a queued case to E-Verify now**

`operationId: GovFormsController_submitCase`

Creates the case in E-Verify (once — the case number is kept, so a retry only re-submits) and submits it. Not connected: returns the case, still `queued`, with `transmission.message: "E-Verify not connected"` and `transmission.detail` naming what is missing.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/submit (employeeId: string, caseId: string) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | EVERIFY_NOT_QUEUED | Only queued cases are transmitted | Case is draft or already sent. | — |
| `502` | EVERIFY_UPSTREAM_ERROR | E-Verify POST /cases failed (422): … | E-Verify rejected or could not take the case; `errors` carries E-Verify's list. Recorded on the case. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `409` | Only queued cases are transmitted — Case is draft or already sent. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |
| `502` | E-Verify POST /cases failed (422): … — E-Verify rejected or could not take the case; `errors` carries E-Verify's list. Recorded on the case. |

## POST /business-made/everify/{employeeId}/case/{caseId}/refresh

**Refresh a case from E-Verify**

`operationId: GovFormsController_refreshCase`

Reads the case from E-Verify and records any change in the history (and auto-closes Employment Authorized). Cases not sent through the connection are returned unchanged.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/refresh (employeeId: string, caseId: string) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `502` | EVERIFY_UPSTREAM_ERROR | E-Verify GET … failed | E-Verify unavailable. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |
| `502` | E-Verify GET … failed — E-Verify unavailable. |

## POST /business-made/everify/{employeeId}/case/{caseId}/tnc

**Tentative Nonconfirmation: acknowledge or refer**

`operationId: GovFormsController_tnc`

`acknowledge` confirms the employee was given the Further Action Notice (`notifiedDate`, optional `language`). `refer` records that the employee contests it; the case moves to `referred` and is checked daily. Acknowledge comes first. Portal-worked cases are recorded here only.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/tnc (employeeId: string, caseId: string, body) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | EVERIFY_NOT_TNC | The case is …, not a Tentative Nonconfirmation | Wrong status. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |
| `400` | EVERIFY_TNC_ACTION_INVALID | action must be acknowledge or refer | `action` is anything else. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Request body

```json
{
  "action": "acknowledge",
  "notifiedDate": "2026-09-26"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `400` | action must be acknowledge or refer — `action` is anything else. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `409` | The case is …, not a Tentative Nonconfirmation — Wrong status. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/everify/{employeeId}/case/{caseId}/photo-match

**Answer photo matching**

`operationId: GovFormsController_photoMatch`

For `photo_matching_required`: does the photo E-Verify shows match the employee's document? `match`, `no_match` or `no_photo`. Transmitted cases only.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/photo-match (employeeId: string, caseId: string, body) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | EVERIFY_NOT_TRANSMITTED | This case was not transmitted through the E-Verify connection | Portal-worked case. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |
| `400` | EVERIFY_PHOTO_MATCH_INVALID | match must be one of match, no_match, no_photo | `match` is anything else. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `400` | match must be one of match, no_match, no_photo — `match` is anything else. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `409` | This case was not transmitted through the E-Verify connection — Portal-worked case. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/everify/{employeeId}/case/{caseId}/close

**Close an E-Verify case**

`operationId: GovFormsController_closeCase`

Closes with an E-Verify closure statement (`closureReason`, one of `closureReasons` from GET; `OTHER` needs `description`). `currentlyEmployed` is sent where E-Verify asks. A case never sent to E-Verify is `cancelled`. Pending status checks are cancelled.

#### Signature

```http
POST /business-made/everify/{employeeId}/case/{caseId}/close (employeeId: string, caseId: string, body) -> The case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | EVERIFY_CLOSURE_REASON_REQUIRED | closureReason must be one of … | Missing or unknown reason. | — |
| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |
| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |
| `409` | EVERIFY_CASE_CLOSED | Case is closed | The case is already in a terminal status. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code (badge number) or bm_employee sk. |
| `caseId` | path | string | yes |  |

### Request body

```json
{
  "closureReason": "EMPLOYEE_QUIT",
  "currentlyEmployed": false
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The case |
| `400` | closureReason must be one of … — Missing or unknown reason. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. |
| `404` | E-Verify case not found — The person has no case with that id. |
| `409` | Case is closed — The case is already in a terminal status. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/payroll/readiness-exceptions

**Pre-payroll exceptions**

`operationId: GovFormsController_readinessExceptions`

Everyone with a problem for this pay date, each with a one-tap fix route: missing_w4 (warn — Single, no adjustments applies), w4_exempt_expired (warn — the IRS default withholding has been applied and the message says so), form_expired (warn — state certificate, W-9 or contractor agreement past its date), form_expiring (warn — good through a date within 30 days), i9_section2_late (warn), reverification_due within 90 days (warn), no_pay_method (block — held with notice), unsigned_policy (per the readiness payroll gate). A payroll-gate rule can raise an effect to block, or override while an override is active.

#### Signature

```http
GET /business-made/payroll/readiness-exceptions (payDate?: string, location?: string, employeeIds?: string) -> { payDate, data: [{ employeeId, name, code, message, fix: { route, label }, effect, requirementId? }], counts }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `payDate` | query | string | — | YYYY-MM-DD, default today |
| `location` | query | string | — |  |
| `employeeIds` | query | string | — | Comma-separated codes |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { payDate, data: [{ employeeId, name, code, message, fix: { route, label }, effect, requirementId? }], counts } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

