Developer docs · API v1.0

Build Form I-9 and E-Verify into your own system

One REST API to start I-9s, read their status, and keep every record in sync with your HR system, with our compliance team handling everything behind it.

REST · JSONJWT bearer auth1,000 req/minOpenAPI 3
api.i-9intelligence.com
# Start an I-9 the moment the offer is acceptedPOST /api/NewHire/create{  "firstName": "Jordan",  "lastName": "Lee",  "email": "jordan.lee@example.com",  "dateOfHire": "2026-11-02",  "applicationId": "A-20417",  "unitNumber": "DAL-01"}200 { "status": "Success" } GET /api/NewHire/status?applicationId=A-20417{ "i9Status": 1 }  # Section 1 signed

Overview

Your system tells us about the people it hires, changes, and lets go. We run the Form I-9 and E-Verify work for each of them, and you read the result back whenever you need it.

ItemValue
Base URLhttps://api.i-9intelligence.com
FormatJSON over HTTPS. Send Content-Type: application/json.
AuthJWT bearer token from POST /api/Auth/api-login, valid for 60 minutes
DatesYYYY-MM-DD, for example 2026-11-02
Rate limit1,000 requests a minute, with a 500-request queue processed oldest first. Over the limit returns 429.

Quickstart

You'll authenticate, start an I-9 for a test hire, read its status, and clean up, which takes about 30 minutes.

Every email this API sends goes to a real inbox, because the API runs against production. A test hire with a real person's address sends that person an I-9 invite.

Ask support for a demo location to test in, use your own email address for test hires, and delete each test record when you're done. If you'd rather test away from production, ask us about a separate sandbox.

  1. Get your credentials

    To get started, email support@i-9intelligence.com or open a ticket for a Client ID and Client Secret, and ask for your location codes (unitNumber). Treat the secret like a password and keep it out of your code repository.

  2. Get a token

    Exchange your credentials for a bearer token that lasts 60 minutes.

    curl -X POST https://api.i-9intelligence.com/api/Auth/api-login \
      -H "Content-Type: application/json" \
      -d '{"clientId":"YOUR_CLIENT_ID","clientSecret":"YOUR_CLIENT_SECRET"}'

    Send it on every other call as Authorization: Bearer YOUR_TOKEN, and keep the word Bearer and the space after it, because the API requires both.

  3. Start an I-9 for a test hire

    Use your own email address, and set a hire date of today or later.

    curl -X POST https://api.i-9intelligence.com/api/NewHire/create \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "firstName": "Test",
        "lastName": "Hire",
        "email": "you@yourcompany.com",
        "dateOfHire": "2026-11-02",
        "applicationId": "TEST-0001",
        "unitNumber": "YOUR_UNIT",
        "employeeSSN": "Pending"
      }'
    200 response
    {
      "status": "Success",
      "employeeId": null,
      "applicationId": "TEST-0001",
      "message": "Information has been successfully saved, and an email has been sent to the new hire.",
      "errors": []
    }

    The Section 1 invite should be in your inbox within a minute or two.

  4. Read the status

    bash
    curl "https://api.i-9intelligence.com/api/NewHire/status?applicationId=TEST-0001&unitNumber=YOUR_UNIT" \
      -H "Authorization: Bearer YOUR_TOKEN"

    You'll get "i9Status": 0 (Section 1 initiated) until the test hire signs Section 1. The statuses section covers every value and what to do with it.

  5. Clean up

    bash
    curl -X DELETE "https://api.i-9intelligence.com/api/NewHire/delete?applicationId=TEST-0001&unitNumber=YOUR_UNIT" \
      -H "Authorization: Bearer YOUR_TOKEN"

    Delete is a soft delete. We keep the record for the audit trail, and the API returns 404 for that ID from then on. You're ready to wire up real events, and the best practices are the place to start.

I-9 deadlines to build around

Your integration decides when each I-9 starts, so it decides whether your company makes the three deadlines that come up in every build.

