Migrate from Finch to Unified.to

Finch focuses on HRIS, payroll and benefits. Unified.to covers the same employee, payroll and benefit data in its HRIS category, and uses the same connection and API model for more than 25 other categories, such as ATS, CRM and accounting.

Two things change the most. First, Finch issues an access token per connection, while Unified.to uses one workspace API key and puts the connection ID in the URL. Second, Finch serves reads from its most recent sync of each provider (every 24 hours for automated integrations), while Unified.to is real-time pass-through: each request goes to the provider and returns current data, and no employee data is stored on our side.

This guide maps each Finch concept to its Unified.to equivalent. For the overall plan and checklist, see Migrate to Unified.to.

Concepts

FinchUnified.to
Connection, with a per-connection access tokenConnection, with a connection ID; one workspace API key for all connections
customer_idexternal_xref
Provider (e.g. gusto, bamboohr)Integration type (e.g. gusto, bamboohr)
Products (directory, individual, …)Scopes (hris_employee_read, hris_payslip_read, …)
Finch ConnectAuthorization component or auth URL
Data syncs and POST /jobs/automatedNot needed: reads are live
WebhooksNative and virtual webhooks
Request forwarding (POST /forward)Passthrough API

What changes without a sync

  • No initial sync. Finch pulls historical data before a new connection is fully usable. A Unified.to connection can be read as soon as the user authorizes it.
  • Current data on every read. You no longer need Finch-Data-Retrieved to judge freshness, or refresh jobs to get newer data.
  • Reads call the provider. Payroll providers often have tight rate limits. If you read the same data repeatedly, keep a copy in your own database and update it with updated_gte or webhooks.
  • Assisted integrations. Finch's assisted integrations are run manually by Finch's team. Unified.to integrations are all API-based. Check Supported integrations for each provider you rely on, and contact us about any gaps.

Authentication

Finch requires a per-connection access token and an API version header. Unified.to needs your workspace API key; the connection is part of the URL.

# Finch
GET https://api.tryfinch.com/employer/directory
Authorization: Bearer {CONNECTION_ACCESS_TOKEN}
Finch-API-Version: 2020-09-17

# Unified.to
GET https://api.unified.to/hris/{connection_id}/employee
Authorization: Bearer {UNIFIED_API_KEY}

You'll no longer store a secret per customer, only a connection ID, which is useless without your workspace API key.

Authorizing end users

Finch Connect takes three steps: your backend creates a session (POST /connect/sessions), your frontend opens it with useFinchConnect, and your backend exchanges the returned code for an access token (POST /auth/token).

Unified.to takes one: embed the Authorization component, or redirect the user to the auth URL. The user comes back to your success_redirect with the connection ID in the URL, and there's no code to exchange.

// Before: Finch Connect
import { useFinchConnect } from '@tryfinch/react-connect';

const { open } = useFinchConnect({
    onSuccess: ({ code }) => exchangeCodeOnYourServer(code), // POST /auth/token
});
open({ sessionId }); // sessionId from POST /connect/sessions

// After: Unified.to Authorization component
import UnifiedDirectory from '@unified-api/react-directory';

<UnifiedDirectory
    workspaceId={WORKSPACE_ID}
    categories={['hris']}
    external_xref={customerId}
    success_redirect="https://yourapp.com/integrations/callback"
/>;

Or, without a framework:

<script src="https://api.unified.to/docs/unified.js?wid=YOUR_WORKSPACE_ID&did=unified_widget&env=Production&cat=hris&uid=customer_123&surl=https%3A%2F%2Fyourapp.com%2Fintegrations%2Fcallback"></script>
<div id="unified_widget"></div>

