> ## Documentation Index
> Fetch the complete documentation index at: https://docs.symmetry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: Complete a form with the SPF API

> Make your first SPF API call end to end. Covers JWT authentication, form determination, `/flowQuestionSet`, `/formQuestionSet`, and `/fillPdf`.

The SPF API lets you build your own employee-facing UI while Symmetry Payroll Forms (SPF) handles form determination, question flows, PDF generation, and tax parameter output. This guide walks a new API client through that flow end to end: authenticate, determine which forms an employee needs, collect their answers, and retrieve the completed form.

For a comparison of API, Hosted, and On-Premise delivery, see [Implementation types](/spf/overview/implementation-types). For full JWT and access token mechanics, see [Authentication in SPF](/spf/getting-started/authentication).

## Before you begin

You'll need an API key to follow this guide. If you don't have one, contact your Symmetry Client Success representative or reach out through the [Client Support Center](https://support.symmetry.com).

You'll also need to decide between **Choose Mode**, where the employee selects forms directly, and **Guided Mode**, where SPF asks targeted questions to determine the employee's required forms. This guide covers both. See [Form Modes](/spf/core-concepts/form-modes) for a full comparison.

<CardGroup cols={2}>
  <Card title="Postman" icon="mailbox" href="/spf/api-reference/postman-collection">
    Download the SPF Postman collection and environment variables to test all endpoints with pre-built sample requests.
  </Card>

  <Card title="Symmetry Payroll Forms API overview" icon="code" href="/spf/api-reference/overview">
    Explore and test every endpoint interactively in the SPF Swagger docs for production and staging.
  </Card>
</CardGroup>

**Environments**

| Environment | SPF API base URL                       |
| ----------- | -------------------------------------- |
| Production  | `https://api.symmetry.com/spf`         |
| Staging     | `https://api-staging.symmetry.com/spf` |

<Info>
  Always develop and test against the **staging** environment before pointing your integration at production.
</Info>

<Warning>
  The authentication endpoint sits outside the `/spf` base path used by every other endpoint below. It's `https://api.symmetry.com/authentication/login` (production) or `https://api-staging.symmetry.com/authentication/login` (staging). Don't prepend `/spf`.
</Warning>

***

## Step 1: Authenticate

Every SPF API endpoint other than `/authentication/login` itself requires a JWT access token. Request one by calling the authentication endpoint with your API key.

```bash theme={null}
GET /authentication/login
```

Pass your API key in the `api-key` header:

```bash theme={null}
curl --request GET \
  --url https://api-staging.symmetry.com/authentication/login \
  --header 'Accept: application/json' \
  --header 'api-key: YOUR_API_KEY'
```

A successful response returns an `accessToken`:

```json theme={null}
{
  "accessToken": "yoUrTokEnISh3re!"
}
```

Send this token as a **Bearer token** in the `Authorization` header on every other request in this guide. See [Authentication in SPF](/spf/getting-started/authentication) for the full header format and token lifetime.

***

## Step 2: Determine applicable forms

With your access token, call `/forms` with the employee's home address and one or more work addresses. SPF uses these locations to determine which withholding forms the employee is recommended to complete.

```bash theme={null}
POST /forms
```

```json theme={null}
{
  "homeAddress": {
    "streetAddress1": "123 Main St",
    "city": "Phoenix",
    "state": "AZ",
    "zipCode": "85001"
  },
  "workAddresses": [
    {
      "streetAddress1": "456 Commerce Blvd",
      "city": "Scottsdale",
      "state": "AZ",
      "zipCode": "85251"
    }
  ]
}
```

The response returns a `forms` array, where each entry includes the form's `id`, `name`, `title`, `formVersion`, `formLocality`, `formType`, whether it's `recommended`, and an `initialQuestionSet`: the question set ID you'll need in Step 4. It also returns a `locationData` array with the normalized address and geocoding result for each address you submitted.

```json theme={null}
{
  "forms": [
    {
      "id": "W4101",
      "name": "W-4",
      "title": "Employee's Withholding Certificate",
      "formVersion": "2025.12.0",
      "formLocality": "FEDERAL",
      "formType": "RESIDENT",
      "recommended": true,
      "initialQuestionSet": "QS1"
    }
  ],
  "locationData": [
    {
      "id": "home",
      "inputAddress": {
        "streetAddress1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zipCode": "85001"
      },
      "normalizedAddress": {
        "streetAddress1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zipCode": "85001-0001"
      },
      "verified": true,
      "geocoded": true
    }
  ]
}
```

<Info>
  Need a full list of supported form IDs? Use the `/getAllFormIds` endpoint, or refer to the [SPF Catalog](/spf/references/tax-parameter-catalog-and-schema). A `/v2/forms` endpoint is also available with additional options such as `includePreviewForms`. See the Swagger docs for details.
</Info>

If you're using **Choose Mode**, present these forms to the employee and let them select which to complete, then skip to Step 4. If you're using **Guided Mode**, continue to Step 3 instead.

***

## Step 3: Get guided flows (guided mode only)

If you're implementing Guided Mode, call `/guided-flows` instead of `/forms`. Guided flows ask the employee targeted questions to determine the precise withholding forms they need, rather than presenting a list to choose from.

```bash theme={null}
POST /guided-flows
```

Use the same request body as `/forms` (`homeAddress` and `workAddresses`). The response returns a `flows` array, where each entry includes a flow `id`, `locality`, `residencyStatus`, and whether it `hasFlowQuestions`. It also returns a `locationData` array with the normalized address and geocoding result for each address you submitted.

```json theme={null}
{
  "flows": [
    {
      "id": "FEDERAL",
      "locality": "FEDERAL",
      "hasFlowQuestions": true,
      "initialQuestionSetId": "QS1",
      "localityFullName": "Federal"
    }
  ],
  "locationData": [
    {
      "id": "home",
      "inputAddress": {
        "streetAddress1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zipCode": "85001"
      },
      "normalizedAddress": {
        "streetAddress1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zipCode": "85001-0001"
      },
      "verified": true,
      "geocoded": true
    }
  ]
}
```

Pass each flow's `id` to `/flowQuestionSet` to retrieve its first question set:

```bash theme={null}
GET /flowQuestionSet/{flowId}/{questionSetId}
```

For example, to retrieve the first question set for the U.S. Federal flow:

```bash theme={null}
GET /flowQuestionSet/FEDERAL/QS1
```

The employee's answers to these questions determine which specific form(s) they're required to complete. Continue calling `/flowQuestionSet` with each subsequent question set ID returned in the response until the flow is complete.

***

## Step 4: Retrieve form questions

Once you have a `formId` (from `/forms`, `/guided-flows`, or the employee's own selection in Choose Mode), call `/formQuestionSet` to retrieve the questions for that form, starting with its `initialQuestionSet` ID.

```bash theme={null}
GET /formQuestionSet/{formId}/{questionSetId}
```

Each question set returns one or more `questions` (with `id`, `questionText`, `htmlType`, `displayType`, and `validationRegex`), along with navigation data telling you the next question set to request. Continue calling `/formQuestionSet` with each subsequent question set ID as the employee progresses through the form.

***

## Step 5: Submit and retrieve the completed form

Once the employee has answered all required questions, call `/fillPdf` to submit their answers and receive the completed form PDF and tax parameters.

```bash theme={null}
POST /fillPdf
```

```json theme={null}
{
  "formId": "W4101",
  "employee": {
    "firstName": "Jane",
    "lastName": "Smith",
    "socialSecurityNumber": "123-45-6789",
    "address": {
      "streetAddress1": "123 Main St",
      "city": "Phoenix",
      "state": "AZ",
      "zipCode": "85001"
    }
  },
  "employer": {
    "name": "Acme Corp",
    "federalEIN": "12-3456789",
    "address": {
      "streetAddress1": "456 Commerce Blvd",
      "city": "Scottsdale",
      "state": "AZ",
      "zipCode": "85251"
    }
  },
  "fields": {
    "question-id-from-formQuestionSet": "employee's answer"
  },
  "signForm": "SIGN"
}
```

`fields` is a flat object: each key is a question `id` returned by `/formQuestionSet` in Step 4, and each value is the employee's answer to that question, as a string.

`signForm` controls how the returned PDF is signed: `PREVIEW` (the default) for an unsigned, masked preview, `SIGN` for a completed and fully executed form, or `SIGN_EXTERNALLY` for a completed form left open for an external signing tool. See [Interpreting SPF API responses](/spf/api/interpreting-responses#form-signing-options-signform) for the full breakdown of each option.

The response includes:

* A completed, signable PDF of the withholding form
* Tax parameters for use in your payroll system
* Form metadata, including `name`, `title`, `formType`, and `locality`

***

## Full endpoint reference

| Endpoint                                    | Method | Description                                                                   |
| ------------------------------------------- | ------ | ----------------------------------------------------------------------------- |
| `/authentication/login`                     | GET    | Authenticate and receive a JWT access token                                   |
| `/forms`                                    | POST   | Return recommended forms for given home and work addresses                    |
| `/v2/forms`                                 | POST   | Return applicable forms (v2)                                                  |
| `/guided-flows`                             | POST   | Return applicable guided flows for given addresses                            |
| `/flowQuestionSet/{flowId}/{questionSetId}` | GET    | Retrieve question set for a flow                                              |
| `/formQuestionSet/{formId}/{questionSetId}` | GET    | Retrieve question set for a form                                              |
| `/fillPdf`                                  | POST   | Submit answers and retrieve completed PDF and tax parameters                  |
| `/fillPdf/{formId}`                         | GET    | Return form data including required fields and tax parameter schema           |
| `/getPdf/{formId}`                          | GET    | Return a blank, unfilled PDF for a given form ID                              |
| `/getAllFormIds`                            | GET    | Return all form IDs currently supported by SPF for your US or CA subscription |
| `/getAllFormsCatalog`                       | GET    | Return all form IDs and their versions                                        |
| `/docs/config`                              | GET    | Return OpenAPI configuration                                                  |
| `/postman/collection`                       | GET    | Return Postman collection                                                     |
| `/postman/environment`                      | GET    | Return Postman environment variables                                          |
| `/steMapping`                               | GET    | Return SPF-to-STE tax parameter mappings                                      |

***

## Next steps

<CardGroup cols={2}>
  <Card title="SPF API overview" icon="code" href="/spf/api-reference/overview">
    Base URLs, authentication, and the full interactive endpoint reference.
  </Card>

  <Card title="Core concepts overview" icon="lightbulb" href="/spf/core-concepts/overview">
    The model behind every SPF integration: forms, determination, and outputs.
  </Card>
</CardGroup>

More references:

* [Authentication in SPF](/spf/getting-started/authentication)
* [Form modes](/spf/core-concepts/form-modes)
* [Question sets and navigation](/spf/core-concepts/question-sets-and-navigation)
* [Postman collection and environment](/spf/api-reference/postman-collection)