StepDeadlineWhat it means for your build
Section 1By the first day of work for pay. The hire can complete it any time after accepting the offer.Call create when the offer's accepted, not on day one.
Section 2Within 3 business days of the first day of work for pay. It can also be done before day one, once the offer's accepted.Send dateOfHire as the real first day, and update it when the date moves.
E-Verify caseNo later than the third business day after the hire starts work for pay.We create the case. Your job is keeping the start date accurate.

Six steps for any HR system integration

Whether you run Workday, SAP, Oracle, ADP, or something you built, every integration with us comes down to the same six decisions, and they're worth settling before you write code.

Turn off your HR system's own I-9 module

Two I-9 workflows for one hire means two sets of invites and two records that disagree. Turn off reverification too, if your platform keeps it as a separate setting.

Turn off its automatic E-Verify submission

An HR system that creates E-Verify cases on its own gives the same person two cases, so confirm it's off before your first live I-9.

Start the I-9 here on the hire event

Send the hire to NewHire/create with the details that route it correctly, including the location, the hire type, and whether our team does Section 2 by video.

Decide where status shows up

Our dashboard and email alerts always show where every I-9 stands. Writing status back into your HR system is optional and differs by company. It depends on the API or webhook access your HR plan includes, and on whether a status field there could set off that system's own I-9 steps, so scope it on purpose.

Don't put other onboarding steps behind the I-9

Benefits, payroll, and IT accounts should keep moving while the I-9 finishes. Section 2 has three business days after the start date, and an open E-Verify case can't hold up pay or training.

Plan for a failed write-back

Tokens expire, APIs change, and rate limits hit at the worst time. Decide what your integration does when a call fails, how it retries, and who gets alerted, before go-live.

Locations, IDs, and hire types

Locations (unitNumber)

Every I-9 belongs to a location, identified by its unitNumber. The location decides which of your users can see and work that I-9, and its address fills in the employer address in Section 2. Locations roll up into a Level 2, usually a region, business unit, or legal entity, and your API key reaches every location under its Level 2.

Map unitNumber to the worker's physical work site in your HR system (Location in Workday and SAP SuccessFactors, Work Location in ADP), not to a department or cost center. We send the list of codes at setup, and most teams keep a small mapping table from their codes to ours.

Your IDs

Each record needs at least one of your identifiers, and you look it up by the same one later.

FieldUse it when
applicationIdThe person is still an applicant or candidate in your ATS, before an employee ID exists.
employeeIdThe person exists in your HR system as an employee. Add it later with update-employee-id.

Both must be unique within your company among active records. Terminated records don't count, which is how rehires work.

Hire types

SendWorkflowEmail to hire
"newHireTypeId": 2Remote (the default). Section 1 from anywhere, and Section 2 by your rep or by our team over video.Yes
"newHireTypeId": 1
"s1FromHome": true
Section 1 from home, Section 2 on site with your staff.Yes

Fully in-person hires, where both sections happen at the worksite, aren't created through the API. Start those from the location's self-start link or in the i9 Intelligence web app.

Add "forceRemoteVerification": true to send the hire's Section 2 to our US-based compliance team by video.

Duplicate protection

Create fails with 400 if an active record already has the same SSN, employeeId, or applicationId. That stops double I-9s, and it's why a returning employee needs their old record terminated before you create a new one.

Statuses

GET /api/NewHire/status returns the I-9 status as a number in i9Status, a plain-English i9StatusDescription, and the E-Verify and reverification fields. GET /api/NewHire/get returns the form's progress as a code, such as SECTION_2_SIGNED. For separations, read status or terminationDate: a terminated hire's get still shows the last form step.

i9StatusCodeMeaningSuggested action in your system
0SECTION_1_INITIATEDI-9 created, hire invitedRemind the hire if day one is close.
1SECTION_1_SIGNEDHire signed Section 1Section 2 is next. Nothing to do if our team has it.
2SECTION_2_INITIATEDDocument review startedNone.
3SECTION_2_SIGNEDSection 2 signedNone.
4I9_COMPLETEDForm I-9 completeMark the I-9 step done. Keep watching E-Verify fields until the case closes.
5TERMINATEDSeparation recordedStop polling. Retention is tracked on our side.
6DELETEDOnboarding cancelledStop polling. After a delete, lookups by that ID return 404.
7INVALIDRecord in an error stateAlert HR and contact support.

