> For the complete documentation index, see [llms.txt](https://docs.joinflo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.joinflo.com/flo-apis/hris-api/overview.md).

# Overview

Welcome to the Flo HRIS API! This guide explains how customers can programmatically sync candidate and job application data from Flo into their HRIS platforms. It outlines authentication, schema, examples, and the proposed solution design. Customers can use this API to retrieve the data that will be directly imported into their HRIS system.

### GET /v1/job-applications

Filtering via Query Parameters

**job\_application\_status** (string, optional) – filters by pipeline stage (Application Extended, Offer Accepted, etc.)

* This field is a set of custom statuses defined by each customer
* The value needs to be a step in an Active Pipeline Template
* The value is case insensitive
* When included, only Statuses of that value are returned.
* When omitted, all Statuses within the timeframe are returned.
* Use Case Examples
  * Viewing data on job applications in the “Hired” Status
  * Viewing only job applications that have “Offer Rejected”

**job\_application\_status\_bucket** (string, optional) – filters by pipeline status bucket (Applied, Offered, Hired etc.)

* This represents the group of statues steps used by your organization
* The value needs to correspond to a Bucket in the pipelines you use for jobs
* The value is case insensitive (i.e. “HIRED” and “hired” are treated the same)
* When included, all of the statuses with the corresponding “statusBucketName” are included in the response.
* Use Case Examples
  * Viewing all job applications that have been in the “Offered” Status bucket

**This Flo article can help with more concepts around Pipeline Building:** [**https://help.florecruit.com/en/articles/6552344-ats-creating-and-editing-pipeline-templates**](https://help.florecruit.com/en/articles/6552344-ats-creating-and-editing-pipeline-templates)

**job\_application\_status\_start** (datetime with timezone, optional) – Returns job applications that had at least one status change within the specified date range. The jobApplicationStatuses array shows only status changes within that range.

Note: jobApplicationLastStatusUpdate always shows the candidate's most recent status timestamp, which may be outside your query range.

* Default value is 30 days in the past
* Not including a time, will default the start time to midnight of the date provided

**job\_application\_status\_end** (datetime with timezone, optional) – filter candidates by last status update.

* Not including a time will set the date to midnight of the date provided

**Note on Timezones:**

* We store DateTime data in the UTC timezone
* For the parameters above, you have the following options:
  * If you include your local timezone, we will parse that and convert to UTC time.
  * If you include a DateTime with no timezone, we will assume it is UTC time.
  * If you include a Date with no time, we will default to midnight UTC of that date.

Using these query parameters if you wanted all job applications that were in a “Hired” status in October of 2025, you would request like this:

```shellscript
GET 
/v1/job-applications?job_application_status_start=2025-10-01&job_application_status_end=2025-11-01&job_application_status=Hired
```

**Response Data Schema**

#### Candidate Fields

<table data-first-column-sticky><thead><tr><th>Field Name</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td>firstName</td><td>string</td><td>Candidate's first name</td></tr><tr><td>lastName</td><td>string</td><td>Candidate’s last name</td></tr><tr><td>email</td><td>string | null</td><td>If the user is not created with an email, value will be null</td></tr><tr><td>phone</td><td>string | null</td><td>Candidate phone number</td></tr><tr><td>candidateId</td><td>UUID string</td><td>Unique ID per candidate</td></tr><tr><td>candidateUrl</td><td>string</td><td>Flo URL that navigates to the Candidate's Profile in your ATS.</td></tr></tbody></table>

#### Job / Job Application Fields

<table data-first-column-sticky><thead><tr><th>Field Name</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td>applicationId</td><td>UUID string</td><td>Unique ID per job application</td></tr><tr><td>jobTitle</td><td>string</td><td>Title of the job</td></tr><tr><td>jobId</td><td>UUID string</td><td>Unique ID per job</td></tr><tr><td>department</td><td>string</td><td>Department from job record</td></tr><tr><td>officeLocation</td><td>string</td><td>Office location from job record</td></tr><tr><td>employmentType</td><td>string</td><td>Employment type (Full-time, Intern, etc.)</td></tr><tr><td>hiringType</td><td>string</td><td>Hiring type (Lateral, Student Hiring, etc.)</td></tr><tr><td>jobApplicationLastStatusUpdate</td><td>datetime</td><td>Timestamp of the latest job application status change – note that this can be later than your filtered end time</td></tr><tr><td>position</td><td>string</td><td>Job position type</td></tr><tr><td>requisitionId</td><td>string | null</td><td>Job requisition id</td></tr><tr><td>startDate</td><td>date | null</td><td>Offer Details Start Date</td></tr><tr><td>salary</td><td>int | null</td><td>Offer Details Salary</td></tr><tr><td>addressLine1</td><td>string</td><td>Candidate address per job app</td></tr><tr><td>addressLine2</td><td>string</td><td>Candidate address per job app</td></tr><tr><td>city</td><td>string</td><td>Candidate address per job app</td></tr><tr><td>state</td><td>string</td><td>Candidate address per job app</td></tr><tr><td>zipCode</td><td>string</td><td>Candidate address per job app</td></tr><tr><td><a href="#job-application-status-fields">jobApplicationStatuses</a></td><td>See Below (List of JSON)</td><td>List of job application status changes</td></tr><tr><td><a href="#job-application-eduction-history-fields">educationHistory</a></td><td>See Below (List of JSON) | null</td><td>Education entries provided on the application, most recent first. When no entries exist, the field may be omitted or <code>null</code>; it is never an empty array.</td></tr></tbody></table>

#### Job Application Status Fields

<table data-first-column-sticky><thead><tr><th>Field Name</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td>statusName</td><td>string</td><td>Name of status</td></tr><tr><td>statusBucketName</td><td>string</td><td>Name of status bucket</td></tr><tr><td>statusUpdatedAt</td><td>datetime</td><td>Time the job application entered the status</td></tr></tbody></table>

#### Job Application Eduction History Fields

<table data-first-column-sticky><thead><tr><th>Field Name</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td>university</td><td>string</td><td>Name of the school as the candidate entered it. Free text, not a fixed list</td></tr><tr><td>degree</td><td>string | null</td><td><p>Degree name in readable form, for example <code>Juris Doctor</code></p><ul><li>One of a fixed set of 27 values — see <a href="#understanding-education-history">Understanding Education History</a> for the full list</li><li><code>null</code> when no degree was recorded</li></ul></td></tr><tr><td>startDate</td><td>date | null</td><td>Start date of study at the university</td></tr><tr><td>endDate</td><td>date | null</td><td><p>Graduation date, or expected graduation date for study in progress.</p><ul><li><code>null</code> if the candidate is still enrolled</li><li><code>null</code> if the job's application form does not collect a graduation date — see <a href="#understanding-education-history">Understanding Education History</a></li></ul></td></tr></tbody></table>

#### Job Application Custom Fields

<table data-first-column-sticky><thead><tr><th>Field Name</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td>customFieldTitle</td><td>string</td><td>The title/question text of the custom field</td></tr><tr><td>customFieldAnswer</td><td>array of strings | null</td><td><p>Array of answer values.</p><ul><li>For single-select, free text, date, numerical, monetary, and boolean questions: contains a single value</li><li>For multi-select questions: contains multiple values</li><li>If no answer exists for the question: <code>null</code> or empty array <code>[]</code></li></ul></td></tr></tbody></table>

#### JSON Format:

<pre class="language-json"><code class="lang-json">[
  {
    "firstName": "Jane",
    "lastName": "Doe",
    "candidateId": "fge25ghn-4mh7-4kli-c9sz-e436ht34jt09",
    "candidateUrl": "https://florecruit.com/app/{org_name}/admin/recruits/125342/",
    "email": "jane.doe@example.com",
    "phone": "+1-555-123-4567",
    "applicationId": "f18fc41a-4357-4520-a9ce-e477ef947b36",
    "jobTitle": "Entry Level Associate",
    "jobId": "d12dvq4p-6234-eo23-a0o3-d41fef91edb12b",
    "department": "Litigation",
    "officeLocation": "New York",
    "employmentType": "Full-time",
    "hiringType": "Student Hiring",
    "position": "Associate",
    "requisitionId": "req-12345",
    "startDate": "2025-10-10",
    "salary": 175000,
    "addressLine1": "1428 Elm Street",
    "addressLine2": "Apartment 101",
    "city": "Springfield",
    "state": "Illinois",
    "zipCode": "62704",
    "jobApplicationStatuses": [
      {
        "statusName": "Application Received",
<strong>        "statusBucketName": "Applied",
</strong>        "statusUpdatedAt": "2025-10-01T22:07:44Z"
      },
      {
        "statusName": "Interviewing",
        "statusBucketName": "Interviewing",
        "statusUpdatedAt": "2025-10-03T12:07:44Z"
      },
      {
        "statusName": "Hired",
        "statusBucketName": "Hired",
        "statusUpdatedAt": "2025-10-06T22:07:44Z"
      }
    ],
    "jobApplicationLastStatusUpdate": "2025-10-06T22:07:44Z",
    "customFields": [
      {
        "customFieldTitle": "Years of Experience",
        "customFieldAnswer": ["5"]
      },
      {
        "customFieldTitle": "Preferred Practice Areas",
        "customFieldAnswer": ["Litigation", "Corporate Law", "Employment Law"]
      },
      {
        "customFieldTitle": "Bar Admission Date",
        "customFieldAnswer": ["2020-06-15"]
      },
      {
        "customFieldTitle": "Additional Notes",
        "customFieldAnswer": null
      }
    ],
    "educationHistory": [
      {
        "university": "Harvard Law School",
        "degree": "Juris Doctor",
        "startDate": null,
        "endDate": "2025-05-20"
      },
      {
        "university": "University of Texas at Austin",
        "degree": "Bachelor of Arts",
        "startDate": null,
        "endDate": "2021-05-20"
      }
    ]
  },
]
</code></pre>

### Filtering Examples with Sample Data

If you would like to get the Job Applications that were moved to the “Hired” Status on October 6th, 2025 – you would structure your request like this:

```shellscript
GET 
/v1/job-applications?job_application_status_start=2025-10-06T00:00:00Z&job_application_status_end=2025-10-07T00:00:00Z&job_application_status=Hired
```

\
The result would be the following. For simplicity, assume there is only one result in the response.

```json
[
  {
    "firstName": "Jane",
    "lastName": "Doe",
    "candidateId": "fge25ghn-4mh7-4kli-c9sz-e436ht34jt09",
    "candidateUrl": "https://florecruit.com/app/{org_name}/admin/recruits/125342/",
    "email": "jane.doe@example.com",
    "phone": "+1-555-123-4567",
    "applicationId": "f18fc41a-4357-4520-a9ce-e477ef947b36",
    "jobTitle": "Entry Level Associate",
    "jobId": "d12dvq4p-6234-eo23-a0o3-d41fef91edb12b",
    "department": "Litigation",
    "officeLocation": "New York",
    "employmentType": "Full-time",
    "hiringType": "Student Hiring",
    "position": "Associate",
    "requisitionId": "req-12345",
    "startDate": "2025-10-10",
    "salary": 175000,
    "addressLine1": "1428 Elm Street",
    "addressLine2": "Apartment 101",
    "city": "Springfield",
    "state": "Illinois",
    "zipCode": "62704",
    "jobApplicationStatuses": [
      {
        "statusName": "Hired",
        "statusBucketName": "Hired",
        "statusUpdatedAt": "2025-10-06T22:07:44Z"
      }
    ],
    "jobApplicationLastStatusUpdate": "2025-10-06T22:07:44Z",
    "customFields": [
      {
        "customFieldTitle": "Years of Experience",
        "customFieldAnswer": ["5"]
      },
      {
        "customFieldTitle": "Preferred Practice Areas",
        "customFieldAnswer": ["Litigation", "Corporate Law", "Employment Law"]
      },
      {
      "customFieldTitle": "Bar Admission Date",
      "customFieldAnswer": ["2020-06-15"]
      },
      {
        "customFieldTitle": "Additional Notes",
        "customFieldAnswer": null
      }
    ],
    "educationHistory": [
      {
        "university": "Harvard Law School",
        "degree": "Juris Doctor",
        "startDate": null,
        "endDate": "2025-05-20"
      },
      {
        "university": "University of Texas at Austin",
        "degree": "Bachelor of Arts",
        "startDate": null,
        "endDate": "2021-05-20"
      }
    ]
  },
]
```

**The same request with a Status of “Interviewing”**

```shellscript
GET 
/v1/job-applications?job_application_status_start=2025-10-06T00:00:00Z&job_application_status_end=2025-10-07T00:00:00Z&job_application_status=Interviewing
```

**Would return**

```json
[]
```

Because there is no Status Update for “Interviewing” within that time frame.

Filtering by the Status Bucket

```shellscript
GET 
/v1/job-applications?job_application_status_start=2025-10-06T00:00:00Z&job_application_status_end=2025-10-07T00:00:00Z&job_application_status_bucket=Applied
```

Would return only the statuses in that bucket

```json
[
  {
    "firstName": "Jane",
    "lastName": "Doe",
    "candidateId": "fge25ghn-4mh7-4kli-c9sz-e436ht34jt09",
    "candidateUrl": "https://florecruit.com/app/{org_name}/admin/recruits/125342/",
    "email": "jane.doe@example.com",
    "phone": "+1-555-123-4567",
    "applicationId": "f18fc41a-4357-4520-a9ce-e477ef947b36",
    "jobTitle": "Entry Level Associate",
    "jobId": "d12dvq4p-6234-eo23-a0o3-d41fef91edb12b",
    "department": "Litigation",
    "officeLocation": "New York",
    "employmentType": "Full-time",
    "hiringType": "Student Hiring",
    "position": "Associate",
    "requisitionId": "req-12345",
    "startDate": "2025-10-10",
    "salary": 175000,
    "addressLine1": "1428 Elm Street",
    "addressLine2": "Apartment 101",
    "city": "Springfield",
    "state": "Illinois",
    "zipCode": "62704",
    "jobApplicationStatuses": [
      {
        "statusName": "Application Received",
        "statusBucketName": "Applied",
        "statusUpdatedAt": "2025-10-01T22:07:44Z"
      },
    ],
    "jobApplicationLastStatusUpdate": "2025-10-06T22:07:44Z",
    "customFields": [
      {
        "customFieldTitle": "Years of Experience",
        "customFieldAnswer": ["5"]
      },
      {
        "customFieldTitle": "Preferred Practice Areas",
        "customFieldAnswer": ["Litigation", "Corporate Law", "Employment Law"]
      },
      {
        "customFieldTitle": "Bar Admission Date",
        "customFieldAnswer": ["2020-06-15"]
      },
      {
        "customFieldTitle": "Additional Notes",
        "customFieldAnswer": null
      }
    ],
    "educationHistory": [
      {
        "university": "Harvard Law School",
        "degree": "Juris Doctor",
        "startDate": null,
        "endDate": "2025-05-20"
      },
      {
        "university": "University of Texas at Austin",
        "degree": "Bachelor of Arts",
        "startDate": null,
        "endDate": "2021-05-20"
      }
    ]
  },
]
```

### Understanding Custom Fields

Custom Fields are custom questions that your organization can configure to collect additional information on job applications. These fields are returned in the `customFields` array for each job application.

**Key Points:**

1. **Visibility:** Only custom fields configured with `SHOW_ON_JOB_APPLICATION = TRUE` are included in the API response
   1. If no Custom Fields are configured the customFields field will be not be present in the response.
2. **Answer Types:** Custom fields support various question types:
   * **Free Text:** Text responses
   * **Date:** Date values (returned as string in ISO format)
   * **Numerical:** Numeric values (returned as string)
   * **Monetary**: Currency values (returned as string)
   * **Single-Select:** One option selected from a list
   * **Multi-Select:** Multiple options selected from a list
3. **Answer Format:** All answers are returned as strings in the `customFieldAnswer` array:
   * Single-value questions (free text, date, numerical, monetary, single-select, boolean): array contains one string
   * Multi-select questions: array contains multiple strings
   * Unanswered questions: `null`
4. **Consistency:** All custom fields configured for job applications are returned, even if they have no answer

**Example Scenarios (Responses have been abbreviated to focus on Custom Fields):**

**Scenario 1:** Job Application with Multiple Custom Fields

```json
{
  "applicationId": "f18fc41a-4357-4520-a9ce-e477ef947b36",
  "firstName": "Jane",
  "lastName": "Doe",
  "customFields": [
    {
      "customFieldTitle": "Years of Experience",
      "customFieldAnswer": ["8"]
    },
    {
      "customFieldTitle": "Practice Areas of Interest",
      "customFieldAnswer": ["Litigation", "Corporate Law", "Employment Law"]
    },
    {
      "customFieldTitle": "Bar Admission Date",
      "customFieldAnswer": ["2020-06-15"]
    },
    {
      "customFieldTitle": "Willing to Relocate",
      "customFieldAnswer": ["true"]
    }
  ]
}
```

**Scenario 2:** Job Application with Unanswered Custom Fields

```json
{
  "applicationId": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
  "firstName": "John",
  "lastName": "Smith",
  "customFields": [
    {
      "customFieldTitle": "Years of Experience",
      "customFieldAnswer": ["3"]
    },
    {
      "customFieldTitle": "Additional Notes",
      "customFieldAnswer": null
    },
    {
      "customFieldTitle": "Preferred Start Date",
      "customFieldAnswer": null
    }
  ]
}
```

**Note:** Custom fields are organization-specific. The fields returned will match the custom field questions configured in your Flo organization settings.

### Understanding Education History

Education History is the list of schools and degrees a candidate entered on their job application. These entries are returned in the `educationHistory` array for each job application.

**Key Points:**

1. **Presence:** `educationHistory` is an array when the application has education entries.
   1. If the application collected none, the field may be absent or `null`. It is never an empty array.
   2. Check that the field is an array before iterating over it.
2. **Order:** Entries are returned most recent first, by `endDate` descending. Entries with no `endDate` are returned first, on the basis that study still in progress is the most recent. The order is stable between requests, so repeated syncs will not show entries reordering.
3. **Per application, not per candidate:** Education is captured on each job application. A candidate who applies to two jobs can have different education on each, because each application records what they entered at the time. Do not assume one set of education per candidate.
4. **Degree is a fixed set of values, safe to map on:** `degree` returns one of the 27 values listed below, spelled exactly as shown. Map them into your HRIS by exact string match.
   1. Existing values are not renamed, so a mapping you build today keeps working.
   2. New degrees are added from time to time. Handle an unrecognised value by routing it to a default or flagging it for review rather than failing the record, so a new value cannot break a sync.
   3. `Other` is itself a selectable value, used when a candidate's degree is not in the list.
5. **A missing graduation date has more than one meaning:** `endDate` is `null` when the candidate is still enrolled, and also when the job's application form does not collect a graduation date. The response does not distinguish between the two.
6. **Jobs that do not collect a graduation date:** if the Expected Graduation Date field is turned off on a job's application form, every education entry on that job's applications is returned with `endDate: null`. The entries themselves are still returned in full — only the dates are withheld, matching what your team sees in Flo.
7. **Dates are entered by candidates:** graduation dates are not validated, so an occasional mistyped year does occur. Because entries are ordered most recent first, a mistyped future year will sort to the top of that application's list.

**Degree values:**

* `Bachelor of Arts`
* `Bachelor of Arts and Bachelors of Laws`
* `Bachelor of Business Administration (Law) and Bachelor of Laws (Double Degree)`
* `Bachelor of Commerce`
* `Bachelor of Commerce and Bachelor of Laws`
* `Bachelor of Economics and Bachelor of Laws`
* `Bachelor of Engineering and Bachelor of Laws`
* `Bachelor of Laws`
* `Bachelor of Science`
* `Bachelor of Sciences and Bachelor of Laws`
* `Bachelor of Social Sciences and Bachelor of Laws`
* `Doctor of Dentistry`
* `Doctor of Medicine`
* `Doctor of Osteopathic Medicine`
* `Doctor of Pharmacy`
* `Doctorate`
* `Juris Doctor`
* `Master of Arts`
* `Master of Business Administration`
* `Master of Education`
* `Master of Finance`
* `Master of Fine Arts`
* `Master of Laws`
* `Master of Public Administration`
* `Master of Science`
* `Master of Social Work`
* `Other`

**Example Scenarios (Responses have been abbreviated to focus on Education History):**

**Scenario 1:** Job Application with a completed and an in-progress degree

```json
{
  "applicationId": "f18fc41a-4357-4520-a9ce-e477ef947b36",
  "firstName": "Jane",
  "lastName": "Doe",
  "educationHistory": [
    {
      "university": "Georgetown University Law Center",
      "degree": "Juris Doctor",
      "startDate": null,
      "endDate": "2027-05-15"
    },
    {
      "university": "University of Michigan",
      "degree": "Bachelor of Arts",
      "startDate": null,
      "endDate": "2022-05-01"
    }
  ]
}
```

The Juris Doctor is listed first because its graduation date is the more recent of the two.

**Scenario 2:** Job Application where the job does not collect a graduation date

```json
{
  "applicationId": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
  "firstName": "John",
  "lastName": "Smith",
  "educationHistory": [
    {
      "university": "Boston College Law School",
      "degree": "Juris Doctor",
      "startDate": null,
      "endDate": null
    },
    {
      "university": "Boston College",
      "degree": "Bachelor of Science",
      "startDate": null,
      "endDate": null
    }
  ]
}
```

All entries are returned, with dates withheld. The order still reflects most recent first.

**Scenario 3:** Job Application that collected no education

```json
{
  "applicationId": "c9d8e7f6-1234-56ab-cdef-7890abcdef12",
  "firstName": "Alex",
  "lastName": "Chen",
  "customFields": [
    {
      "customFieldTitle": "Years of Experience",
      "customFieldAnswer": ["4"]
    }
  ]
}
```

`educationHistory` may be absent or `null` when no education exists. Check that it is an array rather than checking for an empty array.

**Note:** Education History reflects what the candidate entered on the application. It is not verified against the school, and it is separate from any education recorded on a candidate's Flo profile.

<br>