In the embed script, cat limits the list to one category, uid sets external_xref (the equivalent of Finch's customer_id), and surl / furl set the success and failure redirects. The dashboard's Embedded components page generates the exact code for each framework.

If you pass integration to Finch Connect to send users straight to one provider, use the auth URL for that integration instead:

https://api.unified.to/unified/integration/auth/{WORKSPACE_ID}/gusto
    ?success_redirect=https://yourapp.com/integrations/callback
    &failure_redirect=https://yourapp.com/integrations/error
    &external_xref=customer_123
    &scopes=hris_employee_read,hris_payslip_read
    &redirect=1

On success, the callback URL receives id (the connection ID), state, nonce, type and sig. Verify the signature, then store the connection ID where you store the Finch access token today.

Moving existing connections

Finch access tokens are issued for Finch's API and can't be used with Unified.to, so customers need to re-authorize through the Authorization component. You can do this gradually: send new customers through Unified.to and ask existing ones to reconnect when they next visit your integrations page, then call Finch's POST /disconnect for each migrated customer.

For providers where you hold the credentials yourself, such as API keys your customers gave you, import them with Create connection. See Move your connections.

Making API calls

Finch splits employee data across several endpoints: the directory lists people, and /employer/individual and /employer/employment take batches of IDs in a POST body. Unified.to returns personal and employment details together on one employee object, so a single paginated GET replaces all three.

# Finch: list people, then fetch details in batches
curl "https://api.tryfinch.com/employer/directory?limit=100&offset=0" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Finch-API-Version: 2020-09-17"

curl -X POST https://api.tryfinch.com/employer/employment \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Finch-API-Version: 2020-09-17" \
  -H "Content-Type: application/json" \
  -d '{ "requests": [{ "individual_id": "…" }] }'

# Unified.to: one call
curl "https://api.unified.to/hris/$CONNECTION_ID/employee?limit=100&offset=0" \
  -H "Authorization: Bearer $UNIFIED_API_KEY"
// Finch
import Finch from '@tryfinch/finch-api';

const finch = new Finch({ accessToken });
for await (const person of finch.hris.directory.list()) {
    // …then individuals.retrieveMany / employments.retrieveMany
}

// Unified.to
import { UnifiedTo } from '@unified-api/typescript-sdk';

const unified = new UnifiedTo({ security: { jwt: process.env.UNIFIED_API_KEY } });
const employees = await unified.hris.listHrisEmployees({ connectionId, limit: 100, offset: 0 });

Common objects

FinchUnified.to
GET /employer/companyGET /hris/{connection_id}/company
GET /employer/directoryGET /hris/{connection_id}/employee
POST /employer/individualGET /hris/{connection_id}/employee/{id} (or the list above)
POST /employer/employmentSame employee object: title, employment_status, employment_type, hired_at, terminated_at, compensation, manager_id
GET /employer/payment + POST /employer/pay-statementGET /hris/{connection_id}/payslip
GET /employer/benefitsGET /hris/{connection_id}/benefit
Benefit enrollments and deductions/hris/{connection_id}/deduction
Locations and departments on the employment object/hris/{connection_id}/location, /hris/{connection_id}/group

Finch's payment and pay_statement split a pay run from each employee's statement. Unified.to's payslip is per employee and pay period; filter it by user_id for one employee. Field names differ between the two models, so update your mappers object by object using the API Reference.

Pagination and filtering

FinchUnified.to
limit / offset on /employer/directorylimit / offset on every list endpoint
Batched requests arrays in POST bodiesPaginated GET lists, or get by ID
start_date / end_date on paymentsupdated_gte on payslips (see each object's filters)
entity_idscompany_id filter where the provider has several companies

See Pagination, filtering & sorting.

Custom fields and raw data

Finch returns custom fields through its own model where available. Unified.to returns the provider's original record in raw when you ask for it:

GET /hris/{connection_id}/employee?fields=id,name,compensation,raw

See Custom & original fields.

Request forwarding

Finch's POST /forward takes the method and route in the body. Unified.to's Passthrough API takes the provider path in the URL and uses the HTTP method you call it with:

# Finch
curl -X POST https://api.tryfinch.com/forward \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Finch-API-Version: 2020-09-17" \
  -H "Content-Type: application/json" \
  -d '{ "method": "GET", "route": "/v1/companies" }'

# Unified.to
curl "https://api.unified.to/passthrough/$CONNECTION_ID/v1/companies" \
  -H "Authorization: Bearer $UNIFIED_API_KEY"

Unified.to adds the provider's base URL and authentication, and returns the provider's response unchanged. See Passthrough.

Webhooks

Finch fires webhooks after each sync. Unified.to webhooks are created per connection and object, and each delivery carries the changed records.

Finch eventUnified.to
directory.created, individual.createdobject_type: hris_employee, event: created
employment.updated, individual.updatedobject_type: hris_employee, event: updated
directory.deletedobject_type: hris_employee, event: deleted, where supported
pay_statement.created, payment.createdobject_type: hris_payslip, event: created
company.updatedobject_type: hris_company, event: updated
job.data_sync_all.completed, job.initial_data_sync_*Not needed; the first delivery is marked INITIAL-PARTIAL / INITIAL-COMPLETE
curl -X POST https://api.unified.to/unified/webhook \
  -H "Authorization: Bearer $UNIFIED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "'$CONNECTION_ID'",
    "object_type": "hris_employee",
    "event": "updated",
    "hook_url": "https://yourapp.com/webhooks/unified",
    "interval": 60
  }'

Most HRIS and payroll providers don't offer webhooks, so Unified.to uses virtual webhooks for them and checks for changes every interval minutes, rather than once a day.

Signature verification. Replace your Finch-Signature check with a check of the sig256 field in the payload body: HMAC-SHA256(workspace secret, JSON of data + nonce), base64-encoded. See Webhooks.

Disconnecting

Replace POST /disconnect with DELETE /unified/connection/{connection_id}.

Next steps

Are we missing anything? Let us know
Was this page helpful?