E-Verify and reverification fields

FieldWhat it tells you
eVerifyStatusCode, eVerifyStatusTextWhere the E-Verify case stands
eVerifyActionRequiredtrue when someone needs to act on the case. Route it to HR.
eVerifyCaseClosureReasonSet once the case is closed
hasSupplementB, supplementBSignatureStatus, supplementBSignatureDateWhether a reverification (Supplement B) exists and when it was signed. The last two are null when there isn't one.
terminationDate, deletedDateSet when the record is terminated or deleted
200 response
{
  "i9Status": 3,
  "i9StatusDescription": "The employer has completed and signed Section 2 of the I-9 form, verifying the employee's documentation and confirming eligibility.",
  "terminationDate": null,
  "deletedDate": null,
  "eVerifyStatusCode": "…",
  "eVerifyStatusText": "…",
  "eVerifyActionRequired": false,
  "eVerifyCaseClosureReason": null,
  "hasSupplementB": false,
  "supplementBSignatureStatus": null,
  "supplementBSignatureDate": null,
  "errors": []
}

Errors

Errors come back in one of two shapes. Field validation errors, like a missing email or a past hire date, list each failing field under errors. Business rule errors, like a duplicate ID or a record that isn't found, return one errorMessage with an errorMessageKey and the field in additionalInfo. A missing or expired token returns 401 with an empty body.

400, field validation
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "DateOfHire": ["The date must be today or a future date."]
  },
  "traceId": "00-a165ec45e310d5776e06821cb4d2f2a4-80a9f1698161213b-01"
}
400, business rule
{
  "statusCode": 400,
  "errorMessage": "The Employee ID must be unique. An I9 record with this Employee Identifier already exists for this company.",
  "errorMessageKey": "VALIDATION_ERROR",
  "additionalInfo": { "AttributeName": "EmployeeId" }
}
HTTPMeaningWhat to do
200SuccessNothing to do. Bulk calls only return 200 when every row succeeded.
400Validation failedFix the payload using the error. Don't retry unchanged.
401Missing or expired tokenGet a new token and retry once.
404Record not foundCheck the ID and unitNumber.
429Rate limitBack off and retry with increasing waits.
500Server errorRetry once, then contact support with the request time.

Common validation errors

ErrorUsual causeFix
Duplicate SSN or IDAn active record already existsRead its status first. For a returning employee, terminate the old record, then create.
Invalid unitunitNumber isn't one your key can reachCheck your location mapping.
“The date must be today or a future date.”dateOfHire before today on createSend the real start date, today or later.
Missing identifierNo employeeId or applicationIdSend at least one.
Can't update or deleteThe record is terminated or deletedTerminated and deleted records are read-only.

Endpoints

Every endpoint below except login needs a bearer token, and you can click any of them for its fields and an example.

Authentication
POST/api/Auth/api-loginGet a 60-minute bearer token

Send your Client ID and Client Secret, which only work in the environment they were issued for.

FieldTypeNotes
clientId requiredstringFrom i9 Intelligence
clientSecret requiredstringTreat like a password
New hires
POST/api/NewHire/createStart an I-9 for one hire

Creates the I-9 record, runs the duplicate check, and starts the workflow for the hire type. Remote and Section-1-from-home hires get an email invite.

FieldTypeNotes
firstName requiredstringLegal first name
lastName requiredstringLegal last name
email requiredstringWhere the hire gets their invite
dateOfHire requireddateFirst day of work for pay. Today or later.
unitNumber requiredstringLocation code your key can reach
employeeIdstringOne of employeeId or applicationId is required
applicationIdstringYour applicant or candidate ID
middleInitialstringOne character
employeeSSNstringXXX-XX-XXXX, or "Pending" if you don't have it yet
newHireTypeIdinteger2 remote (default), or 1 with s1FromHome: true. See hire types.
s1FromHomebooleanSend true with newHireTypeId: 1. Leave it out for remote hires.
forceRemoteVerificationbooleanSend Section 2 to our team by video
POST/api/NewHire/bulk-createStart I-9s for a batch of hires

Send an array of create objects, and know that one row failing validation means nothing is created, so fix that row and resend the whole batch.

200 response
{
  "status": "Success",
  "message": "All 2 new hire records created successfully.",
  "totalRecords": 2,
  "successfulRecords": 2,
  "results": [
    { "index": 0, "employeeId": null, "applicationId": "A-20417", "success": true, "message": "New hire record created successfully and email sent to employee.", "errorCode": null },
    { "index": 1, "employeeId": null, "applicationId": "A-20418", "success": true, "message": "New hire record created successfully and email sent to employee.", "errorCode": null }
  ],
  "errors": []
}
GET/api/NewHire/statusI-9 and E-Verify status

Query by employeeId or applicationId, plus unitNumber when the same ID could exist at more than one location. For what each field in the response means, see Statuses.

GET/api/NewHire/getFull record for one hire

This takes the same query parameters as status and returns name, email, hire date, location, termination date, the I-9 status as a code, and a masked SSN.

PUT/api/NewHire/updateChange hire date, location, or details

Identify the record with employeeId or applicationId, and send only the fields that changed, on any record that isn't terminated or deleted. Updatable fields are firstName, lastName, middleInitial, email, employeeSSN, dateOfHire, and unitNumber (which moves the record to another location).

request
{
  "employeeId": "EMP-10042",
  "dateOfHire": "2026-11-09",
  "unitNumber": "AUS-02"
}
PUT/api/NewHire/bulk-updateUpdate a batch, all or nothing

Send an array of update objects, but know that one row failing validation means nothing changes, so fix the bad rows and resend the whole batch.

PUT/api/NewHire/update-employee-idAdd or change the employee ID

Use this when an applicant becomes an employee in your HR system, and send unitNumber and newEmployeeId, plus applicationId or oldEmployeeId to find the record.

request
{
  "unitNumber": "DAL-01",
  "applicationId": "A-20417",
  "newEmployeeId": "EMP-10042"
}
PUT/api/NewHire/update-application-idAdd or change the applicant ID

Send unitNumber and newApplicationId, plus employeeId or oldApplicationId to find the record.

POST/api/NewHire/terminateRecord a separation

Sets the status to TERMINATED, records the date for retention, and closes any open E-Verify case, while the record stays available for audits.

request
{
  "employeeId": "EMP-10042",
  "unitNumber": "DAL-01",
  "terminationDate": "2027-06-30"
}
POST/api/NewHire/bulk-terminateRecord separations in a batch

Send an array of terminate objects, and like the other bulk calls it's all or nothing, so one row that can't be found or is already terminated fails the batch.

DELETE/api/NewHire/deleteCancel a hire who never started

For rescinded offers, no-shows, and failed background checks before day one. Query by ID and unitNumber. It's a soft delete: we keep the record for audit purposes, and the API returns 404 for that ID afterward. Once someone has started, use terminate instead.

DELETE/api/NewHire/bulk-deleteCancel a batch, all or nothing

Body is an array of { "unitNumber", "employeeId" or "applicationId" }, and if any row fails, none of them are deleted.

Links and embedding Enterprise Package

Embedding the I-9 Enterprise Package

Your portal asks for a link, opens it in a frame, and reads the status when the hire's done, so new hires never leave your site.

1 · AskYour server calls UnitLink/get with the hire's ID and usesIFRAME=true.
2 · FrameYour page opens linkUrl in an iframe next to the rest of onboarding.
3 · ConfirmYour server reads NewHire/status and moves the hire to the next step.
server
curl "https://api.i-9intelligence.com\
/api/UnitLink/get\
?unitNumber=DAL-01\
&applicationId=A-20417\
&usesIFRAME=true" \
  -H "Authorization: Bearer YOUR_TOKEN"
200 response
{
  "status": "Success",
  "linkUrl": "https://app.i-9intelligence.com/…",
  "unitNumber": "DAL-01",
  "locationName": "Dallas Plant",
  "errors": []
}
html
<iframe
  src="LINK_URL_FROM_YOUR_SERVER"
  title="Form I-9, Section 1"
  style="width:100%;min-height:900px;border:0">
</iframe>

Section 2 for your own reviewers

Managers and HR reps who complete Section 2 get their own link from UnitLink/s2. Request it with their i9 Intelligence userName and the hire's ID, then add redirectURL if you'd like us to send them back to your tool after they sign.

Ask for a fresh link each time you render the page. Requesting a new link can turn off the previous one, so a cached URL can stop working. Keep the API call on your server, where your token lives.

If you're on Workday, note that its onboarding screens don't support embedded forms from outside vendors. Workday customers use our pre-built connector, and the hire completes Section 1 from an email link instead.

Best practices

What we recommend after building and supporting integrations with Workday, SAP, and homegrown systems.

Start the I-9 when the offer's accepted

Create with applicationId as soon as the candidate accepts. The hire can finish Section 1, and Section 2 can be done before day one. Swap in the employee ID later with update-employee-id.

Map each HR event to one call

Keep the integration boring and predictable.

Event in your systemCall
Offer acceptedPOST /NewHire/create
Employee ID assignedPUT /NewHire/update-employee-id
Start date, location, name, or email changesPUT /NewHire/update
Offer rescinded or no-showDELETE /NewHire/delete
SeparationPOST /NewHire/terminate
RehireTerminate the old record if it's still active, then POST /NewHire/create

Make create safe to retry

Sometimes a create succeeds but the response never reaches you, so your system retries. Before creating, check status by your employee ID. If you get the duplicate-ID 400, look the record up and carry on instead of failing the job.

Cache your token

Get one token per worker and reuse it, refreshing at about 55 minutes or whenever you get a 401. Logging in on every request wastes your rate limit.

Poll only open records

Check open I-9s every 15 to 60 minutes. Once i9Status reaches 4 (I-9 completed), check it daily until eVerifyCaseClosureReason is set, then stop. Stop right away at 5 (terminated) or when a lookup returns 404 after a delete.

Delete before day one, terminate after

Delete is for people who never started, and anyone who worked even one day needs a terminate call so their I-9 is kept for the retention period.

Keep start dates accurate

Section 2 and E-Verify deadlines count from dateOfHire, so send the update the same day the start date moves in your system.

Validate a batch before you send it

Every bulk endpoint is all or nothing, so one bad row fails the whole batch and nothing changes. Check rows on your side first, and when a batch fails, fix the row the error points to and resend.

Back off on 429

Wait and retry with increasing delays, such as 1, 2, then 4 seconds. Use bulk endpoints for hiring events instead of hundreds of single calls.

Handle SSNs carefully

Send "Pending" if you don't have the SSN yet, and never log request bodies that contain one, since our API only ever returns it masked.

Log what support will ask for

Keep the request time, unitNumber, your ID, the HTTP code, and the errors array. With those, we can trace any call.

Go-live checklist

  • Your HR system's automatic E-Verify submission is off
  • Your HR system's own I-9 and reverification steps are off
  • Credentials stored as secrets, not in code
  • Your location codes mapped to our unitNumber values
  • Create fires on offer accepted, with the right hire type
  • Test hire created with your own email, and the invite arrived
  • Status written to the worker's profile in your system
  • Start date and location changes send an update
  • Employee ID swapped in once your HR system assigns it
  • Rescinded offers call delete, and separations call terminate
  • Duplicate 400 handled without failing the job
  • 401 and 429 retried correctly
  • eVerifyActionRequired routed to HR
  • Payroll, benefits, and IT steps don't wait on the I-9
  • Failed calls retry, and someone gets alerted
  • All test records deleted

Get help

Our support team answers API questions by email and phone, and our team can also work hands-on with your developers.

Support

support@i-9intelligence.com
(713) 668-6200, Monday to Friday, 8 to 5 Central. We answer tickets within 2 business hours. Include the details from your logs.

Open a ticket →

Help with your build

We can walk your developer through how your fields map to the I-9, help you connect, and check your first live I-9s. We also build custom integrations and embedded I-9s.

Talk to our team →