# Unified.to – Complete Documentation
Unified.to provides a single API to integrate multiple B2B SaaS platforms. This file contains the full documentation for the Unified.to API.
Source: https://docs.unified.to
OpenAPI spec: https://api.unified.to/swagger.json
---
# Getting Started
## Unified.to Documentation
URL: https://docs.unified.to/intro
# Unified.to Documentation
The [Unified.to API](/concepts/what-is-a-unified-api) allows you to add multiple integrations to your application by using a single (i.e. unified) API. Focus on your core product instead of developing and maintaining different integrations.
## Access your customer's data however you want
:::grid
:::gridhightlight
[Unified REST API](/reference/rest)
:::
:::gridhightlight
[Unified MCP Server](/mcp)
:::
:::gridhightlight
[Unified Webhooks](/reference/webhooks)
:::
:::gridhightlight
[Unified SDKs](/reference/sdks)
:::
:::gridhightlight
[Unified CLI](https://github.com/unified-to/unified-cli)
:::
:::
## How to use these docs
Our documentation is organized as follows:
- **How-to guides**: These guides provide the answers to “How do I...?” types of questions. Accomplish common task flows like getting OAuth 2 credentials or testing connections.
- **API Basics**: Get to know the fundamentals for interacting with our API, such as pagination, webhooks, and rate limits.
- **Concepts**: Gain a deeper understanding of topics that are core to the Unified.to platform, such as virtual webhooks, scopes, and unified APIs.
- **Reference**: Look up API specs for each of our unified integration categories as well as examples of API requests.
If you're new, we recommend starting with our tutorial: [Build a simple Javascript app that calls the Unified.to API](/tutorials/build-a-simple-javascript-app).
## Accessing documentation programmatically
The developer documentation at this website is published for human developers. If you need to access the documentation programmatically, via HTTP clients, curl, LLM client, or any non-browser tooling during development, use the following options instead:
- Markdown exports: All pages can be accessed with a .md suffix which would provide a markdown version. (for example, this Introduction page is at https://docs.unified.to/intro.md). A GET request for these URLs returns Markdown suitable for version control, search, and offline use.
- Documentation index: Use the https://docs.unified.to/llms.txt which lists curated links to Markdown versions of guides and reference pages, including webhooks and API definitions. Use it as a stable entry point when you need to discover or bulk-fetch machine-friendly documentation.
- OpenAPI definitions: Endpoint and webhook reference specifications can be found at https://docs.unified.to/reference/sdks.md.
## Additional resources
Curious about Unified.to's security posture? Learn more at [Security](https://unified.to/security){:target="\_blank"}.
Have a question or just want to chat? Join our [Discord](https://discord.gg/85z7HF7JbD){:target="\_blank"} server.
Watch guided video walkthroughs on our [YouTube](https://www.youtube.com/@unified_api){:target="\_blank"} channel, such as [How to get started with Unified.to](https://youtu.be/arTwN_OT8Do) or [How to use Webhooks in Unified.to](https://youtu.be/_zbUplmJd84).
## Get started with Unified.to
URL: https://docs.unified.to/quick-start
# Get started with Unified.to
This quick start guides you through connecting your application to third-party platforms using Unified.to. You'll see how to:
- Set up your Unified.to account and credentials
- Activate integrations in the sandbox environment
- Add the Authorization component to your app
- Make your first API call
::callout
Want to play around with the Unified API right away? Download [Postman](https://www.postman.com/downloads/) and import our [Postman collection](/reference/sdks) to start making API calls. You'll need your workspace ID and API key first.
::
## Set up your workspace
1. Create your Unified.to account
- Sign up at [app.unified.to/login](https://app.unified.to/login)
- Log in to your dashboard
2. Get your API credentials
- Navigate to **Settings > API Keys**
- Copy your **Workspace ID** and **API Key**
- Store these securely where you can reference them in your app, such as in an `.env` file - you'll need them for API calls
3. Activate an integration in the sandbox environment
- Go to **Integrations**
- Select **Sandbox** from the environment dropdown in the top-right corner of the screen
- Search for your desired integration
- Click the integration card and select **Activate** (don't worry about filling in credentials - they're all mocked in the sandbox environment)

## Add authorization to your app
Your users need to authorize your application to access their third-party accounts (like Lever, HubSpot, etc.). Unified.to provides an embedded Authorization component that handles this process securely. When users click on an integration in this component, they'll be redirected to an authorization page, then returned to your app with a unique connection ID that you'll use for making API calls.

Add this to your app:
```html
```
Replace `YOUR_WORKSPACE_ID` with the Workspace ID you copied earlier.
(Want to use a Javascript framework or make the API call from your server? Discover more options at [app.unified.to/embed](https://app.unified.to/embed?tab=Authorization))
## Make your first API call
After successful authorization, you'll receive a connection ID in the callback URL e.g. `localhost:3000?id={connection_id}`. Save this in your database and use it to make API calls.
Our Unified endpoints generally follow the pattern of `{API_CATEGORY}/{CONNECTION_ID}/{DATA_OBJECT}`. For example:
### CRM (HubSpot, Salesforce, etc.)
```javascript
// Fetch contacts
const response = await fetch(`https://api.unified.to/crm/${connectionId}/contact`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
```
### ATS (Lever, Greenhouse, etc.)
```javascript
// Fetch candidates
const response = await fetch(`https://api.unified.to/ats/${connectionId}/candidate`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
```
### HRIS (BambooHR, Workday, etc.)
```javascript
// Fetch employees
const response = await fetch(`https://api.unified.to/hris/${connectionId}/employee`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
```
Refer to our **API Reference** for the category and data object you are interested in.
## Next steps
When you're ready to move on to production environments, review these guides to set yourself up for success:
- [Learn how to work with webhooks](https://docs.unified.to/concepts/webhooks)
- [Configure scopes for your authorization flow](https://docs.unified.to/concepts/scopes)
- [Associate your end-users with connections](https://docs.unified.to/guides/how_to_associate_a_connection_id_with_your_end_user)
- [SDKs for popular languages](https://docs.unified.to/reference/sdks)
Need help?
- Join our [Discord](https://discord.gg/2nsAPmbx)
- Email [hello@unified.to](mailto:hello@unified.to)
---
# Concepts
## Integration Authorization
URL: https://docs.unified.to/concepts/embedded-components
# Authorization
Authorization is the process of asking customers to authorize access to their third-party accounts.
Unified provides several options to accomplish this, from easy 1-line embedded components to a full-featured API, and even CNAMEd domains.
## Embedded Authorization Component
Unified.to provides a suite of pre-built front-end authorization components that support React, Angular, Vue, and Svelte (and plain JavaScript) for you to use in your app.
These authorization components display a list of your activated integrations, returned directly from our API. Once the end-user clicks on an integration, they will be redirected to the authorization page for that integration, regardless if it is OAuth2, API Token and any other type of authorization.
in their File Storage connection.
### Instructions
To use any one of these components in your app:
- Make sure you've activated some integrations first. You can do so at [app.unified.to/integrations](https://app.unified.to/integrations).
- On app.unified.to, click on Embedded components and select which of the widgets you want to use from the tab.
- Configure how you want the component to behave under the Configuration tab.
- View and copy the code to add to your app under the Embedded Code tab.
- Important: Under the Options tab, be sure to select the correct permissions scopes that the integrations you activated are expecting to receive. By leaving these options blank, Unified.to will request access to all possible scopes. To learn more about scopes, see: [Understanding scopes](/concepts/scopes).
### Front-end Frameworks
| | |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **React** | [npmjs.com/package/@unified-api/react-directory](https://www.npmjs.com/package/@unified-api/react-directory){:target="\_blank"} |
| **Angular** | [npmjs.com/package/@unified-api/angular-directory](https://npmjs.com/package/@unified-api/angular-directory){:target="\_blank"} |
| **VueJS** | [npmjs.com/package/@unified-api/vuejs-directory](https://npmjs.com/package/@unified-api/vuejs-directory){:target="\_blank"} |
| **Svelte** | [npmjs.com/package/@unified-api/svelte-directory](https://www.npmjs.com/package/@unified-api/svelte-directory) |
## Manually Authorizing Connections
If you do not want to use our pre-built authorization components, nor want to copy our component source code and make changes, then you can use our [API](https://docs.unified.to/unified/integration/Authorize_new_connection)
This is the flow to follow in your application with your end-user:
1. You redirect your user to our authorization URL. Make sure to include a `success_redirect`, `failure_redirect`, and `permissions`. Also include either `state` or `external_xref`.
2. Your end-user will authorize the connection
3. If they cancel, then we return them to the `failure_redirect`
4. If they authorize the connection, we then test their API credentials to see if it has the valid permissions
5. If that fails, then we redirect them back to the `failure_redirect` and also include an `error` message with a string denoting the error
6. If it succeeds, then we redirect them to the `success_redirect` and add additional parameters to the URL
## Successful authorization
If the authorization is successful (either via our authorization components or via the API), we redirect the user to the specified `success_redirect` URL and add the following URL parameters:
- `id`: the newly created connection ID
- `state`: the state value you provided
- `nonce`: a random nonce value
- `type`: the integration type of connection
- `sig`: a security signature to ensure that this information has not been tampered with;
### Signature calculation
The signature is calculated as follows:
```javascript
sig = HmacSha256(workspace.secret).update(connection.id).update(nonce).update(state).digest('hex');
```
## Other Embedded Components
Unified also has non-authorization components that you can embed into your app. These components are:
- **Sign-in**: Authenticate users so they can sign into your app; [info](https://app.unified.to/embed?tab=Sign-in){:target="\_blank"}
- **Public Directory**: Display a list of your integration so your users can learn about your third-party API offerings; [info](https://app.unified.to/embed?tab=Directory){:target="\_blank"}
- **FileManager**: Display a list of files in a directory so your users can pick a file; [info](https://app.unified.to/embed?tab=FileManager){:target="\_blank"}
- **CalendarScheduler**: Display a calendar so your users can schedule events in their Calendar connection; [info](https://app.unified.to/embed?tab=CalendarScheduler){:target="\_blank"}
- **MessagingManager**: Display a list of channels/folders and messages; [info](https://app.unified.to/embed?tab=Messaging){:target="\_blank"}
## Sandbox / Synthetic Data environment
URL: https://docs.unified.to/concepts/sandbox
# Sandbox / Synthetic Data environment
The synthetic data (ie. sandbox) environment is a controlled, isolated testing space provided by Unified.to. It allows developers to activate integrations, create connections, and make API calls without interacting with real third-party services or affecting production data.
Key points to understand about the synthetic data environment:
- It is specific to Unified.to and separate from any sandbox accounts you might have with third-party service providers.
- All API calls made in the sandbox environment return synthetic, mock data.
- At no point do you communicate with actual third-party providers when using the synthetic data environment.
## Using the synthetic data environment
There are two ways to use the synthetic data environment:
1. **From the web app:**
- On [app.unified.to](http://app.unified.to/), in the top right corner of the page, click on the **Env** dropdown.
- Select **Synthetic Data** from the options.
2. **When calling the Core API:**
- When calling the Unified Core API e.g. to query integrations or generate an authorization URL, include `env=Sandbox` in the query parameters. For example:
```
https://api.unified.to/unified/integration/auth/{workspaceId}/{integration}?env=Sandbox
```
**API reference:** [Core API overview](https://docs.unified.to/unified/overview)
**Note**: If you use the Core API and do not specify `env`, we will default to using the environment that is currently active on app.unified.to. As a best practice, be explicit about the environment you want to use when working with this API.
## Sandbox vs Production environments
| | **Synthetic Data / Sandbox** | **Production** |
| ----------------- | ------------------------------------------------ | --------------------------------------------------- |
| **Purpose** | For testing and development | For live, real-world usage |
| **Data** | Synthetic, mock data generated by Unified.to | Real data from integrated services |
| **Integrations** | Can be activated with mock credentials | Requires real credentials from third-party services |
| **API Calls** | Return synthetic data | Interact with actual third-party services |
| **Risk** | No risk of affecting real data or services | Can affect real data and services |
| **Authorization** | Can use any mock credentials and bypasses scopes | Requires valid credentials and correct scopes |
**Note**: Production environment refers to any non-sandbox environment.
## Key considerations about the sandbox environment
### Synthetic data generation
- Mock data is generated when you activate an integration in the Sandbox environment.
- If you deactivate and reactivate an integration, it will be populated with new synthetic data.
### Isolated testing environment
- Each activated integration and connection in the Sandbox has its own isolated set of synthetic data.
- This isolation allows you to test various scenarios without affecting real data or services.
### Mock credentials
- In the sandbox environment, you can use any mock values for OAuth client IDs, secrets, API tokens, and other credentials.
- When creating a connection for an integration in the sandbox environment, you can also use any mock values when asked for them.
### Integration-specific environments vs. [Unified.to](http://unified.to/) Synthetic Data / Sandbox
An important distinction to be aware of:
- Some integrations allow you to choose an "API region / environment" when activating them. This refers to that specific API provider's environment, not Unified.to's environment.
- Choosing "Sandbox" when activating an integration this way is **not** the same as using the Unified.to sandbox environment.
### Environment-specific integrations and connections
- Activated integrations are tied to the active environment at the time of their activation.
- If you activate an integration (e.g., HubSpot) in the sandbox environment, it won't be available in the production environment, and vice versa.
- Similarly, a connection created for an integration in the sandbox environment will only return synthetic mock data and won't be accessible in the production environment.
## When to use the sandbox environment
The Synthetic Data / Sandbox environment is ideal for:
1. Initial integration testing and exploration
2. Developing and debugging your application without affecting real data
3. Running automated tests that don't require real-world data
4. Demonstrating functionality without needing access to actual third-party services
## Best practices
1. Clearly distinguish between your sandbox and production environments in your code and workflows.
2. Use the sandbox environment for initial testing before moving to production.
3. When seeking support or reporting issues, specify which environment you're working in.
## Additional information
- [Tutorial: Build a simple Javascript app that calls the Unified API](https://docs.unified.to/tutorials/build-a-simple-javascript-app)
- See how to build an app entirely in the synthetic data / sandbox environment
## sandboxes
URL: https://docs.unified.to/concepts/sandboxes
# Sandbox Accounts
This directory provides guidance for obtaining sandbox accounts for integrations supported by Unified.to. These accounts can be used for testing your app with live data.
:::callout
Note: These sandbox accounts are different from Unified.to's [synthetic data sandbox environment](/concepts/sandbox). While our sandbox environment generates synthetic data for trying out he Unified API, these sandbox accounts are for testing with real integration endpoints in production.
:::
:::sandboxes
:::
For integrations requiring partner program enrollment or special access, we recommend:
1. Starting the application process early in your development cycle
2. Having a clear description of your integration use case ready
## Understanding scopes
URL: https://docs.unified.to/concepts/scopes
# Understanding scopes
In the world of API integrations and OAuth, scopes play a crucial role in managing access to resources. This guide explains the concept of scopes, their importance in OAuth, and how they are handled specifically at Unified.to.
## What are scopes?
Scopes are a way to limit an application's access to a user's account. Instead of granting complete access to an account, scopes allow for fine-grained permission control.
Think of scopes as permission slips. When you use an app that integrates with another service (like signing in with Google), scopes define exactly what that app is allowed to do with your account. It's like telling the bouncer at a club, "This person can enter the main area, but not the VIP lounge."
## Why are scopes important?
1. **Security**: Scopes ensure that applications only have access to the specific data and actions they need, reducing the risk of unauthorized access.
2. **User Control**: Users can make informed decisions about what access they're granting to applications.
3. **Compliance**: Scopes help applications adhere to data protection regulations by implementing the principle of least privilege.
## How scopes work in OAuth
In the OAuth flow:
1. An application requests one or more scopes.
2. The user is presented with these scope requests during the authorization process.
3. If the user approves, an access token is issued that's limited to the approved scopes.
For example, an application might request the scope `https://www.googleapis.com/auth/drive.readonly` to read files from Google Drive, but not edit or delete them.
## Scopes at Unified.to
Unified.to acts as a conduit between your application and various API providers. We use a unified set of scopes that map to provider-specific scopes. This abstraction simplifies the integration process across multiple providers.
### Unified scopes
Unified.to uses its own set of scopes, which are then mapped to provider-specific scopes. For example:
- `storage_file_read` maps to `https://www.googleapis.com/auth/drive.readonly` for Google Drive
- `crm_company_read` maps to `oauth, crm.objects.owners.read, crm.objects.companies.read` for HubSpot
You may notice that some Unified scopes map to multiple provider-specific scopes. This is done to ensure that all the necessary permissions are requested for the integration to function correctly.
The mappings for all scopes are found under their respective Integration page at app.unified.to under OAuth 2 Credentials e.g. [here](https://app.unified.to/integrations/googledrive?tab=oauth2) are the mappings for Google Drive.
### Platform scopes
When setting up your developer account and/or developer app, you will need to define your scopes with the API providers themselves - Unified.to is unable to do this for you. How this is done varies on a case-by-case basis, but it is usually configured while generating your OAuth credentials. We have several how-to guides for generating credentials and setting scopes for our most popular integrations. If you think any are missing, please let us know.
## Requesting scopes
When using Unified.to, you can request scopes in one of two ways, depending on how you intend to authorize your users:
1. **(Recommended) Using the Authorization component**: On app.unified.to, you can select the scopes you need under Embedded components > Permission scopes.
Example of the scope interface on app.unified.to
2. **Using the Authorization URL**: When generating an auth URL, you can include the scopes as a query parameter. For example:
```
https://api.unified.to/unified/integration/auth/{WORKSPACE_ID}/{INTEGRATION}?scopes=webhook,crm_deal_read,crm_event_read
```
### Requesting custom or platform-specific scopes
In addition to passing Unified scopes in the query parameter, you can also specify scopes that are specific to the integration only. Behind the scenes, Unified scopes are "unfurled" to map to the integration's scopes, and anything else that is passed in will be used as-is. For example:
```
https://api.unified.to/unified/integration/auth/{WORKSPACE_ID}/{INTEGRATION}?scopes=webhook,crm_deal_read,crm_event_read,hubspot_scope_1,hubspot_scope2s
```
:::callout
**Note**: If you do not specify any scopes at all, then Unified.to will attempt to request _all_ the scopes available for the integration. We strongly advise you to consider which scopes you actually need and then intentionally specify them using one of the above two methods. The next section will go over best practices when requesting scopes.
:::
### Overriding scopes per integration
Each Unified permission (like `ats_job_read`) maps to a default set of the integration's raw scopes. You can replace that default on a per-integration basis using the integration's scope override setting.
If you want to use our API to change these scopes, update the WorkspaceIntegration object by adding the value in a map keyed by the Unified permission, where each value is a comma-separated list of the integration's raw scopes:
```json
{
"overriden_scopes": {
"ats_job": "jobs_r"
}
}
```
In this example, Unified requests the provider's `jobs_r` scope for the `ats_job` permission instead of its default. At authorization time, the override replaces the default scopes for that permission in the OAuth authorize URL. To revert, change the value back to the default scope or remove the override entirely.
You can also change the default scopes for the activation workspace integration in app.unified.to inside the integration's page.
You can also change the default scopes for the activation workspace integration in app.unified.to inside the integration's page.
:::callout
**Note**: Scope overrides apply to the OAuth authorization flow. For API-key based integrations, scopes are determined by the key itself, so the override will not change runtime behavior.
:::
### Best practices for scope usage
1. **Request Only What You Need**: Always request the minimum set of scopes required for your application to function.
2. **Check Provider Requirements**: Some providers may require certain scopes to always be enabled. Check the Unified.to documentation for provider-specific requirements.
3. **Include the Webhook Scope**: If you're setting up webhooks, always include the `webhook` scope in your requests.
4. **Verify Scope Mappings**: You can view the mapping of Unified.to scopes to provider-specific scopes in the Unified.to dashboard for each integration.
## Conclusion
Understanding and correctly implementing scopes is crucial for secure and efficient API integrations. By leveraging Unified.to's scope system, you can simplify the process of working with multiple providers while maintaining granular control over access permissions.
## Virtual Webhooks
URL: https://docs.unified.to/concepts/virtual_webhooks
# Understanding virtual webhooks
This guide explains what virtual webhooks are, how they differ from native webhooks, and when you would want to use a virtual webhook.
## What are virtual webhooks?
Virtual webhooks are [Unified.to](http://unified.to/)'s solution for providing webhook functionality even when an integration doesn't natively support it. They simulate real-time updates by periodically checking for changes in your connected SaaS applications' data e.g. new records, update records, or deleted records. In other words, they use scheduled polling.
While native webhooks are great when they're available, many software vendors either don't support webhooks or have limitations with their webhook implementations. Virtual webhooks bridge this gap by providing a consistent webhook experience across all supported integrations.
## How virtual webhooks work
Virtual webhooks work by:
1. Monitoring your connections for updates to specific objects (e.g., CRM deals, ATS jobs, etc) at regular intervals (polling)
2. Detecting when data has changed or new data has come in since the last check
3. Sending updates to your webhook URL only when changes are found
4. Managing rate limits automatically
The key difference from native webhooks is that virtual webhooks use polling behind the scenes, but this complexity is managed by us so that you, the developer, don’t have to worry about it. You interact with virtual webhooks as you would with native webhooks.
## Virtual vs native webhooks comparison
| | **Virtual Webhooks** | **Native Webhooks** |
| --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Implementation** | Scheduled polling-based virtualization | Immediate push notifications |
| **Setup** | Configured entirely through Unified.to | May require additional configuration on other platforms |
| **Cost Structure** | One API call per interval when changes found - the interval can be set by you | One API call per event whenever they occur |
| **Event Types** | Mainly supports created and updated events | Supports created, updated, and deleted events if the integration supports them |
| **Retry Logic** | Built-in for both reading and dispatching | Handled on a per-integration basis |
| **Health Monitoring** | Built-in connection health tracking with retry mechanism | Subject to your server’s availability and the integration’s retry mechanism |
## When to use virtual webhooks
Virtual webhooks are ideal for:
- Tasks where 1+ minute delays are acceptable and you don’t need the data immediately
- When you want greater control over costs by setting the frequency the integration is polled
- Integrations that don't offer native webhook support
When an integration offers both virtual and native webhooks, consider native webhooks when:
- You need immediate updates as soon as they arrive
## How to identify webhook support
You can check what type of webhook support an integration offers:
1. Navigate to the integration's page in the [Unified.to](http://unified.to/) dashboard
2. Click on **Feature Support**
3. Look under the **Webhooks** section:
- Virtual webhooks are labeled with "virtual"
- Native webhooks are labeled with "native"

_In this example, the integration supports virtual updated and virtual created events._
## Creating and configuring virtual webhooks
You create and configure virtual webhooks the same as you would with any other webhook, with the exception of also being able to set an interval for the polling. To see how to do that, refer to: [How to create and configure webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
## Additional resources
- [How to troubleshoot unhealthy webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks)
- [Introduction to webhooks](https://docs.unified.to/reference/webhooks)
## What is a Unified API?
URL: https://docs.unified.to/concepts/what-is-a-unified-api
# What is a Unified API?
A unified API is a single API interface that aggregates multiple third-party APIs in the same software category — CRM, accounting, HRIS, ATS, payroll, and others — behind one standardized endpoint. With a unified API, applications authenticate once, integrate once, and receive normalized data from any supported integration in that category.
Unified APIs vary in their underlying architecture. Real-time pass-through unified APIs route every request live to the source system without storing customer data. Sync-based unified APIs cache data from source systems on a schedule and serve reads from that cache. The architectural choice has implications for data freshness, compliance scope, latency, and AI agent compatibility — covered in detail below.
## Key takeaways
- A unified API gives applications one endpoint, one auth flow, and one data model across many vendors in the same category — instead of building per-vendor integrations.
- Real-time pass-through unified APIs route requests live to source systems with no customer data stored at rest. Sync-based unified APIs cache data on a schedule and serve reads from that cache.
- The architectural choice affects data freshness, compliance scope, latency profile, and AI agent compatibility. These trade-offs are concrete, not stylistic.
- Most unified APIs handle the lowest-common-denominator critique through a "raw" passthrough mechanism that preserves vendor-specific custom fields and objects outside the normalized schema.
- Pricing models vary substantially: per linked account, per active consumer, per API call, and consumption-based. Each scales differently with customer count and integration breadth.
## What problem does a unified API solve?
Different APIs represent the same concepts in different ways, even within a single category. One CRM API refers to an end-user as a "User"; another calls them a "Contact"; a third uses "Lead." Each representation has different properties, field names, authentication schemes, pagination patterns, rate limits, and error formats.
For an application that needs to integrate with several vendors in the same category, this fragmentation translates directly into engineering cost. Every integration requires reading new documentation, mapping a new data schema, handling new authentication flows, and building error-handling for new edge cases. Maintenance compounds: each vendor's API evolves independently, and breaking changes must be tracked and adapted to per integration.
A unified API addresses this by providing an abstraction layer between an application and the underlying APIs in a category. The application interacts with a single, consistent interface; the unified API translates each request into the appropriate vendor-specific call and normalizes the response into a common schema.
## How does a unified API speed up development?
A unified API reduces the number of integrations and the work associated with them. Instead of writing integration code per vendor, applications write integration code once against the unified data model. New vendors in the same category become available without additional integration work — the unified API vendor handles the mapping.
For an application supporting a handful of integrations, the time savings are modest. For an application supporting dozens, savings are substantial — typically reducing months of engineering work per integration to days or hours.
## What complexity does a unified API actually remove?
A unified API removes per-vendor variance in four specific areas:
- **Authentication and authorization** — different OAuth2 flows, API key schemes, scope models, and token refresh behaviours collapse into a single authentication interface.
- **Data representation** — different schemas, field names, and data types collapse into a normalized data model per category.
- **Endpoint structure** — different URL patterns, HTTP methods, and request formats collapse into consistent endpoints across the category.
- **Error handling** — different error codes, response formats, and rate-limit behaviours collapse into consistent error semantics.
What a unified API does not remove: the underlying differences in vendor capability. If one vendor in a category supports a feature that others do not, the unified API still has to make a choice about how to surface that feature — typically through a "raw" passthrough mechanism (covered below).
## How does a unified API enhance functionality beyond raw aggregation?
Some vendor APIs lack features that simplify integration — webhooks for change notifications, consistent pagination, predictable rate limits, or filtering. A well-designed unified API can add these capabilities as a layer above the underlying APIs:
- Polling-based event delivery for vendors without native webhooks ("virtual webhooks").
- Consistent pagination patterns across all integrations in a category.
- Centralized rate-limit management that handles per-vendor quotas transparently.
- Standardized filtering and field selection.
This is the difference between a unified API and a request proxy: a unified API can provide features the underlying APIs do not natively support.
## Real-time pass-through vs. sync-based architectures
Unified APIs fall into two architectural categories. The choice has concrete implications for application design.
| Property | Real-time pass-through | Sync-and-cache |
|---|---|---|
| Data freshness | Live; reflects source-system state at request time | Bounded by sync interval; can be hours stale |
| Customer data storage | None; only operational metadata stored | Customer records cached for serving reads |
| Compliance scope | Not a sub-processor for payload data | Full sub-processor for cached records |
| Latency profile | Full source-system round-trip per request | Local cache reads (fast); source delays on writes |
| Write-back behaviour | Synchronous; success/failure known immediately | Instant write, but reads may lag sync interval |
| AI agent compatibility | Agents reason on current source-system state | Risk of agent acting on stale cached data |
| Typical pricing model | Per API call or per request | Per linked account or per consumer |
Note on operational metadata: real-time pass-through unified APIs typically store connection identifiers, encrypted OAuth credentials, and API call logs — but not customer payload data (records like contacts, employees, or invoices). This distinction is what determines compliance scope.
### When real-time pass-through is the right choice
Real-time pass-through is appropriate when:
- The application requires current data on every read (financial reconciliation, AI agent integrations, live dashboards).
- The application's compliance posture benefits from minimizing stored customer data.
- Write-back operations need immediate confirmation of success or failure at the source.
- The application's latency budget can accommodate a full source-API round-trip per request (typically 800ms–1.5s).
### When sync-and-cache is the right choice
Sync-and-cache is appropriate when:
- The application's read patterns tolerate data being minutes or hours stale.
- The application requires sub-100ms read latency that local cache reads provide.
- The application performs high-volume historical analytics that would be expensive to execute against live source APIs.
- The application's compliance framework already accommodates a unified API vendor as a data sub-processor.
## What should you look for in a unified API?
### How are unified API endpoints structured?
A unified API needs unified endpoints for each integration category. An API call to retrieve CRM contacts should be identical regardless of which underlying CRM is providing the data. The endpoint structure should be predictable, RESTful, and consistent across all integrations in the category.
### How does data normalization work across vendors?
Data models must be unified, not just endpoints. A CRM contact returned from one vendor should have the same field structure as a CRM contact returned from another. The normalized schema needs sufficient depth to support real use cases — names and emails are not enough for most applications.
Two depth dimensions to evaluate:
- **Field coverage**: how many fields per object are normalized into the unified schema?
- **Object coverage**: how many distinct object types per category are supported (e.g., for CRM: contacts, companies, deals, pipelines, activities, notes)?
### How is authentication handled?
The unified API should provide a single authentication flow across all integrations in a category. Common authentication metadata (vendor name, account name, scopes granted) should be available programmatically so the application can display connection state natively.
A pre-built authorization UI component is often provided to handle the OAuth2 handshake with end users, reducing the application's auth implementation burden to a single integration.
### How are permission scopes managed?
Along with unified authentication, a unified API should provide abstracted permission scopes so the application does not need to research per-vendor OAuth2 scopes for each use case. Scope abstraction maps the application's intent (e.g., "read contacts") to the appropriate per-vendor scope automatically.
### How do unified APIs handle vendors that don't support webhooks?
Most SaaS APIs do not support native webhooks. A unified API should provide a consistent event-delivery model regardless of whether the underlying vendor supports webhooks natively.
The common implementation pattern is "virtual webhooks": the unified API polls source APIs at configurable intervals, detects changes via timestamps or diffs, and delivers events to the application's endpoint only when changes are found. From the application's perspective, native and virtual webhooks use the same subscription model, the same event format, and the same retry semantics.
This differs from sync-based notification systems, where data is first cached on a schedule and a notification fires after sync completion — requiring the application to fetch the changed data in a separate request. With virtual webhooks, change detection and event delivery are unified: detect changes at the source, deliver events directly.
Polling intervals should be configurable. Default intervals vary substantially across unified API vendors (some default to 24 hours; others support per-minute polling).
### How are custom fields and custom objects handled?
The "lowest common denominator" critique of unified APIs is that normalization across many vendors loses vendor-specific features, custom fields, and custom objects. Most modern unified APIs address this through three mechanisms:
- **Custom field support** in the normalized schema, allowing per-customer field definitions.
- **Metadata APIs** that surface vendor-specific schema information programmatically.
- **Raw passthrough** that allows direct calls to vendor-specific endpoints while still using the unified API's authentication and connection management.
Evaluating these mechanisms requires testing against specific use cases. The ability to handle Salesforce custom objects, HubSpot custom properties, or industry-specific HRIS fields varies meaningfully across unified API vendors.
### How does pricing scale with usage?
Unified API pricing models vary substantially. The four common patterns:
- **Per linked account**: priced by the number of customer-vendor connections (one customer using three integrations = three linked accounts).
- **Per active consumer**: priced by the number of active end-customers using any integration.
- **Per API call**: priced by request volume, with unlimited connections.
- **Consumption-based**: priced by data volume transferred (typically GB).
The right pricing model depends on the application's own pricing structure and customer-integration ratio. Per-linked-account pricing scales with customer × integration count, which can be predictable for low-integration applications and prohibitive for multi-integration ones. Per-API-call pricing scales with actual data activity, which favours applications with many connections but low per-connection volume.
## When is a unified API the wrong choice?
A unified API is not the right architectural choice in every case. Three common scenarios where alternatives are better:
### Single-vendor or two-vendor integrations
If an application only integrates with one or two vendors and has no near-term plans to add more, building per-vendor integrations directly against the source API is often faster and cheaper than adopting a unified API. The amortized engineering savings of a unified API only materialize at three or more integrations in a category.
### Workflow automation across internal applications
If the integration use case is internal workflow automation (e.g., "when a Salesforce deal closes, create a Jira ticket and update an internal database"), an iPaaS platform like Workato, Zapier, or MuleSoft is typically a better fit than a unified API. iPaaS platforms are built for workflow orchestration; unified APIs are built for customer-facing data access.
### Deep per-customer customization
If each customer requires unique integration logic — different field mappings, different sync schedules, different transformation rules — an embedded iPaaS platform (Paragon, Workato Embedded, Tray Embedded) is often a better fit. Embedded iPaaS gives end-users a configuration interface for their own integrations; unified APIs give developers a programmatic interface for standardized integrations.
### Use cases requiring full vendor-specific feature depth
If the application needs deep access to vendor-specific features that are not represented in the unified schema (e.g., Salesforce Apex triggers, HubSpot workflow automation, Workday's full reporting framework), the unified API's normalized layer may not provide sufficient depth. The raw passthrough mechanism partially addresses this, but at the cost of losing the unified abstraction for those operations.
## Security considerations
A unified API sits in the data path between an application and its customers' third-party data. Security evaluation should cover:
- **Customer data storage**: does the unified API store customer payload data at rest, or pass requests through statelessly? This determines compliance scope and audit surface.
- **Credential storage**: where are OAuth2 client credentials and customer access tokens stored? External secret management (e.g., AWS Secrets Manager) is preferable to in-platform storage for high-security applications.
- **Encryption**: at-rest encryption for stored credentials and operational metadata; in-transit encryption (TLS 1.2+) for all API traffic.
- **Compliance certifications**: SOC 2 Type II, GDPR, HIPAA, PIPEDA, ISO 27001 as applicable to the application's regulated data categories.
- **Data residency**: regional infrastructure for applications subject to data residency requirements (US, EU, AU, etc.).
## Reliability considerations
The unified API becomes a critical dependency for the application. Evaluation should cover:
- **SLA and uptime history**: published uptime targets and historical performance.
- **Connection health monitoring**: does the unified API detect broken third-party vendor connections proactively, before the application's customers do?
- **Error reporting and observability**: API call logs, error categorization, retry visibility.
- **Hosting and scaling**: where the unified API is hosted and how it scales under load.
## Use case compatibility: breadth, depth, and clustering
Three dimensions to evaluate against the application's use case:
- **Breadth**: number of supported vendors in any one category. Higher breadth supports more customer-vendor combinations.
- **Depth**: number of fields and object types in the normalized data model. Higher depth supports more sophisticated use cases (e.g., full HRIS payroll vs. just employee directory).
- **Clustering**: support for multiple categories with linked data models. Applications that span CRM + Support + Marketing benefit from clustering; single-category applications do not.
## Unified.to
Unified.to provides a real-time, stateless pass-through unified API across 440+ integrations and 27 categories — including CRM, accounting, HRIS, ATS, payroll, ticketing, e-commerce, messaging, and others. Every request is routed live to the source system. No customer payload data is stored at rest.
Unified.to additionally provides a hosted MCP (Model Context Protocol) server with 20,000+ callable MCP tools, allowing AI agents to read and write across customer SaaS integrations through a single connection-scoped interface.
For pricing details, see [unified.to/pricing](https://unified.to/pricing). For full API documentation, see [docs.unified.to](https://docs.unified.to).
---
# Guides
## Advertising Report Metrics by Integration
URL: https://docs.unified.to/guides/advertising_report_metrics_by_integration
# Advertising Report Metrics by Integration
------
_June 12, 2026_
**Endpoint:** `GET /ads/{connection_id}/report` — [List all reports](https://docs.unified.to/ads/report/List_all_reports)
This is a reference for the metrics returned by the Advertising **Report** endpoint: which metrics each supported integration populates, and how each platform's native field maps onto Unified.to's single normalized reporting model.
## How reports return metrics
A call to `GET /ads/{connection_id}/report` returns an array of [Report objects](https://docs.unified.to/ads/report/model). Each report describes the performance of an advertising entity over a window, and carries its metrics in a single normalized array:
| Field | Type | Description |
| --------------------------- | ------ | ----------------------------------------------------------------- |
| `id` | string | Report identifier |
| `created_at` / `updated_at` | date | ISO-8601 timestamps |
| `start_at` / `end_at` | date | The reporting window the metrics cover |
| `currency` | string | Currency for all monetary metrics in the report |
| `organization_id` | string | The advertising organization the report belongs to |
| `metrics` | array | The normalized performance metrics (the subject of this document) |
| `raw` | any | The untransformed payload from the underlying platform |
The value of the `metrics` array is that it is **the same shape regardless of which platform** **`connection_id`** **points to**. Whether the connection is Meta Ads or LinkedIn, a `CLICKS` metric means the same thing and is read the same way. Behind the scenes, Unified.to handles the platform-specific renaming, the nested-field extraction, and the unit conversions — and where a platform doesn't return a value directly, it computes it. If you ever need the original platform field, it remains available under `raw`.
## The unified metric model
Each ad platform reports the same fundamental ideas — a click, an impression, a dollar spent — but names and shapes them differently. A click is `clicks` in Meta, `METRIC_CLICKS` in Google DV360, and `Clicks` in Microsoft Ads. Cost arrives as raw currency in some platforms and as micro-units (millionths of a currency unit) in Google Ads, which must be divided by 1,000,000 before it means anything.
Unified.to collapses all of that into one vocabulary of **92 normalized metrics** (written in `UPPER_SNAKE_CASE`). The tables below show, for each normalized metric, the native field it is sourced from on each of the five integrations the Report endpoint supports: **Meta Ads, Google Ads, Google DV360, LinkedIn, and Microsoft Ads**. A `—` means that integration does not expose the metric, so it will not appear in the `metrics` array for that connection.
## Coverage at a glance
The breadth of the `metrics` array depends entirely on which integration the connection is for. Not all platforms expose data at the same depth:
| Integration | Metrics returned | Character of the data |
| ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------- |
| **LinkedIn** | 57 / 92 | Deepest coverage, driven by a large set of social, lead-gen, and "viral" (organic amplification) metrics |
| **Google DV360** | 45 / 92 | Strong on viewability, video engagement, and cost/financial breakdowns |
| **Google Ads** | 30 / 92 | Broad core coverage plus search-specific and viewability metrics |
| **Meta Ads** | 19 / 92 | Focused on core performance and detailed video-watch metrics |
| **Microsoft Ads** | 14 / 92 | Core performance metrics only |
Coverage is highly uneven across the metric set. Of the 92 normalized metrics, **60 are returned by only a single integration**, while just **8 are returned by all five**. The long tail is dominated by LinkedIn's social and viral metrics and DV360's financial and viewability metrics, which have no equivalent elsewhere. Write your code defensively: check for a metric's presence rather than assuming every report contains it.
## The universal core
These eight metrics are the common denominator — every supported integration returns them, making them the safe foundation for any cross-connection report or comparison:
`CLICKS` · `IMPRESSIONS` · `CONVERSIONS` · `COST` · `CTR` · `CPC` · `CONVERSION_VALUE` · `CPA`
Even within this universal set, the source data differs. `COST` is `spend` in Meta but arrives in micro-units in Google Ads and as `costInLocalCurrency` in LinkedIn (all normalized to the report's `currency`). `CONVERSIONS` is a clean field in most platforms but is pulled from Meta's nested `actions[purchase]`. And `CTR`, `CPC`, and `CPA` are all **derived** for LinkedIn — a reminder that "returned" sometimes means "computed for you," not "delivered by the platform."
Two near-universal ratios fall just short of full coverage: `ROAS` is derived everywhere it exists but is absent from DV360, and `CPM` is missing only from Google Ads.
## Core performance metrics
The headline numbers most reports are built on — volume, cost, and the efficiency ratios derived from them.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------ | ------------------------------ | -------------------------- | -------------------------------------------------- | ------------------------------ | ----------------- |
| `CLICKS` | clicks | clicks | METRIC_CLICKS | clicks | Clicks |
| `IMPRESSIONS` | impressions | impressions | METRIC_IMPRESSIONS | impressions | Impressions |
| `CONVERSIONS` | actions[purchase] | conversions | METRIC_TOTAL_CONVERSIONS | externalWebsiteConversions | Conversions |
| `COST` | spend | cost_micros (/1e6) | METRIC_REVENUE_ADVERTISER | costInLocalCurrency | Spend |
| `CTR` | ctr | ctr | METRIC_CTR | derived | Ctr |
| `CPC` | cpc | average_cpc (/1e6) | METRIC_REVENUE_ECPC_ADVERTISER | derived | AverageCpc |
| `CONVERSION_VALUE` | action_values[purchase] | conversions_value | METRIC_TRUEVIEW_TOTAL_CONVERSION_VALUES_ADVERTISER | conversionValueInLocalCurrency | Revenue |
| `CPA` | cost_per_action_type[purchase] | cost_per_conversion (/1e6) | METRIC_REVENUE_ECPA_ADVERTISER | derived | CostPerConversion |
| `ROAS` | derived | derived | — | derived | derived |
| `CPM` | cpm | — | METRIC_REVENUE_ECPM_ADVERTISER | derived | AverageCpm |
| `ECPM` | — | — | METRIC_REVENUE_ECPM_ADVERTISER | — | — |
## Conversion detail
Beyond the headline conversion count, platforms differ on how they attribute and segment conversions. Google Ads and Microsoft Ads add "all conversions" totals and their revenue counterparts; DV360 and LinkedIn distinguish post-click from view-through (post-view) conversions; and Google Ads alone exposes cross-device conversions and raw `interactions`.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| -------------------------- | -------- | ------------------------ | ----------------------------- | ----------------------------------- | ---------------------- |
| `POST_CLICK_CONVERSIONS` | — | — | METRIC_POST_CLICK_CONVERSIONS | externalWebsitePostClickConversions | — |
| `VIEW_THROUGH_CONVERSIONS` | — | view_through_conversions | METRIC_POST_VIEW_CONVERSIONS | externalWebsitePostViewConversions | ViewThroughConversions |
| `ALL_CONVERSIONS` | — | all_conversions | — | — | AllConversions |
| `ALL_CONVERSION_VALUE` | — | all_conversions_value | — | — | AllRevenue |
| `CROSS_DEVICE_CONVERSIONS` | — | cross_device_conversions | — | — | — |
| `INTERACTIONS` | — | interactions | — | — | — |
## Video engagement
Video is the most fragmented category, because each platform instruments playback in its own way. Quartile completion (25/50/75/100%) is the one widely shared concept, supported across Meta, Google Ads, DV360, and LinkedIn — though Google Ads reports quartiles as _rates_ while the others report counts. From there the platforms diverge: Meta uniquely tracks `VIDEO_AVG_TIME_WATCHED` and `VIDEO_THRUPLAY`; DV360 captures rich-media interaction signals like pauses, mutes, skips, and companion-banner activity; and Google Ads surfaces search-driven views.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------------- | ------------------------------ | -------------------------------------- | ------------------------------------------------ | ----------------------------- | ------------- |
| `VIDEO_VIEWS` | — | video_views | METRIC_TRUEVIEW_VIEWS | videoViews | VideoViews |
| `VIDEO_PLAYS` | video_play_actions | — | METRIC_RICH_MEDIA_VIDEO_PLAYS | videoStarts | — |
| `VIDEO_COMPLETIONS` | — | — | METRIC_RICH_MEDIA_VIDEO_COMPLETIONS | videoCompletions | — |
| `VIDEO_QUARTILE_25` | video_p25_watched_actions | video_quartile_p25_rate | METRIC_RICH_MEDIA_VIDEO_FIRST_QUARTILE_COMPLETES | videoFirstQuartileCompletions | — |
| `VIDEO_QUARTILE_50` | video_p50_watched_actions | video_quartile_p50_rate | METRIC_RICH_MEDIA_VIDEO_MIDPOINTS | videoMidpointCompletions | — |
| `VIDEO_QUARTILE_75` | video_p75_watched_actions | video_quartile_p75_rate | METRIC_RICH_MEDIA_VIDEO_THIRD_QUARTILE_COMPLETES | videoThirdQuartileCompletions | — |
| `VIDEO_QUARTILE_100` | video_p100_watched_actions | video_quartile_p100_rate | METRIC_RICH_MEDIA_VIDEO_COMPLETIONS | — | — |
| `VIDEO_AVG_TIME_WATCHED` | video_avg_time_watched_actions | — | — | — | — |
| `VIDEO_THRUPLAY` | video_thruplay_watched_actions | — | — | — | — |
| `AVERAGE_CPV` | — | average_cpv (/1e6) | — | — | — |
| `EARNED_VIEWS` | — | — | METRIC_TRUEVIEW_EARNED_VIEWS | — | — |
| `UNIQUE_VIEWERS` | — | — | METRIC_TRUEVIEW_UNIQUE_VIEWERS | — | — |
| `VIDEO_VIEWS_FROM_SEARCH` | — | video_views_from_google_search_results | — | — | — |
| `VIDEO_FULLSCREENS` | — | — | METRIC_RICH_MEDIA_VIDEO_FULL_SCREENS | fullScreenPlays | — |
| `VIDEO_PAUSES` | — | — | METRIC_RICH_MEDIA_VIDEO_PAUSES | — | — |
| `VIDEO_MUTES` | — | — | METRIC_RICH_MEDIA_VIDEO_MUTES | — | — |
| `VIDEO_SKIPS` | — | — | METRIC_RICH_MEDIA_VIDEO_SKIPS | — | — |
| `COMPANION_CLICKS` | — | — | METRIC_RICH_MEDIA_VIDEO_COMPANION_CLICKS | — | — |
| `COMPANION_VIEWS` | — | — | METRIC_RICH_MEDIA_VIDEO_COMPANION_VIEWS | — | — |
## Social engagement
Likes, shares, comments, and follows are native to the social-first platforms. LinkedIn reports them directly, and DV360 exposes the YouTube "earned" equivalents (earned likes, shares, and subscribers). Curiously, Google Ads maps `SHARES` and `SAVES` onto Gmail-specific forward and save actions — an artifact of how Gmail Ads engagement was instrumented.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------- | -------- | -------------- | ----------------------------------------- | ---------------- | ------------- |
| `ENGAGEMENT` | — | — | METRIC_ENGAGEMENT_RATE | derived (sum) | — |
| `ENGAGEMENTS` | — | engagements | METRIC_ENGAGEMENTS | — | — |
| `TOTAL_ENGAGEMENTS` | — | — | — | totalEngagements | — |
| `OTHER_ENGAGEMENTS` | — | — | — | otherEngagements | — |
| `LIKES` | — | — | METRIC_TRUEVIEW_EARNED_LIKES | likes | — |
| `SHARES` | — | gmail_forwards | METRIC_TRUEVIEW_EARNED_SHARES | shares | — |
| `COMMENTS` | — | — | — | comments | — |
| `FOLLOWS` | — | — | METRIC_TRUEVIEW_EARNED_SUBSCRIBERS | follows | — |
| `SAVES` | — | gmail_saves | METRIC_TRUEVIEW_EARNED_PLAYLIST_ADDITIONS | — | — |
| `COMMENT_LIKES` | — | — | — | commentLikes | — |
| `OPENS` | — | — | — | opens | — |
## Click and impression breakdowns
Most of these granular click types are LinkedIn-specific, reflecting its sponsored-content and document-ad formats (card clicks, company-page clicks, text-URL clicks, and so on). Meta contributes `LANDING_PAGE_CLICKS` (its `inline_link_clicks`) and `UNIQUE_CLICKS`, while LinkedIn adds an approximate unique-impression count.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------------ | ------------------ | ---------------------- | ------------ | ---------------------------- | ------------- |
| `LANDING_PAGE_CLICKS` | inline_link_clicks | — | — | landingPageClicks | — |
| `AD_UNIT_CLICKS` | — | — | — | adUnitClicks | — |
| `CARD_CLICKS` | — | — | — | cardClicks | — |
| `CARD_IMPRESSIONS` | — | — | — | cardImpressions | — |
| `COMPANY_PAGE_CLICKS` | — | — | — | companyPageClicks | — |
| `ACTION_CLICKS` | — | — | — | actionClicks | — |
| `TEXT_URL_CLICKS` | — | — | — | textUrlClicks | — |
| `GMAIL_SECONDARY_CLICKS` | — | gmail_secondary_clicks | — | — | — |
| `UNIQUE_CLICKS` | unique_clicks | — | — | — | — |
| `UNIQUE_IMPRESSIONS` | — | — | — | approximateUniqueImpressions | — |
## Lead generation
Native lead-form metrics are exclusive to LinkedIn in this mapping, tied to its one-click Lead Gen Forms.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ----------------- | -------- | ---------- | ------------ | --------------------- | ------------- |
| `LEADS` | — | — | — | oneClickLeads | — |
| `LEAD_FORM_OPENS` | — | — | — | oneClickLeadFormOpens | — |
## Viewability and measurement
Viewability — whether an impression was actually seen — is a Google ecosystem strength. Google Ads and DV360 share the Active View family (viewable, measurable, and percent-viewable impressions), DV360 adds billable and eligible impression counts plus average viewable time, and Google Ads contributes top-of-page impression share metrics from search.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------------------- | -------- | ---------------------------------- | ------------------------------------------- | -------- | ------------- |
| `VIEWABLE_IMPRESSIONS` | — | active_view_impressions | METRIC_ACTIVE_VIEW_VIEWABLE_IMPRESSIONS | — | — |
| `MEASURABLE_IMPRESSIONS` | — | active_view_measurable_impressions | METRIC_ACTIVE_VIEW_MEASURABLE_IMPRESSIONS | — | — |
| `VIEWABILITY_RATE` | — | active_view_viewability | METRIC_ACTIVE_VIEW_PCT_VIEWABLE_IMPRESSIONS | — | — |
| `BILLABLE_IMPRESSIONS` | — | — | METRIC_BILLABLE_IMPRESSIONS | — | — |
| `ELIGIBLE_IMPRESSIONS` | — | — | METRIC_ACTIVE_VIEW_ELIGIBLE_IMPRESSIONS | — | — |
| `ACTIVE_VIEW_AVG_TIME` | — | — | METRIC_ACTIVE_VIEW_AVERAGE_VIEWABLE_TIME | — | — |
| `ABSOLUTE_TOP_IMPRESSION_SHARE` | — | absolute_top_impression_percentage | — | — | — |
| `TOP_IMPRESSION_SHARE` | — | top_impression_percentage | — | — | — |
## Cost and financial breakdowns
DV360 is the only integration that decomposes cost into its component fees — media cost, data fees, platform fees — and reports advertiser-level revenue and profit. This reflects its role as a programmatic buying platform where margin transparency matters.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| ------------------ | -------- | ---------- | ---------------------------------- | -------- | ------------- |
| `REVENUE` | — | — | METRIC_REVENUE_ADVERTISER | — | — |
| `MEDIA_COST` | — | — | METRIC_MEDIA_COST_ADVERTISER | — | — |
| `TOTAL_MEDIA_COST` | — | — | METRIC_TOTAL_MEDIA_COST_ADVERTISER | — | — |
| `DATA_FEES` | — | — | METRIC_DATA_COST_ADVERTISER | — | — |
| `PLATFORM_FEES` | — | — | METRIC_PLATFORM_FEE_ADVERTISER | — | — |
| `PROFIT` | — | — | METRIC_PROFIT_ADVERTISER | — | — |
## Viral / organic amplification (LinkedIn)
LinkedIn's "viral" metrics measure the organic spread of sponsored content — engagement that happens when someone's network sees an ad because a connection interacted with it. This entire 19-metric family is exclusive to LinkedIn and mirrors its standard metrics (impressions, clicks, conversions, video quartiles, leads) but for organically-amplified reach. No other integration models this concept.
| Unified Metric | Meta Ads | Google Ads | Google DV360 | LinkedIn | Microsoft Ads |
| -------------------------------- | -------- | ---------- | ------------ | ---------------------------------------- | ------------- |
| `VIRAL_IMPRESSIONS` | — | — | — | viralImpressions | — |
| `VIRAL_CLICKS` | — | — | — | viralClicks | — |
| `VIRAL_LIKES` | — | — | — | viralLikes | — |
| `VIRAL_COMMENTS` | — | — | — | viralComments | — |
| `VIRAL_SHARES` | — | — | — | viralShares | — |
| `VIRAL_FOLLOWS` | — | — | — | viralFollows | — |
| `VIRAL_VIDEO_PLAYS` | — | — | — | viralVideoStarts | — |
| `VIRAL_VIDEO_VIEWS` | — | — | — | viralVideoViews | — |
| `VIRAL_VIDEO_COMPLETIONS` | — | — | — | viralVideoCompletions | — |
| `VIRAL_VIDEO_QUARTILE_25` | — | — | — | viralVideoFirstQuartileCompletions | — |
| `VIRAL_VIDEO_QUARTILE_50` | — | — | — | viralVideoMidpointCompletions | — |
| `VIRAL_VIDEO_QUARTILE_75` | — | — | — | viralVideoThirdQuartileCompletions | — |
| `VIRAL_LEADS` | — | — | — | viralOneClickLeads | — |
| `VIRAL_LEAD_FORM_OPENS` | — | — | — | viralOneClickLeadFormOpens | — |
| `VIRAL_LANDING_PAGE_CLICKS` | — | — | — | viralLandingPageClicks | — |
| `VIRAL_CONVERSIONS` | — | — | — | viralExternalWebsiteConversions | — |
| `VIRAL_POST_CLICK_CONVERSIONS` | — | — | — | viralExternalWebsitePostClickConversions | — |
| `VIRAL_VIEW_THROUGH_CONVERSIONS` | — | — | — | viralExternalWebsitePostViewConversions | — |
| `VIRAL_ENGAGEMENTS` | — | — | — | viralTotalEngagements | — |
## Integration-by-integration takeaways
**LinkedIn** is the deepest integration by a wide margin. Beyond solid core coverage, it owns two large metric families that exist nowhere else: native lead-form metrics and the complete "viral" organic-amplification set. If a report needs B2B lead data or organic-reach attribution, LinkedIn is the only source.
**Google DV360** is the choice for media-buying transparency. It uniquely breaks cost into media, data, and platform fees and reports profit, and it offers the richest video-interaction signals (pauses, mutes, skips, companion banners) alongside full Active View viewability.
**Google Ads** provides broad, dependable core coverage plus search-native metrics — impression share, cross-device conversions, and "all conversions" rollups. Watch for its two quirks: micro-unit cost fields that need dividing by a million, and quartile metrics expressed as rates rather than counts.
**Meta Ads** concentrates on core performance and granular video-watch behavior. Its conversion and value figures come from nested action breakdowns rather than flat fields, so the `purchase` action type must be extracted explicitly.
**Microsoft Ads** is the leanest integration, covering the universal core plus video views, view-through conversions, and "all conversions" totals — sufficient for standard performance reporting but without engagement, viewability, or social depth.
## Working with the response
- **Build cross-connection reports on the universal eight.** `CLICKS`, `IMPRESSIONS`, `CONVERSIONS`, `COST`, `CTR`, `CPC`, `CONVERSION_VALUE`, and `CPA` are the only metrics guaranteed to appear in the `metrics` array for every supported integration.
- **Check for presence, don't assume it.** With 60 of 92 metrics returned by only one integration, code that reads the `metrics` array should handle a metric being absent rather than expecting a fixed set.
- **Read currency from the report, not the metric.** All monetary metrics are normalized into the report's `currency` field; Google Ads micro-units (`/1e6`) and per-platform conventions are already resolved for you.
- **Some metrics are computed.** Ratios like `ROAS`, and several of LinkedIn's rates, are derived by Unified.to rather than supplied by the platform — they arrive in the `metrics` array all the same.
- **Drop to** **`raw`** **when you need the original.** Anything platform-specific that the normalized model doesn't capture remains available on the report's `raw` field.
## ATS to Vector DB: How to Power Talent Intelligence with Real-Time Data
URL: https://docs.unified.to/guides/ats_to_vector_db_how_to_power_talent_intelligence_with_real_time_data
# ATS to Vector DB: How to Power Talent Intelligence with Real-Time Data
------
_October 14, 2025_
_Updated June 2026_
With Unified.to, you can build a talent intelligence application that works with your customers' preferred ATS — Lever, Greenhouse, and 80+ others — through one integration.
With a single API, you fetch candidate records, normalize and embed resumes, and upsert those embeddings into a vector database like Pinecone for semantic search and recruiter-agent workflows. The result is a retrieval layer for talent intelligence, built on ATS data fetched live from the source.
This guide shows you how to go from ATS to vector DB, step by step, using Unified.to, its GenAI API, and Pinecone.
## What this builds
1. Fetch normalized candidate data from an ATS.
2. Chunk and embed resume content.
3. Store embeddings in a vector database.
4. Retrieve the most relevant candidates at query time.
5. Use the retrieved context to power recruiter search, ranking, or AI agents.
Unified.to handles ingestion and live updates across ATS providers; the embeddings and vector storage stay in your infrastructure. Because Unified.to is pass-through, no candidate data rests on its servers — the records flow to your retrieval layer and stop there.
---
## Prerequisites
- Node.js (v18+)
- A Unified.to account with an ATS integration enabled (e.g., Lever, Greenhouse)
- A Unified.to API key
- Your customer's ATS connection ID
- A Unified.to GenAI connection ID (for embeddings)
- A Pinecone API key and index
---
## Step 1: Set up your project
```bash
mkdir ats-vector-demo
cd ats-vector-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv @pinecone-database/pinecone
```
Add your credentials to `.env`:
```plain text
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_ATS=your_customer_ats_connection_id
CONNECTION_GENAI=your_genai_connection_id
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_INDEX=your_pinecone_index
```
---
## Step 2: Initialize the SDKs
```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { Pinecone } from '@pinecone-database/pinecone';
const {
UNIFIED_API_KEY,
CONNECTION_ATS,
CONNECTION_GENAI,
PINECONE_API_KEY,
PINECONE_INDEX,
} = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
const pinecone = new Pinecone({ apiKey: PINECONE_API_KEY! });
const index = pinecone.Index(PINECONE_INDEX!);
```
---
## Step 3: Get your customer's connection ID
Before you can fetch candidates, your customer authorizes your app to access their ATS (e.g., Lever, Greenhouse) through Unified.to's embedded authorization flow. Once authorized, you receive a **connection ID** for that customer's integration. Store it securely and use it in all API calls for that customer.
---
## Step 4: Fetch and normalize candidate records
Fetch candidates from the ATS and flatten each into a single text block for embedding. Normalizing the resume text before embedding keeps embeddings consistent across every ATS.
```typescript
import type { AtsCandidate } from '@unified-api/typescript-sdk/models/components';
export async function fetchCandidates(connectionId: string): Promise {
return await sdk.ats.listAtsCandidates({
connectionId,
limit: 50,
});
}
export function normalizeResume(candidate: AtsCandidate): string {
const experiences = (candidate.experiences ?? [])
.map((exp: any) => `${exp.title ?? ''} at ${exp.company_name ?? ''}`)
.join('; ');
const education = (candidate.education ?? [])
.map((edu: any) => `${edu.degree ?? ''} in ${edu.field_of_study ?? ''} from ${edu.institution ?? ''}`)
.join('; ');
return [
`Name: ${candidate.name ?? ''}`,
`Email: ${candidate.emails?.[0]?.email ?? ''}`,
`Title: ${candidate.title ?? ''}`,
`Skills: ${(candidate.skills ?? []).join(', ')}`,
`Experience: ${experiences}`,
`Education: ${education}`,
].join('\n');
}
```
---
## Step 5: Embed resumes with the GenAI API
Use Unified.to's GenAI embedding endpoint. Note `content` is an array, `encoding_format` takes `FLOAT`, and `type` distinguishes documents you index (`SEARCH_DOC`) from the recruiter query (`SEARCH_QUERY`). The `embeddings` field returns a JSON string, so parse it before use.
```typescript
export async function embed(text: string, kind: 'SEARCH_DOC' | 'SEARCH_QUERY'): Promise {
const result = await sdk.genai.createGenaiEmbedding({
connectionId: CONNECTION_GENAI!,
genaiEmbedding: {
modelId: 'text-embedding-3-small',
content: [text],
// If the SDK rejects this key, the upstream schema spells it `enconding_format`;
// the mechanically camelCased form is then `encondingFormat`. One live call confirms which.
encodingFormat: 'FLOAT',
type: kind,
dimension: 1536,
},
});
// `embeddings` is a read-only JSON string per the GenAI model.
return JSON.parse(result.embeddings ?? '[]');
}
```
### Keeping the vector index current
To keep your retrieval layer fresh, subscribe to ATS webhooks for candidate create and update events. When a resume changes, re-fetch the candidate, re-embed, and upsert. Unified.to manages native and virtual webhooks, so you receive these events even when the ATS has no native webhook support.
---
## Step 6: Upsert embeddings to Pinecone
```typescript
export async function upsertCandidate(candidate: AtsCandidate, values: number[]) {
await index.upsert([
{
id: candidate.id!,
values,
metadata: {
name: candidate.name ?? '',
email: candidate.emails?.[0]?.email ?? '',
candidate_id: candidate.id ?? '',
},
},
]);
}
```
---
## Step 7: Retrieval
Given a recruiter query, embed it with `SEARCH_QUERY` and search Pinecone for the closest candidates.
```typescript
export async function searchCandidates(query: string) {
const queryVector = await embed(query, 'SEARCH_QUERY');
const results = await index.query({
vector: queryVector,
topK: 5,
includeMetadata: true,
});
return results.matches;
}
```
---
## Step 8: Putting it together
```typescript
async function main() {
const candidates = await fetchCandidates(CONNECTION_ATS!);
for (const candidate of candidates) {
const resumeText = normalizeResume(candidate);
const values = await embed(resumeText, 'SEARCH_DOC');
await upsertCandidate(candidate, values);
}
const matches = await searchCandidates('Senior Python developer with fintech experience');
console.log('Top matches:', matches);
}
main();
```
---
## What you built
- A single API call fetches candidate records from any ATS (Lever, Greenhouse, and 80+ more).
- Resumes are normalized and embedded through Unified.to's GenAI API, then upserted into Pinecone.
- Recruiters search in natural language and retrieve the most relevant candidates — over data fetched live from the source, with nothing cached on Unified.to's side.
For the product-level walkthrough, see [How to Build a Candidate Assessment Product with Unified.to](https://unified.to/blog/how_to_build_a_candidate_assessment_product_with_unified). To wire up sourcing, see How to Build Candidate Sourcing with a Unified API.
Ready to build? [Sign up for a free 30-day trial](https://app.unified.to/) or [book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified).
## Building AI applications with Unified and Langbase
URL: https://docs.unified.to/guides/building_ai_applications_with_unified_and_langbase
# Building AI applications with Unified and Langbase
------
_February 27, 2025_

Developers building AI applications for their customers face a common challenge: they need to juggle many different APIs and authorization flows, configure and manage multiple data pipelines, and deal with complex data models. What if you could focus on building amazing AI experiences without getting bogged down by all that complexity?
That's where the power of Unified.to's APIs combined with [Langbase's](https://langbase.com/) serverless AI developer platform comes in. In this article, we'll look at how you can leverage these platforms to build sophisticated AI applications using your customers' data while writing significantly less integration and AI boilerplate code.
## Why use Unified.to with Langbase?
When you pair Unified.to's normalized data access with Langbase's AI capabilities, you get:
- **One API, many integrations:** Instead of building separate integrations for each platform, use [Unified.to](https://unified.to/)'s unified APIs to access normalized data from multiple data sources through a single, unified API. Langbase does the same for AI—with multi-agent orchestration and advanced long-term memory, seamlessly integrating AI capabilities. It supports 250+ LLMs through one API, ensuring a unified developer experience with easy model switching and optimization. It's an API-first platform, with simple Pipe and Memory Agent APIs for effortless integration.
- **Unified authorization:** Seamlessly handle authorization with Unified.to's pre-built components, creating a secure link between your app and your customers' data with just a few lines of code.
- **Production-ready AI:** Langbase Pipes and Memory Agents (serverless RAG agents with long-term memory) eliminate infrastructure management, making scaling and deployment seamless for all developers—not just AI/ML experts. Its composable AI infrastructure lets developers chain multiple models into AI pipelines, optimizing cost, performance, and personalization at every step.
- **Real-time updates:** Build dynamic applications using Unified.to's webhook system. Fine-tine AI models on the fly when your customers have new data, allowing your AI agent to provide more relevant responses.
## Real-world applications you can build
Let's look at some practical applications you can create by combining these platforms.
**Intelligent support bots**
Build an AI-powered support bot that can answer questions using your customer's knowledge base, handle real-time inquiries, and maintain context across conversations. By combining Unified.to's KMS (Knowledge Management System) and messaging APIs with [Langbase's Memory Agents](https://langbase.com/docs/memory) , you can create a bot that gets smarter as your knowledge base grows.
The best part? Since you're using unified APIs, you can easily adapt this pattern for different platforms:
- Use Notion, Confluence or other KMS platforms for your knowledge base
- Deploy your bot on Slack instead of Discord
Want to build your own? Check out our step-by-step [guide on building a Discord support bot.](https://docs.unified.to/guides/how_to_build_a_discord_support_bot_with_unified_and_langbase#how-to-build-a-discord-support-bot-with-unifiedto-and-langbase)
**AI-powered recruitment assistant**
Create an intelligent recruiting assistant that streamlines your hiring process by automatically screening candidates, conducting initial interviews, updating candidates' application statuses, and providing detailed summaries to your hiring team. Using Unified.to's [ATS integrations](https://unified.to/ats) and Langbase [Pipe agents](https://langbase.com/docs/pipe/quickstart), your assistant can handle the time-consuming parts of recruitment while maintaining a personal touch.
The unified approach means you can support multiple ATS platforms (Lever, Greenhouse, Workday, etc.) without rewriting your core logic.
**Enhanced email agents**
With Unified.to and Langbase, you can automate email responses and streamline communication. By combining Unified.to's email integrations with Langbase's composable multi-agent architecture, you can build an AI-powered email workflow that enhances efficiency and personalization.
**Intelligent sales assistant**
Supercharge your sales team with an AI assistant that understands your customer relationships, helps qualify leads, and generates personalized follow-ups based on past interactions. Using Unified.to's CRM integrations with [Langbase's Memory Agents](https://langbase.com/docs/memory) (that dynamically attach private data to any LLM at scale, with industry-leading accuracy in advanced agentic routing and intelligent reranking), you can create an assistant that becomes an invaluable part of your sales process. Create leads and deals in your CRM automatically and tap into Unified.to's Enrichment API to add extra contextual data for the Memory Agent.
## Getting started
Ready to start building? Here's how to get going:
1. Sign up for accounts on [Unified.to](https://unified.to/) and[ Langbase](https://langbase.com/)
2. Join our [Discord community](https://discord.gg/85z7HF7JbD) to connect with other developers
3. Keep an eye on our blog for upcoming guides in this series
## Looking ahead
The combination of Unified.to and Langbase opens up exciting possibilities for AI application development. As both platforms continue to evolve, you can expect:
- More supported platforms and integrations
- Enhanced AI capabilities through Langbase's Memory Agents
- New unified APIs for different use cases
- Expanded tutorials and example applications
Stay tuned for our upcoming tutorials that will dive deep into each use case, providing concrete examples and code you can use in your own applications.
## Concur & Concur (Company) — Connection Guide
URL: https://docs.unified.to/guides/concur_and_concur_company_connection_guide
# Concur & Concur (Company) — Connection Guide
------
_April 10, 2026_
## Overview
There are two SAP Concur integrations are available. You will need to choose which one to use based off of the authentication type and required data objects.
### Integrations
- **Concur**
- End-user OAuth (authorization code flow)
- Categories: Accounting, Storage, Auth
- **Concur (Company)**
- Company-level authentication
- Uses credentials instead of user OAuth
- Categories: Accounting, HRIS, Storage, Auth
---
## Concur vs Concur (Company)
### Concur
- **Authentication:** OAuth 2.0 (user-based)
- **Credentials:**
- Client ID
- Client Secret
- Redirect URI
- **Supported Objects:**
- Accounting — Expense
- Accounting — Invoice
- Accounting — Contact
- Storage — File
- **Use Case:**
- User connects their own account and grants access
---
### Concur (Company)
- **Authentication:** Company token flow (server-to-server)
- **token_names:**
- Client ID
- Client Secret
- Company UUID
- Request Token
- **Supported Objects:**
- Accounting — Expense
- Accounting — Invoice
- Accounting — Contact
- HRIS — Employee
- Storage — File
- **Use Case:**
- Company-wide access
- Backend integrations
- Employee data access
---
## When to Use Which
- **Use Concur**
- When a user signs in and authorizes access
- **Use Concur (Company)**
- When you need company-wide access
- When working with employee data
- When SAP requires Company UUID + Request Token
---
## OAuth Scopes
| Object | Read | Write |
| ------- | --------------------------------------------------- | --------------------------------------------------- |
| Expense | EXPRPT | EXPRPT |
| Invoice | INVPMT | INVPMT |
| File | IMAGE | IMAGE |
| User | `identity.user.ids.read`, `identity.user.core.read` | `identity.user.ids.read`, `identity.user.core.read` |
| Vendor | INVVEN | INVVEN |
---
## Concur (Company) — Credential Details
### Required Fields
- **Client ID:** From SAP Concur OAuth Application Management
- **Client Secret:** From the same OAuth application
- **Company UUID:** Unique identifier for your company in SAP Concur
- **Request Token:** Temporary token generated via SAP Concur Company Request Token tool (Guide: [https://developer.concur.com/api-reference/authentication/company-refresh-tool.html](https://developer.concur.com/api-reference/authentication/company-refresh-tool.html))
- Not long-lived
- Must be regenerated when expired
---
## Region Reference
Select the correct API region based on your tenant:
- Production (US2 default)
- EU2
- APJ1
- US Government
- Sandbox / Implementation
If unsure, confirm with your SAP Concur administrator.
---
## Network Requirements
To ensure successful API communication, you may need to whitelist Unified IP addresses on your SAP Concur account.
You can find the list of IP addresses here:
[https://app.unified.to/apikeys](https://app.unified.to/apikeys)
Failure to whitelist these IPs may result in connection or data access issues.
## Connect Amazon Seller Central to Unified
URL: https://docs.unified.to/guides/connect_amazon_seller_central to_unified
# Connect Amazon Seller Central to Unified
------
_May 13, 2026_
**What this integration covers**
| Area | Unified objects | Typical use |
| -------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| **E‑Commerce** | commerce_item, commerce_collection, commerce_inventory, commerce_saleschannel | Catalog, product types, FBA inventory summaries, marketplace participation |
| **Accounting** | commerce_account, accounting_salesorder, accounting_contact | Orders and related account/contact style data from SP-API |
| **Metadata** | metadata_metadata | Custom / structured metadata mapping where supported |
Exact read/write support follows your workspace permissions and the integration's object support matrix in the Unified app.
---
**Prerequisites (Amazon side)**
1. **Active Seller Central** account for the marketplaces you care about.
2. **SP-API developer registration** — create and configure a **Selling Partner API** application in the Amazon developer / SP-API flow. Please follow this guide: [https://developer-docs.amazon.com/sp-api/docs/registering-your-application](https://developer-docs.amazon.com/sp-api/docs/registering-your-application)



Put the redirect_url as shown here: [https://app.unified.to/integrations/amazonsellercentral?tab=oauth2](https://app.unified.to/integrations/amazonsellercentral?tab=oauth2)

3. **LWA (Login with Amazon) security profile** linked to that application so you obtain:
- **LWA Client ID**
- **LWA Client Secret**
1. **Marketplace ID(s)** for each marketplace (e.g. US). Reference: Marketplace IDs.
2. **Correct region** for the SP-API endpoint you will use (see below).
**NOTE:** Amazon's SP-API documentation also describes **IAM users/roles and request signing** for many operations. Follow Amazon's current SP-API onboarding for your app (including any **IAM / signing** requirements Amazon still enforces for certain endpoints). If calls fail with auth errors, verify the app's SP-API roles and Amazon's latest auth requirements.
---
**Choose the SP-API region (must match your selling region)**
When connecting, pick the environment that matches where you sell (this selects **authorize URL** and **SP-API base URL**):
| Region | Host |
| ----------------- | -------------------------------- |
| **North America** | **Seller Central** host : .com |
| **Europe** | **Seller Central** host : .co.uk |
| **Far East** | **Seller Central** host : .co.jp |
---
**Connect in Unified (step-by-step)**
1. **Open Integrations** (or your product's connection UI) and activate **Amazon Seller Central**.
2. **Enter OAuth app credentials** from Amazon (LWA **Client ID** and **Client Secret**) in the fields your Unified workspace uses for this integration (same pattern as other OAuth2 apps).
3. **Set Marketplace ID** in the field Unified uses as the developer / connection key for this integration (**labeled as Marketplace ID** in vendor config).
4. **Select the correct regional URL** (North America vs Europe vs Far East) so authorize and API hosts align with your seller account.
5. **Start OAuth** — you will be sent to **Seller Central** to approve the app (/apps/authorize/consent).
---
**Sandbox**
Amazon documents an SP-API sandbox: [https://app.unified.to/integrations/amazonsellercentral?tab=partnership](https://app.unified.to/integrations/amazonsellercentral?tab=partnership)
Use it if you are testing before production seller data.
## Connecting BambooHR via OAuth 2
URL: https://docs.unified.to/guides/connecting_bamboohr_via_oauth_2
# Connecting BambooHR via OAuth 2
------
_July 28, 2026_
# BambooHR OAuth Setup Guide
## Overview
First, register an application in the BambooHR Developer Portal to obtain OAuth credentials.
Next, configure those credentials in Unified.
After the setup is complete, your end users can authorize their own BambooHR accounts through your embedded Unified connection flow.
---
# Part A — Set Up Your App in the BambooHR Developer Portal
> **One-time setup**
## 1. Open the BambooHR Developer Portal
Go to:
[https://developers.bamboohr.com/home/applications](https://developers.bamboohr.com/home/applications)
Sign in to your BambooHR Developer Portal account.
## 2. Create a New Application

1. Click **Add application** in the top-right corner.
2. Enter an **Application Name**, for example: `unified`
3. Click **Create Application**.
## 3. Collect Your Application Details
After creating the application, note the following values:
- **Application Key**
- **Application ID**
You will need the **Application Key** when configuring the integration in Unified.
## 4. Copy Your OAuth Credentials
Under **App Credentials**, copy the following:
- **Client ID**
- **Client Secret**
Use the eye icon to reveal the Client Secret.

You can use **Regenerate** to create a new Client Secret when required.
> **Important:** Treat the Client Secret like a password. If you regenerate it, you must also update the Client Secret in Unified.
## 5. Add the Redirect URI
Under **Redirect URIs**, add the OAuth callback URL provided by Unified.
## 6. Select the Required Scopes
Go to:
**Application Scopes → Select Scopes**

For each available category, such as Employee, Company, Hiring, Time Off, and Reports, choose the appropriate permission level:
- **No Access**
- **Read**
- **Read / Write**
Configure the following scopes as required by your implementation:
```plain text
email
openid
company:info
company_file
company_file.write
employee
employee.write
employee:assets
employee:assets.write
employee:compensation
employee:compensation.write
employee:contact
employee:contact.write
employee:custom_fields
employee:custom_fields.write
employee:custom_fields_encrypted
employee:custom_fields_encrypted.write
employee:demographic
employee:demographic.write
employee:dependent
employee:dependent.write
employee:dependent:ssn
employee:dependent:ssn.write
employee:education
employee:education.write
employee:emergency_contacts
employee:emergency_contacts.write
employee:file
employee:file.write
employee:identification
employee:identification.write
employee:job
employee:job.write
employee:management
employee:management.write
employee:name
employee:name.write
employee:payroll
employee:payroll.write
employee:photo
employee:photo.write
employee:providers
employee:providers.write
employee:providers:payroll
employee:providers:payroll.write
employee:vaccination
employee:vaccination.write
employee_verifications
employee_verifications.write
goal
goal.write
onboarding
onboarding.write
performance:assessments
performance:assessments.write
performance:feedback
performance:feedback.write
performance:one_on_ones
performance:one_on_ones.write
sensitive_employee:address
sensitive_employee:address.write
sensitive_employee:creditcards
sensitive_employee:creditcards.write
sensitive_employee:protected_info
sensitive_employee:protected_info.write
tasks
tasks.write
hiring:applications
hiring:applications.write
hiring:job_openings
hiring:job_openings.write
offline_access
user
user.write
report
report.write
time_off
time_off.write
time_off:requests
time_off:requests.write
```
---
# Part B — Configure BambooHR in Unified
## 7. Configure the BambooHR Integration
In the Unified dashboard, go to:
**Integrations → BambooHR → Authorization**

Configure the following fields:
- Set the authorization method to **OAuth 2**
- Paste the **OAuth 2 Client ID** from Step 4
- Paste the **OAuth 2 Client Secret** from Step 4
- Enter the **Developer API Key**
- Use the **Application Key** collected in Step 3
Click **Update** to save the configuration.

## 8. Configure the Embedded Component
In the Unified dashboard, go to:
**Embedded components**

Configure the following settings:
- **Success URL**
- **Failure URL**
- **Data region**, such as:
- US
- EU
- AU
- **Permission Scopes / Categories**
Copy the appropriate embed code into your application.
Unified provides embed options for:
- React
- Angular
- Vue
- Svelte
- JavaScript
- API-based implementations
---
# Part C — End-User Authorization Flow
## 9. Enter the BambooHR Domain
In the embedded authorization flow, the end user sees the **Authorize Access** screen.
The user enters their **BambooHR Domain** and clicks **Authorize**.

## 10. Approve Access
BambooHR displays a consent screen similar to:
> **Share access with unified?**
The user reviews the requested scopes and clicks **Allow Access**.

## 11. Confirm the Connection
After authorization is completed, the connection appears in Unified under:
**Settings → Connections**
The connection should display a **Healthy** status.

## Connecting Google Workspace Integrations with a Service Account
URL: https://docs.unified.to/guides/connecting_google_workspace_integrations_with_a_service_account
# Connecting Google Workspace Integrations with a Service Account
------
_May 21, 2026_
Unified API supports two ways to authenticate Google integrations: the standard interactive **OAuth2** flow (a user clicks "Allow"), or a **service account** for server-to-server access with no human in the loop. This guide covers the service account path: how to create one, how to grant it access to your data, and what to enter in Unified API.
## **When to use a service account**
Use a service account when you want a backend system to access Google data without a user logging in each time — for example, syncing every mailbox in your company, reading a shared Drive, or managing your Workspace directory. For most Workspace data (Gmail, Calendar, Drive files owned by users, etc.) the service account must **impersonate** a user via **domain-wide delegation (DWD)**. For a few APIs (Analytics, Merchant Center, Campaign Manager) you instead just **share** the resource with the service account's email — no delegation needed.
---
## **Step 1 — Create a Google Cloud project**
1. Go to the [Google Cloud Console](https://console.cloud.google.com/).
2. Create a new project (or select an existing one) using the project picker at the top.
3. Note the **Project ID** — you'll see it referenced in the key file later.
## **Step 2 — Enable the APIs you need**
1. Navigate to **APIs & Services → Library**.
2. Search for and **Enable** the API for each integration you'll use:
| **Integration** | **API to enable** |
| ------------------- | ------------------------------------- |
| Google Drive | Google Drive API |
| Gmail | Gmail API |
| Google Calendar | Google Calendar API |
| Google Sheets | Google Sheets API |
| Google Docs | Google Docs API |
| Google Slides | Google Slides API |
| Google Forms | Google Forms API |
| Google Tasks | Google Tasks API |
| Google Contacts | People API |
| Google Meet | Google Meet API + Google Calendar API |
| Workspace Directory | Admin SDK API |
If an API isn't enabled, calls will fail with a `403 ... API has not been used in project` error.
## **Step 3 — Create the service account**
1. Go to **APIs & Services → Credentials → Create credentials → Service account**.
2. Give it a name (e.g. `unified-api-sync`) and click **Create and continue**.
3. You can skip the optional "grant access" steps for now. Click **Done**.
4. You'll land on the service account list. Note its **email** — it looks like:`unified-api-sync@your-project-id.iam.gserviceaccount.com`
## **Step 4 — Generate a key**
1. Click the service account, then open the **Keys** tab.
2. **Add key → Create new key → JSON → Create.**
3. A `.json` file downloads. **Store it securely** — Google does not let you re-download it.
The file looks like this (the two fields you need are highlighted):
```json
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "abc123...",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEv...\n-----END PRIVATE KEY-----\n", ← Service Account Private Key
"client_email": "unified-api-sync@your-project-id.iam.gserviceaccount.com", ← Service Account Email
"client_id": "11223344...",
"token_uri": "https://oauth2.googleapis.com/token",
...
}
```
---
## **Step 5 — Grant the service account access to your data**
This is the step that differs by API. Pick the path that matches your integration.
### **Path A — Domain-wide delegation (Workspace user data)**
Required for **Gmail, Calendar, Drive (user files), Sheets, Docs, Slides, Forms, Tasks, Contacts, Meet, and Directory**. This lets the service account act _as_ a specific user in your Workspace.
1. On the service account's detail page, note its **Client ID** (the numeric `client_id` from the JSON, also called "Unique ID").
2. As a Workspace **super admin**, go to the [Admin Console → Security → Access and data control → API controls → Domain-wide delegation](https://admin.google.com/ac/owl/domainwidedelegation).
3. Click **Add new** and enter:
- **Client ID**: the service account's numeric Client ID.
- **OAuth scopes**: a comma-separated list of the scopes for the APIs you'll use (see the scope table below).
4. **Authorize.** Changes can take a few minutes to propagate.
> Only the scopes you authorize here will work. If you add the Drive integration later, you must come back and add the Drive scope.
### **Path B — Resource sharing (no delegation)**
For APIs where data is owned by an account/property rather than a user, you skip DWD and just grant the service account's **email** access inside the product:
- **Google Analytics** — add the service account email as a user on the GA4 property.
- **Merchant Center** — add it as a user in Merchant Center settings.
- **Campaign Manager / Display & Video 360** — add it as a user in the platform.
(These integrations aren't part of the Workspace set covered here but follow the same key-creation steps.)
---
## **Step 6 — Scopes per integration**
When configuring domain-wide delegation (Path A), authorize the matching scope(s). These are exactly the scopes Unified API requests when minting tokens:
| **Integration** | **Scope(s) to authorize** |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Google Drive | `https://www.googleapis.com/auth/drive` |
| Gmail | `https://mail.google.com/` |
| Google Calendar | `https://www.googleapis.com/auth/calendar` |
| Google Sheets | `https://www.googleapis.com/auth/spreadsheets` |
| Google Docs | `https://www.googleapis.com/auth/documents` |
| Google Slides | `https://www.googleapis.com/auth/presentations` |
| Google Forms | `https://www.googleapis.com/auth/forms.body`, `https://www.googleapis.com/auth/forms.responses.readonly` |
| Google Tasks | `https://www.googleapis.com/auth/tasks` |
| Google Contacts | `https://www.googleapis.com/auth/contacts` |
| Google Meet | `https://www.googleapis.com/auth/calendar`, `https://www.googleapis.com/auth/meetings.space.readonly` |
| Workspace Directory | `https://www.googleapis.com/auth/admin.directory.user`, `https://www.googleapis.com/auth/admin.directory.group`, `https://www.googleapis.com/auth/admin.directory.group.member` |
---
## **Step 7 — Configure the connection in Unified API**
When creating the connection, choose the **Service Account** authentication option and fill in three fields:
| **Field** | **Value** |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Service Account Email** | The `client_email` from the JSON key file. |
| **Service Account Private Key** | The `private_key` from the JSON key file — paste it whole, including the `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----` lines. |
| **Subject** | _(Optional)_ The email of the Workspace user to impersonate via domain-wide delegation. **Required for most Workspace data** (e.g. Gmail needs a mailbox owner). Leave blank only if the service account itself owns the data or you've shared resources directly with it. |
That's it. Unified API signs a short-lived JWT with your private key, exchanges it with Google for an access token (caching it until it expires), and uses it for all API calls — no further interaction needed.
---
## **Troubleshooting**
| **Error** | **Likely cause** |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `unauthorized_client` | The scope isn't authorized in domain-wide delegation, or the Client ID is wrong. Re-check Step 5A. |
| `403 ... has not been used in project` | The API isn't enabled for the project (Step 2). |
| `400 invalid_grant` / `Invalid JWT Signature` | The private key was pasted incorrectly (truncated, or newlines lost). Re-copy the full `private_key` value. |
| Empty results / `404` for a user's data | Missing or wrong **Subject** — you're querying as the service account itself instead of impersonating the user. |
| `403 Not Authorized to access this resource` (Directory) | The impersonated **Subject** must be a Workspace **admin** with rights to the directory data. |
### **Security notes**
- The private key grants standing access to your data — store it in a secret manager, never in source control.
- Authorize only the scopes you actually use; broad scopes increase blast radius if the key leaks.
- Rotate keys periodically (create a new key, update the connection, delete the old key).
## Correct WelcomeKit Scopes for Jungle Integration
URL: https://docs.unified.to/guides/correct_welcomekit_scopes_for_jungle_integration
# Correct WelcomeKit Scopes for Jungle Integration
------
_April 14, 2026_
This guide outlines the required WelcomeKit scopes per Unified object for the Jungle integration.
## Scope Mapping (by Unified Object)
| Unified Object | Required Scope(s) | Partnership (su_*) Needed? |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| **ATS · Job (**`ats_job`**)** | `su_jobs_r` | Yes |
| **ATS · Candidate (**`ats_candidate`**)** | **Read:** `candidates_r` AND `my_candidates_r` **Write:** `candidates_rw` AND `my_candidates_rw` | No |
| **ATS · Document (**`ats_document`**)** | **Read:** `documents_r` AND (`candidates_r` & `my_candidates_r`) **Write:** `documents_rw` AND (`candidates_rw` & `my_candidates_rw`) | No |
| **ATS · Activity (**`ats_activity`**)** | **Read:** `emails_r` AND (`candidates_r` & `my_candidates_r`) **Write:** `comments_w` AND (`candidates_rw` & `my_candidates_rw`) | No |
| **HRIS · Company (**`hris_company`**)** | **Read:** `su_organizations_r` | Yes |
| HRIS · Location (hris_location) | Read: `offices_r` & `su_offices_r` Write: `offices_rw` | Yes |
| HRIS · Group (hris_group) | Read: `departments_r` & `departments_rw` & `su_departments_r` Write: `departments_rw` | Yes |
---
## Important Notes
### Partnership-only scopes
- `su_jobs_r` → Required for listing jobs
- `su_organizations_r` → Required for listing organizations
- `su_offices_r` → Required for listing locations
- `su_departments_r` → Required for listing groups
These require WelcomeKit partnership approval.
### **Non-partner access (scope overrides)**
If you do **not** have WelcomeKit partnership approval, you won't be able to use the partnership-only scopes. Instead, request the standard non-partner scopes by overriding the defaults for this integration. Each Unified permission (like `ats_job_read`) maps to a default set of the provider's raw scopes, and you can replace that default per integration via the integration's scope override setting. Update the `WorkspaceIntegration` object with an `overriden_scopes` map keyed by the Unified permission, where each value is a comma-separated list of the provider's raw scopes:
`{ "overriden_scopes": { "ats_job": "jobs_r" } }`
With this override, Unified requests `jobs_r` (org-scoped jobs) instead of `su_jobs_r`. At runtime, the connector lists jobs via `GET /jobs?organization_reference=...` using the organization cached on the connection during auth setup, and falls back to `GET /users/current` for organization discovery — so non-partner keys never depend on the `su_*` scopes. For full details, see [Overriding scopes per integration](https://docs.unified.to/concepts/scopes#overriding-scopes-per-integration).
---
### Candidate-dependent scopes
The following always require candidate access:
- Documents → require candidate read/write scopes
- Emails → require candidate read scopes
- Comments → require candidate write scopes
---
## How to Get Partnership Access (Jungle / WelcomeKit)
To access partnership-only scopes (`su_*`), you will need to be approved by WelcomeKit.
### Steps:
1. **Reach out to WelcomeKit support or your account manager (**[https://help.welcometothejungle.com/en](https://help.welcometothejungle.com/en))
- Request access to partnership scopes
- Clearly mention your use case (e.g. job syncing, org-level data access)
2. **Provide integration details**
- Your application name
- What data you intend to access
- Whether access is read-only or includes write operations
3. **Explain your end-user flow**
- How users connect their WelcomeKit account
- Why elevated (partnership) access is required
4. **Await approval**
- Once approved, the scopes will be enabled for your app
- You can then include `su_*` scopes in your OAuth flow
## Creating Ads using the Unified Ads API
URL: https://docs.unified.to/guides/creating_ads_using_the_unified_ads_api
# Creating Ads using the Unified Ads API
------
_March 14, 2026_
# Unified Ads API Overview
The Unified API Ads category models advertising structures across Meta, Google, Amazon, Twitter, LinkedIn, and other providers. Understanding how objects relate helps you build the correc flows.
### Core Hierarchy
Most integrations follow a hierarchy from organization → campaign → ad group → ad:
```plain text
ads_organization (ad account)
└── ads_campaign
└── ads_group (ad set, line item, etc.)
└── ads_ad
```
| Object | Description | Parent(s) | Typical use |
| -------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| **ads_organization** | The ad account (e.g., Meta ad account, Google Ads customer). Billing and access are scoped here. | — | List accounts, select which account to use |
| **ads_campaign** | A campaign groups ad sets under a goal, budget, and schedule. | `organization_id` | Set campaign-level budget, dates, objective |
| **ads_group** | An ad set or line item. Holds targeting, bid, and budget. Often called "ad set" (Meta) or "line item" (DV360). | `campaign_id`, `organization_id` | Define who sees ads (targeting), bid strategy, promoted object |
| **ads_ad** | The actual ad creative + placement. What users see. | `group_id`, `campaign_id`, `organization_id` | Creative content, destination URL, promoted entity (e.g., tweet, product) |
### Creatives and Insertion Orders
| Object | Description | Parent(s) | Notes |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------ |
| **ads_creative** | The creative asset (image, video, copy). Some providers treat it as a separate resource; others embed it in the ad. | `group_id`, `campaign_id`, `organization_id` | Meta: can be shared across ads. Amazon/Twitter: often 1:1 with ad. |
| **ads_insertionorder** | DV360-specific. Sits between campaign and line item. | `campaign_id`, `organization_id` | Similar to a purchase order for a campaign. |
### Discovery
| Object | Description | Purpose |
| --------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| **ads_target** | Targeting options (countries, regions, interests, etc.). | Look up valid IDs to use in `targeting` when creating campaigns/groups. |
| **ads_promote** | Promoted entities (pages, apps, products, tweets, ad group types). | Look up or pass IDs for `promoted` when creating groups/ads/creatives. |
### Reporting
| Object | Description | Purpose |
| -------------- | -------------------- | ---------------------------------------------------------------------- |
| **ads_report** | Performance metrics. | Fetch impressions, clicks, spend, etc. by org, campaign, group, or ad. |
### Provider Mapping
Naming varies by provider:
| Unified API | Meta Ads | Google Ads | Amazon Ads | Twitter Ads | LinkedIn |
| ---------------- | ----------- | ---------- | ---------- | -------------- | ----------------- |
| ads_organization | Ad Account | Customer | Profile | Account | Sponsored Account |
| ads_campaign | Campaign | Campaign | Campaign | Campaign | Campaign Group |
| ads_group | Ad Set | Ad Group | Ad Group | Line Item | Campaign |
| ads_ad | Ad | Ad | Product Ad | Promoted Tweet | Creative |
| ads_creative | Ad Creative | Ad | Product Ad | Promoted Tweet | Creative |
_Some integrations collapse levels (e.g., LinkedIn's "Campaign" maps to our ads_group)._
### Creating Ads: Required Parent IDs
When creating objects, you typically need parent IDs:
- **Create campaign:** `organization_id`
- **Create ad group:** `organization_id`, `campaign_id`
- **Create ad:** `organization_id`, `campaign_id`, `group_id`
- **Create creative:** `organization_id`, `campaign_id`, `group_id` (varies by provider)
The `promoted` field is required when creating ad groups or ads that need a promoted entity (e.g., Meta ad set with a page, Twitter promoted tweet with a tweet ID). Use `ads_promote` to discover options, or pass IDs manually when the provider has no list API.
---
# Promoted Explained
The new `promoted` field specifies promoted entities (pages, apps, products, tweets, ad group types) when creating and managing ads across the Unified API.
Ads, Campaigns, and AdGroups have a `promoted` field to specify an ID of an entity to promote. There is also a `ads_promote` endpoint that provides a clean way to query the promoted entities.
```json
{
"organization_id": "act_123",
"campaign_id": "456",
"name": "My Ad Group",
"promoted": [
{ "id": "789", "type": "PAGE_ID" }
]
}
```
---
## The AdsPromoted Object
Each promoted entity has a simple structure:
| Field | Type | Required | Description |
| ------ | ------ | -------- | ----------------------------------------------- |
| `id` | string | Yes | The entity ID (e.g., page ID, ASIN, tweet ID) |
| `name` | string | No | Display name (optional, used in list responses) |
| `type` | string | Yes | One of the supported promoted types (see below) |
### Supported Promoted Types
| Type | Integration(s) | Description |
| --------------- | -------------------- | ----------------------------------------------------------------- |
| `PAGE_ID` | Meta Ads | Facebook Page to promote |
| `APP_ID` | Meta Ads | App to promote |
| `PIXEL_ID` | Meta Ads | Conversion pixel |
| `CATALOG_ID` | Meta Ads | Product catalog |
| `STORE_URL` | Meta Ads | Store URL (manual input) |
| `PRODUCT_ID` | Amazon Ads | Product ASIN (manual input) |
| `TWEET_ID` | Twitter Ads | Tweet to promote |
| `AD_GROUP_TYPE` | LinkedIn, Google Ads | Campaign/ad group type (e.g., SPONSORED_UPDATES, SEARCH_STANDARD) |
---
## GET /ads/{connection_id}/promoted Endpoint
Use the `ads_promote` list endpoint to discover available promoted entities that the integration supports.
**Endpoint:** `GET /ads/{connection_id}/promote`
**Query parameters:**
| Parameter | Type | Description |
| --------- | ------ | ----------------------------------------------------- |
| `type` | string | Filter by promoted type (e.g., `PAGE_ID`, `TWEET_ID`) |
| `org_id` | string | Organization/account I |
| `query` | string | Optional search filter |
| `limit` | number | Max results (default 100) |
| `offset` | number | Pagination offset |
### Example: List Facebook Pages (Meta Ads)
```bash
GET /ads/{connection_id}/promote?type=PAGE_ID
```
Response:
```json
[
{ "id": "123456789", "name": "My Business Page", "type": "PAGE_ID" },
{ "id": "987654321", "name": "Another Page", "type": "PAGE_ID" }
]
```
### Example: List User Tweets (Twitter Ads)
```bash
GET /ads/{connection_id}/promote?type=TWEET_ID
```
### Example: List Ad Group Types (Google Ads, LinkedIn)
```bash
GET /ads/{connection_id}/promote?type=AD_GROUP_TYPE
```
Some integrations will return static options (e.g., `SEARCH_STANDARD`, `DISPLAY_STANDARD` for Google; `SPONSORED_UPDATES`, `TEXT_ADS` for LinkedIn).
### When ads_promoted Returns Empty
Some promoted types have no discovery API (e.g., Amazon `PRODUCT_ID`, Meta `STORE_URL`). In those cases, `ads_promote` returns an empty array. You can still create ads by passing `promoted` manually with IDs you already know (e.g., ASINs for Amazon, store URLs for Meta).
---
## Quick Reference: Create Examples
### Meta Ads – Create Ad Group with Page
```json
POST /ads/{connection_id}/group
{
"organization_id": "act_123",
"campaign_id": "456",
"name": "My Ad Group",
"promoted": [{ "id": "789", "type": "PAGE_ID" }]
}
```
### Amazon Ads – Create Product Ad
```json
POST /ads/{connection_id}/ad
{
"organization_id": "profile_123",
"campaign_id": "456",
"group_id": "789",
"name": "Product Ad",
"promoted": [{ "id": "B08N5WRWNW", "type": "PRODUCT_ID" }]
}
```
### Twitter Ads – Create Promoted Tweet
```json
POST /ads/{connection_id}/ad
{
"organization_id": "account_123",
"group_id": "line_item_456",
"name": "Promoted Tweet",
"promoted": [{ "id": "1234567890", "type": "TWEET_ID" }]
}
```
### LinkedIn – Create Ad Group (Campaign)
```json
POST /ads/{connection_id}/group
{
"organization_id": "123",
"campaign_id": "456",
"name": "Sponsored Updates Campaign",
"promoted": [{ "id": "SPONSORED_UPDATES", "type": "AD_GROUP_TYPE" }]
}
```
### Google Ads – Create Ad Group
```json
POST /ads/{connection_id}/group
{
"organization_id": "123",
"campaign_id": "456",
"name": "Search Ad Group",
"promoted": [{ "id": "SEARCH_STANDARD", "type": "AD_GROUP_TYPE" }]
}
```
---
## Using Targeting
Targeting lets you define who sees your ads (geography, demographics, interests, etc.). Use the `ads_target` endpoint to look up valid targeting IDs, then pass them in the `targeting` field when creating campaigns, ad groups, or ads.
### The ads_target Endpoint
Look up targeting options (countries, regions, cities, interests, etc.) before creating ad objects.
**Endpoint:** `GET /ads/{connection_id}/target`
**Query parameters:**
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------------- |
| `type` | string | Yes | Targeting type (see supported types below) |
| `query` | string | Yes* | Search term (*required for most types) |
| `org_id` | string | No | Organization/account ID (required for some types) |
| `limit` | number | No | Max results (default 100) |
| `offset` | number | No | Pagination offset |
**Supported** **`type`** **values:** `countries`, `regions`, `cities`, `zips`, `us_dmas`, `locales`, `interests`, `behaviors`, `topics`, `user_lists`, `age_ranges`, `genders`
_Support varies by integration. Meta Ads supports most types; others (e.g., Amazon, Twitter) may support only_ _`countries`_ _and_ _`locales`__._
### Example: Look Up Countries
```bash
GET /ads/{connection_id}/target?type=countries&query=united
```
Response:
```json
[
{ "id": "US", "name": "United States", "value": "US", "type": "countries" },
{ "id": "GB", "name": "United Kingdom", "value": "GB", "type": "countries" }
]
```
### Example: Look Up Regions (e.g., US States)
```bash
GET /ads/{connection_id}/target?type=regions&query=california
```
Response:
```json
[
{ "id": "3843", "name": "California", "value": "3843", "type": "regions" }
]
```
### Example: Look Up Interests (Meta Ads)
```bash
GET /ads/{connection_id}/target?type=interests&query=technology
```
### Using Targeting IDs When Creating Ads
Pass the `id` (or `value`) from `ads_target` responses into the `targeting` object. The structure matches the targeting type:
- **Countries:** `targeting.geographic.countries` — array of ISO country codes (e.g., `["US", "CA"]`)
- **Regions:** `targeting.geographic.regions` — array of `{ id, name? }` (use `id` from ads_target)
- **Cities:** `targeting.geographic.cities` — array of `{ id, name?, radius?, radius_unit? }`
- **Demographics:** `targeting.demographic` — age and gender (no lookup needed; use values directly)
---
## Targeting Examples
### Geo-Targeting: Countries and Regions
First, look up IDs:
```bash
# Get country codes
GET /ads/{connection_id}/target?type=countries&query=united
# Get regions (e.g., states) — for regions, query often includes country context
GET /ads/{connection_id}/target?type=regions&query=california
```
Then create an ad group with geographic targeting:
```json
POST /ads/{connection_id}/group
{
"organization_id": "act_123",
"campaign_id": "456",
"name": "US & California Ad Group",
"promoted": [{ "id": "789", "type": "PAGE_ID" }],
"targeting": {
"geographic": {
"countries": ["US"],
"regions": [
{ "id": "3843", "name": "California" }
]
}
}
}
```
### Geo-Targeting: Cities with Radius
```json
{
"targeting": {
"geographic": {
"countries": ["US"],
"cities": [
{
"id": "2420379",
"name": "San Francisco",
"radius": 25,
"radius_unit": "MILES"
}
]
}
}
}
```
### Demographic Targeting
Demographic targeting uses simple values; no `ads_target` lookup is needed:
```json
{
"targeting": {
"demographic": {
"age_min": 25,
"age_max": 54,
"male": true,
"female": true
}
}
}
```
- `age_min` / `age_max`: 18–65 typically
- `male` / `female`: `true` to include, `false` or omit to exclude
### Combined Geo + Demographic Targeting
```json
POST /ads/{connection_id}/group
{
"organization_id": "act_123",
"campaign_id": "456",
"name": "US Adults 25-54",
"promoted": [{ "id": "789", "type": "PAGE_ID" }],
"targeting": {
"geographic": {
"countries": ["US"]
},
"demographic": {
"age_min": 25,
"age_max": 54,
"male": true,
"female": true
}
}
}
```
### Audience Targeting (Interests, Custom Audiences)
For interests and custom audiences, look up IDs first:
```bash
GET /ads/{connection_id}/target?type=interests&query=technology
GET /ads/{connection_id}/target?type=user_lists&query=my&org_id=act_123
```
Then use the `id` values in `targeting.audience`:
```json
{
"targeting": {
"audience": {
"interests": [
{ "id": "600313926646746", "name": "Technology" }
],
"custom_audiences": [
{ "id": "12345678", "name": "My Custom Audience" }
]
}
}
}
```
---
## Creating Walmart Items via Unified
URL: https://docs.unified.to/guides/creating_walmart_items_via_unified
# Creating Walmart Items via Unified
------
_July 31, 2025_
Unified allows you to create structured product listings on **Walmart Marketplace** via our Unified data model `commerce_item`. This guide explains the **critical fields** you must understand and provide correctly:
---
### 🧩 Key Concepts
| Field | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | This specifies the **Walmart subcategory** your item belongs to. It determines what metadata is required. Must be one of the supported 76 categories (see below). |
| `global_code` | This is your product's **GTIN** (Global Trade Item Number), a required identifier for Walmart. It must be a **valid 12–14 digit string**. |
| `metadata` | A dynamic array of key-value attributes that depends on the selected `type`. This is how you provide structured product details like color, material, warranty, etc. |
---
### 🧠 How Metadata Works
Each `type` (subcategory) has a corresponding schema of fields allowed by Walmart. For example:
- `animal_accessories` allows fields like `animalType`, `petSize`, `isFoldable`, `colorCategory`, etc.
- `computers` allows fields like `processorType`, `screenSize`, `ramMemory`, `operatingSystem`, etc.
**List the metadata fields from Unified** using this API
```typescript
GET /metadata_metadata?type=commerce_item
[
{
"id": "shortDescription",
"name": "shortDescription",
"slug": "shortDescription",
"format": "TEXT",
"original_format": "string",
"object_type": "commerce_item"
},
{
"id": "minimumRecommendedAge",
"name": "minimumRecommendedAge",
"slug": "minimumRecommendedAge",
"format": "MEASUREMENT",
"original_format": "{\"measure\":\"number\",\"unit\":[\"months\",\"years\"]}",
"object_type": "commerce_item"
},
{
"id": "smallPartsWarnings",
"name": "Small Parts Warning Code",
"slug": "smallPartsWarnings",
"format": "MULTIPLE_SELECT",
"original_format": "string[]",
"options": [
"0 - No warning applicable",
"5 - Choking hazard is a marble",
"4 - Choking hazard balloon",
"6 - Choking hazard contains a marble",
"2 - Choking hazard contains small ball",
"3 - Choking hazard contains small parts",
"1 - Choking hazard is a small ball"
],
"object_type": "commerce_item"
},
]
```
You can filter these results by inspecting the supported pagination options.
---
### Supported `type` Values for Walmart Items (commerce_item)
- `Skateboard Risers`
- `Baby Play Yards`
- `Power Hedge Trimmers`
- `Eyeglass Cases`
- `Microphone Splitters`
- `Electric Buffet Servers`
- `Radio Control Vehicle Servos`
- `Sticky Notes`
- `Matcha Tea Bowls & Whisks`
- `Doughs`
- `Taffy Candy`
- `Hair Dryers`
- `Base Layers`
- `Emergency Lighting`
- `Bidet Toilet Seats`
- `Purse Making Supplies`
- `Grill Grid Lifters`
- `Hunting Scents`
- `Automotive Headlight Restorer`
- `Ear Thermometer Replacement Lens Filters`
- `Glassware & Drinkware`
- `Precious Metals`
- `Lab Chromatography Paper`
- `Plant Terrariums`
- `Microphone Cables`
- `Glucose Supplements`
- `Snow Skis`
- `Perineal Cleansers`
- `Soda Maker Kits`
- `Camera Accessory Bundles`
- `Tetherball Equipment`
- `Paint Reducers & Retarders`
- `Pork Rinds`
- `Portable Basketball Systems`
- `Plate Joiners`
- `Other Weight Loss Supplies`
- `Powder Candy`
- `Outdoor Kitchen Serving & Storage Carts`
- `Unitards & Leotards`
- `Lab Sample Bags`
- `Fishing Rod & Reel Combos`
- `Adhesive Tapes`
- `Track & Field Markers`
- `Fruit & Vegetable Corers`
- `Automotive Fuel Tank Caps`
- `Drawing Boards`
- `Hand Dryers`
- `Pregnancy Tests`
- `Cat Litter Mats`
- `Facial Wipes`
- `Exercise Weights`
- `Sewing Pins`
- `Other Makeup Brushes & Tools`
- `Other Shaving Supplies`
- `Sugar Packet Holders`
- `Honey Dippers`
- `Artificial Topiaries`
- `Cupcake Making Kits`
- `Mobility Walker Bags, Baskets & Carriers`
- `Faucet Water Filters`
- `Urinary Tract Infection Test Strips`
- `Baptism Gowns & Outfits`
- `Function Generators`
- `FM Transmitters`
- `Home Brewing Refractometers`
- `Saddle Bags`
- `Track & Field Competitor Numbers`
- `Fuel Distributors`
- `Nicotine Patches`
- `Griddles & Grill Pans`
- `Planter & Stand Sets`
- `Other Pet Health Supplies`
- `Scooter Decks`
- `Knitting & Crochet Stitch Counters`
- `Camera Tool Kits`
- `Eyelash Combs`
- `Blouses & Tops`
- `Knitting & Crochet Stitch Markers`
- `Hand Tool Punches`
- `Climbing Rappel Devices`
- `Kitchen Towels`
- `Homogenizer Accessories`
- `Attendance Time Cards`
- `Digital Camera Parts`
- `Planners & Appointment Book Refills`
- `Herbal Supplements`
- `Hand-Sewing Needles`
- `Weight Bars`
- `Photography Fixers`
- `Massage Table Carrying Cases`
- `Grill Covers`
- `Other Skating Supplies`
- `Maple Syrups`
- `Pizza Stones & Pans`
- `Manual Letter Openers`
- `Water Guns`
- `Manual Can Openers`
- `Electronic Touchpads`
- `String Trimmer Replacement Parts`
- `Baby Bodysuits & One-Pieces`
- `Walking Cane & Crutch Tips`
- `Cutting Mats`
- `Throwing Knives`
- `Skins for Tablets`
- `Serving Utensils & Sets`
- `Photographic Art`
- `Soap Making Kits`
- `Athletic Shirts & Tops`
- `Ticket Rolls`
- `Fireplace Bellows`
- `Travel Plug Adapters`
- `Vacuum Sealers`
- `Small Animal Habitat Decor`
- `Vacuum Tubes`
- `Stroller Connectors`
- `Ceiling Fans`
- `Other Dancing Supplies`
- `Model Train Locomotives`
- `Electronic Cigarette Starter Kits`
- `Bicycle Baskets`
- `Golf Visors`
- `Bicycle Storage Hooks`
- `Handheld Video Games`
- `Cocktail Mixers`
- `Follow-Focus Levers`
- `Embossing Tools`
- `Incentive Charts`
- `Pressure Gauges`
- `Art Paints & Pigment Powders`
- `Replacement Water Filters`
- `Moisture Meters`
- `Other Pet Grooming Supplies`
- `Shopping Cart & High Chair Covers`
- `Nursing Pillows`
- `Disposable Cutlery Sets`
- `Heat Guns`
- `Construction Flashing`
- `Automotive Aerodynamics Kits`
- `Fire Retardants`
- `Gun Snakes`
- `Snow Removal Rakes & Hand Pushers`
- `Costume Wigs`
- `Outdoor Decorative Stones`
- `Lip Stains`
- `Workwear Uniform Badges`
- `Gut Health Tests`
- `Carbon Monoxide Alarms`
- `Cable Covers`
- `Lathe Cylindrical Rollers`
- `Hamburger Patty Makers`
- `Printing Press Accessories`
- `Automotive Light Covers & Guards`
- `Overbed Tables`
- `Artwork Cases & Portfolios`
- `Disposable Storage Bags`
- `Football Helmets`
- `Automatic Transmission Filters`
- `Live Trees`
- `Umpire Leg Guards`
- `Line Conditioners`
- `Cup & Saucer Sets`
- `Rain Barrels`
- `S`
- `,`
- `s Tapes`
- `Indoor Fireplaces`
- `Headphone Cases`
- `Battery Testers`
- `Floor Hockey Carts`
- `T-Shirts`
- `Dunun Drums`
- `Speed Reducers`
- `Percussion Mallets`
- `Pallet Covers`
- `Telescope Cases`
- `Automotive Grilles`
- `Fencing Sport Epee Parts`
- `Emergency Food Kits`
- `Sports Corner Flags`
- `Bird Nests & Nesting Material`
- `Boxing Rings`
- `Paper Sorters`
- `Lollipop & Treat Sticks`
- `Cabin Air Filters`
- `Pool Covers`
- `Video Game Dance Pads`
- `Football Sleds & Chutes`
- `Clay & Dough Extruders & Presses`
- `Baseball Gloves & Mitts`
- `Yeasts & Leaveners`
- `Skins for Aerial Drones`
- `Replacement Faucet Water Filters`
- `Other Electronic Components & Accessories`
- `Lab Coats`
- `Laser Detectors`
- `Baseball Sliding Shorts`
- `Hunting Game Finders`
- `Rain & Snow Gauges`
- `Shapewear Bodysuits`
- `Toiletry Kits & Bags`
- `Photo Enlarger Heads`
- `Athletic Outfit Sets`
- `Combustion Analyzers`
- `Personal Organizer Refills & Accessories`
- `Harmonic Exciters`
- `Billiard Table Felt`
- `Video Game Console Parts`
- `Ham Radio Transceivers`
- `Baby Juices`
- `Hot Dog Machines`
- `Pipe Inspection Cameras`
- `Misting Systems`
- `Sleeping Bag Liners`
- `Kids Gardening Tools`
- `Camera Film Winders`
- `Other Billiard Supplies`
- `Ergonomic Backrests`
- `Balloon Pumps`
- `Lump Charcoals`
- `Pilates Barrels`
- `Surfing Leashes`
- `Dart Shafts`
- `Makeup Blotting Paper`
- `Eye Masks`
- `Welding Helmets`
- `Electronic Coils`
- `Energy Drinks`
- `Push-Up Stands`
- `Photography Stop Baths`
- `Handbag Hook Hangers`
- `Network Security Devices`
- `Stitch Holders`
- `Bicycle Pegs`
- `Outdoor Daybeds`
- `Party Favors`
- `Canning Kits`
- `Molding & Casting Materials & Kits`
- `Musical Instrument Bodies`
- `Other Games`
- `Electrolyte Solutions`
- `Fruit & Wine Crushers & Destemmers`
- `Power Inverters`
- `Jewelry Jump Rings & Split Rings`
- `Table Tennis Paddles`
- `SIM Card Backup Devices`
- `Tie Pins`
- `Billiard Tables`
- `Archery Broadheads`
- `Vehicle Windshields`
- `Jock Straps`
- `Pet Live Animals`
- `DC-to-DC Converters`
- `Transport Drums`
- `Vinyl Figures`
- `Entertainment Centers`
- `Fireplace Tools`
- `Serving Forks`
- `Nut Splitters`
- `Blasters & Foam Play`
- `Garden Border Edging`
- `Gun Holsters & Holders`
- `Spice Racks & Organizers`
- `Stamps & Stamp Sets`
- `Udu Drums`
- `Reception Chairs & Seating`
- `Pepper & Peppercorns`
- `Golf Drivers`
- `CC Cream`
- `Yoga Socks`
- `Vacuum Cleaner Attachments`
- `Planter Liners`
- `Pie & Pastry Fillings`
- `Bed Skirts`
- `Dart Carrying Cases & Wallets`
- `Pizza Ovens`
- `Crepe Spreaders`
- `Hex Tools`
- `Stretch Film & Shrink Wrap`
- `Massage Headrests`
- `Electronics Carrying Cases`
- `Dust Collectors`
- `Decorative Stones`
- `Yoga Straps`
- `Espresso Machines`
- `Lab Face Masks & Shields`
- `Guitar Effects Pedals`
- `Hockey Hip Pads`
- `Pressure Cookers & Canners`
- `Diabetic Foot Care`
- `Nail Top Coat`
- `Wax Molding Materials`
- `Dock Guards`
- `Decoupage Tools & Sets`
- `Rugby Balls`
- `Bra Back Adapters`
- `Other Backyard Wildlife Supplies`
- `Food Peelers`
- `Modular Synthesizers`
- `Mirror Balls`
- `Cricket Wickets`
- `Snatch Rigging Blocks`
- `Rowing Shells`
- `Staple Gun Staples`
- `Golf Rangefinders`
- `Waterbed Liners`
- `Beer Glasses`
- `KVM Switches`
- `Panel Carriers`
- `Sanding Frames`
- `First Aid Tape`
- `Hand Pruners & Loppers`
- `Reusable Training Pants`
- `Outdoor Spas`
- `Air Hockey Pucks`
- `Cat Trees`
- `Surgical Gowns`
- `Baseball Protective Screens`
- `Artificial Plants`
- `Basting Brushes`
- `Archery Arrows`
- `Calibration Weights`
- `Laptop Computers`
- `Chalkboard Chalk`
- `Nasal Aspirators`
- `Instant Coffee`
- `Vanity Tables & Table & Bench Sets`
- `Printer Drums`
- `Automotive Body Paint`
- `Paraffin Treatment Hand & Foot Mitts`
- `Stovetop Espresso Pots`
- `Robotic Lawn Mowers`
- `Phone Grips`
- `Hammock Chairs`
- `Home Brewing Kegs & Keg Supplies`
- `Dishwasher Rack Adjusters`
- `Sculpting Tools & Kits`
- `Mattocks & Pickaxes`
- `Watch & Clock Hands`
- `Umpire Bags`
- `Other Lighting Accessories`
- `Lab Shaker Accessories`
- `Print Servers`
- `Home Brewing Beer Bottle Trees`
- `Football Hand Warmers`
- `Spectrum Analyzers`
- `Hair Perms & Texturizers`
- `Lab Glassware Washers`
- `Trailer Weight Distributing Systems`
- `Digital Cameras`
- `Drum & Pail Mixers`
- `Porch Swings`
- `Gun Choke Tubes`
- `Pet Milk Replacers`
- `Fabric Paints`
- `Other Kiteboarding Accessories`
- `Paper Cups`
- `Bottle Pourers`
- `Golf Divot Tools`
- `Aquarium Cleaning Tools`
- `Fishing Vest & Packs`
- `Die-Cut Cartridges`
- `Kiteboard Control Bars`
- `Welding Tips`
- `Food Tongs`
- `Corn & Callus Remover Cushions`
- `Cocktail Shakers`
- `Inspection Mirrors`
- `Fruit Snacks`
- `Kayak Storage Racks`
- `Cycling Vests`
- `Field Hockey Goggles`
- `Breathalyzer Alcohol Monitors`
- `Dry Erase Markers`
- `Racquet Sport Stringing Machines`
- `Gun Swivels`
- `Power Tool Chargers`
- `Home Brewing Sanitization Kits`
- `Ice Cream, Sorbet & Frozen Yogurt`
- `Underwater Cameras`
- `Hair Color Stain Shields`
- `Roof Gutter Accessories`
- `Bicycle Brake Rotors`
- `Repinique Drums`
- `Camera Film Backs & Holders`
- `Automotive Cigarette Lighters & Plugs`
- `Meal Kits`
- `Lathe Cole Jaws`
- `PBX Phone Systems`
- `Face Serums`
- `Exercise Treadmills`
- `Lawn Sweepers`
- `Sunglasses Replacement Lenses`
- `Pipe Fitting Reducers`
- `Microphone Pop Filters`
- `MiniDisc Players`
- `Audio/Video Cables`
- `Hand Sanitizers`
- `Nail Pullers`
- `Traditional Salwar Kameez`
- `Other Air Tool Accessories`
- `Cymbal Pads`
- `Automotive Winch Mounting Systems`
- `Laptop Bags`
- `Contour Makeup Set`
- `Puzzle Cubes`
- `Plant Covers`
- `Empty Paint Cans`
- `Tableware Knives`
- `Storage Sheds`
- `Window & Door Frames`
- `Paper Plates`
- `Calculator Accessories`
- `Shaving Sets`
- `Microphone Preamplifiers`
- `Baby Feeding Chairs`
- `Rain Ponchos`
- `Aquarium Thermometers`
- `Mini Helmets`
- `RV Awnings`
- `Lawn & Garden Sprayers`
- `Car CD Changers`
- `Selfie Sticks`
- `Calibration Standard Rods`
- `Lab Chromatography TLC Developing Tanks`
- `Poultry Treats`
- `Mechanical Pencil Refills`
- `Dip Stands`
- `Drinkware Accessories`
- `Artist Manikins`
- `Leathercraft Kits`
- `Metalworking Spring Plungers`
- `Marquee Signs`
- `Craft Dyes`
- `Wakesurf Boards`
- `Pool Safety Supplies`
- `Pedal Boats`
- `Snack Crackers`
- `Jewelry Cleaning Machines`
- `Party Supply Sets`
- `Other Decking & Fencing Hardware`
- `Motorcycle Glasses`
- `Art Smocks`
- `Archery Nocks`
- `Diving Regulators`
- `Camera Flash Snoots`
- `Martial Arts Weapon Stands`
- `Automotive Wiper Nozzles`
- `Tea Cozies`
- `Stadium Seats & Cushions`
- `Bear Protection`
- `Flagpole Brackets & Mounts`
- `Patio Umbrella Base Weights`
- `Stretch Film & Shrink Wrap Dispensers`
- `Punching Bag Hangers`
- `Track Lighting Sets`
- `Wheel Chocks & Stops`
- `Itching & Rash Treatments`
- `Automotive Rims`
- `Tire Pressure Gauges`
- `Hair Crimping Irons`
- `Baker`
- `s Helmets`
- `Curtains & Valances`
- `Aquarium Starter Kits`
- `Body Muds`
- `Body Piercing Retainers`
- `Hockey Chest Protectors`
- `Diving Boards`
- `Small Engines`
- `Pet Sofa & Chair Protectors`
- `Tent Accessories`
- `Pedal Covers`
- `Appliance Covers`
- `Vegetable Dips`
- `Hockey Shin Pads`
- `Acne Creams`
- `Mobility Aid Steps`
- `Quilting Frames`
- `Water Heaters`
- `Sound Pressure Level Meters`
- `Glider Rocking Chairs`
- `Punching Bags`
- `Nail Ridge Filler`
- `Yoga Gloves`
- `Jewelry Stringing Materials`
- `Dishwasher Rack Rollers`
- `Other Hair Removal Supplies`
- `Agility Ladders`
- `Watch Winders`
- `Blender Pitchers`
- `Hand Tampers`
- `Dehumidifier Accessories`
- `Punch Bowl Sets`
- `Mini Projectors`
- `Drinkware Sets`
- `Evaporated Milks`
- `Spoon Rests`
- `Juggling Toys`
- `Mixed Spices & Seasonings`
- `Bicycle Child Seats`
- `Water Coolant Systems`
- `Jai Alai Sets`
- `Surveillance Camera Lenses`
- `Chain Hoists`
- `Snack Pudding & Gelatin`
- `Hair Curling Wands`
- `Spa Cover Lifts`
- `Track Lighting Heads`
- `Photo Enlargers`
- `Photo Enlarger Lenses`
- `Plant Cages`
- `DJ Mixers`
- `Condiment Servers`
- `Electric Kettles`
- `Glass Raw Materials`
- `Radio Antennas`
- `Bicycle Front & Rear Racks`
- `Body Oils`
- `ATV Tires`
- `RAM Memory`
- `Pet Toothpaste`
- `Dressing Aid Sticks`
- `Coconut Knives`
- `Sports Pole Holders & Bases`
- `Dock Steps`
- `Bath Toy Storage`
- `Martial Arts Belts`
- `Currency Bands & Straps`
- `Outdoor Playsets`
- `Video Switchers`
- `Brake System Replacement Parts & Hardware`
- `Tennis Shorts`
- `Facial Treatments`
- `Athletic Compression Socks`
- `Brake Booster Filters`
- `Other Knitting & Crochet Supplies`
- `Martini Glasses`
- `Personal Organizers`
- `pH Meters`
- `Parchment Paper`
- `Other Gift Wrapping Supplies`
- `Leathercraft Accessories`
- `Rod Hockey`
- `House Numbers & Letters`
- `Toaster Pastries`
- `Anti-Chafing Creams`
- `Sunshine Recorders`
- `Makeup Sets`
- `Baby Jumping Exercisers`
- `Replacement Appliance Knobs`
- `Hair Removal Waxing Strips`
- `Toaster Ovens`
- `Electric Crepe Makers`
- `Kiteboard Control Lines`
- `Camera Cleaning Equipment`
- `Body Piercing Jewelry`
- `Weatherproofing Pipe Coverings`
- `Plant Misters`
- `Rosin Bags`
- `Plastic Cups`
- `Baby Bouncer & Rocker Chairs`
- `Washing Machines`
- `Ground Coffee`
- `Gun Mounts`
- `Remote Controls`
- `Medicine Dosing Containers`
- `Chainsaw Chains`
- `Climbing Slings`
- `Basketball Storage`
- `Writing Notebooks & Sketch Books`
- `Thermometer Probe Covers`
- `Wall Calendars`
- `Power Tool Batteries`
- `Corded Phones`
- `Desk Lamps`
- `Network-Attached Storage Servers`
- `Slatwall Panels`
- `Golf Push & Pull Carts`
- `Media Storage Cabinets`
- `Stovetop Waffle Irons`
- `Pet Tie-Outs`
- `Toilet Paper Holders`
- `Boating Heads`
- `Movie Cameras`
- `Vanity Lights`
- `Plaques & Signs`
- `Decorative Bottles`
- `Art Paint Sponges`
- `Kayak Carts`
- `Radar Detectors`
- `Outdoor Bar Stools`
- `Piggy Banks & Money Jars`
- `Fog Machines`
- `Food Storage Jars & Containers`
- `Pet ID Tags`
- `Salad Serving Utensils`
- `Bath Rugs`
- `Water Distillers`
- `Sewing Bias Tape`
- `Racquet Vibration Dampeners`
- `Live Shrubs`
- `Cable Tie Guns`
- `Floating Shelves`
- `Empty Camping Stove Fuel Bottles`
- `Toy Billiards`
- `Water Diverters`
- `Powdered Baking Cocoa`
- `Card Shufflers`
- `Tool Sets`
- `Paper Holders & Dispensers`
- `Bed-in-a-Bag`
- `Rail Planters`
- `Glass Markers & Stemware Charms`
- `Poison Ingestion Treatments`
- `Sewing Baskets`
- `Decorative Boxes`
- `Play Tents`
- `Oke Daiko Drums`
- `One-Piece Swimsuits`
- `Bicycle Stands`
- `Photography Flash Heads`
- `Yogurt Makers`
- `Field Hockey Goalie Hand Protectors`
- `Window Shades`
- `Camera Battery Grips`
- `Hunting Game Hoists & Gambrels`
- `Sunglass Visor Clips`
- `Christmas Trees`
- `Edge & Corner Guards`
- `Golf Carts`
- `Screen Houses`
- `Tent Poles`
- `Fish Cleaning Tables`
- `Bicycle Saddle Covers`
- `Plant Seeds`
- `Geographic Globes`
- `Jewelry Making Chain`
- `Figurines & Knick-Knacks`
- `Binder Insert Strips`
- `Hair Clipper Blades`
- `Nutrition Drinks`
- `Musical Bow Cases`
- `Glue Guns`
- `Breast Tape`
- `Automotive Lift Supports`
- `Acoustic Guitars`
- `Therapeutic Light Boxes`
- `Stovetop Espresso Pot Replacement Gaskets & Filters`
- `Beverage Tubs`
- `Lifeguard Swimsuits`
- `Other Backpacking & Camping Supplies`
- `Napkin Rings`
- `Staple Removers`
- `Gravy Boats, Stands & Sets`
- `Bath Pillows`
- `Easter Eggs`
- `Cut Flowers`
- `Traditional Abayas`
- `Air Guns`
- `Electric Fence Insulators`
- `SIM Cards`
- `Oven Mitts`
- `Gun Barrels`
- `Automotive Skid Plates`
- `Sport Starter Pistols`
- `Personal Misters`
- `Telescope Eyepiece & Filter Sets`
- `Cell Phone Radiation Guards`
- `Snowmobile Riser Blocks`
- `Football Target Nets`
- `Memory Card Adapters`
- `Mobility Walkers`
- `Ice Buckets`
- `Skins for Vape Pens`
- `Electrical Ballasts`
- `Bicycle Frames`
- `Cheese Wax`
- `Whole Bean Coffee`
- `Tennis Rebounders`
- `Plaster Hawks`
- `Dance Shoes`
- `Pizza Crusts`
- `Other Fireplaces Accessories`
- `Epoxy Coatings`
- `Automotive Trailers`
- `Gift Wrap Paper`
- `Material Handling Securing Straps`
- `Disposable Cameras`
- `Small Animal Exercise Wheels`
- `Hall Trees`
- `Cash Boxes`
- `Sprinkler Valves`
- `Renewable Energy Controllers`
- `Impact Drivers`
- `Home Brewing Grain & Hop Bags`
- `ATV Cargo Storage`
- `Bicycle Derailleurs`
- `Darkroom Squeegees`
- `Lens Supports`
- `Roller Stands`
- `Breath Fresheners`
- `Leaf & Plant Cleaner`
- `Cricket Balls`
- `Automotive Bumpers`
- `Other Baby Safety Supplies`
- `Confetti Poppers`
- `Swimsuit Sets`
- `Manual Pencil Sharpeners`
- `Toilet Bowls`
- `Art & Blueprint Tubes`
- `Air Fryer Liners`
- `Other Bicycle Parts`
- `Gymnastics Hand Grips`
- `Garlic Presses`
- `GPS Software`
- `Banana Hangers`
- `Animal Grooming Scissors`
- `Dining Tables`
- `DAT Recorders`
- `Decorative Bowls`
- `Dustpans & Dustpan & Brush Sets`
- `Racquet Sport Racquets`
- `Lap Guitars`
- `Construction Siding`
- `Baking Cups`
- `Hair Extensions`
- `Starting Fluid`
- `Chemical Solvents`
- `Pet Training Pad Trays`
- `Fishing Hook Sharpeners`
- `Other Construction Heating & Cooling Supplies`
- `Air Press Coffee Makers`
- `Playground Merry-Go-Rounds`
- `Clothes Iron Rests`
- `Coffee Drippers`
- `Microscope Mechanical Stages`
- `Makeup Palettes`
- `Fish Poachers & Bakers`
- `Power Screwdrivers`
- `Other Bowling Supplies`
- `Hardware Bolts`
- `Gymnastics Rings`
- `Other Occupational Health`
- `Automotive Glow Plugs`
- `Gun Stocks`
- `Ice Fishing Spearing Equipment`
- `Light Timers`
- `Plant Pots & Planters`
- `Camera Rain Covers`
- `Table Place Cards`
- `Door Closers`
- `Commercial Refrigerators`
- `Home Brewing Beer Bottle Caps`
- `Badge & ID Holders`
- `Interdental Brushes`
- `Facial Self-Tanners`
- `Archery Finger Tabs`
- `Hand Drills`
- `Cooking Sauces & Marinades`
- `Conductor`
---
## Walmart Category Metadata Field Mapping
Use this table to understand what metadata fields are allowed for each `type` when creating items via Unified. Please note that these metadata fields are optional, while some categories do need specific metadata fields
[file](https://s3.us-east-2.amazonaws.com/unified-article-images/creating_walmart_items_via_unified-file-0-walmart_schema_fields.md)
---
### Sample Payload
```json
{
"name": "Wireless Earphones with Advanced Features",
"slug": "09876543210987",
"global_code": "06146190200012",
"type": "Exterior Automotive Accessories",
"description": "Totally wireless earphones are built to revolutionize your workouts. The adjustable, secure-fit earhooks are customizable for extended comfort and stability. A reinforced design for sweat and water resistance lets you take it to the next level. Each earbud has full volume and track controls and up to 9 hours of listening time to fuel your training with powerful, balanced sound.",
"media": [
{
"url": "https://i5.walmartimages.com/seo/onn-Wireless-Bluetooth-on-Ear-Headphones-Black-New_07de2d93-505b-4902-8366-388a3f30e3b4.9375c5690645733ec080eef3f4895f7b.jpeg"
}
],
"vendor_name": "Walmart",
"variants": [
{
"sku": "09876543210987",
"weight": 0.57,
"prices": [
{
"price": 249.99
}
]
}
],
"metadata": {
"productName": "Wireless Earphones with Advanced Features",
"brand": "Walmart",
"keyFeatures": [
"Designed to keep up with your workouts",
"Reinforced design for sweat and water resistance with adjustable, secure-fit earhooks for added comfort and stability.",
"Stay connected from a distance with Bluetooth technology, these wireless earphones deliver extended range and fewer dropouts.",
"Powers through long hours of training"
],
"countPerPack": 1,
"multipackQuantity": 1,
"isProp65WarningRequired": "No",
"condition": "New",
"has_written_warranty": "Yes - Warranty Text",
"netContent": {
"productNetContentMeasure": 1,
"productNetContentUnit": "Each"
},
"productSecondaryImageURL": [
"https://i5.walmartimages.com/seo/onn-Wireless-Bluetooth-on-Ear-Headphones-Black-New_07de2d93-505b-4902-8366-388a3f30e3b4.9375c5690645733ec080eef3f4895f7b.jpeg"
],
"assembledProductLength": {
"measure": 4.33,
"unit": "in"
},
"assembledProductHeight": {
"measure": 4.33,
"unit": "in"
},
"assembledProductWeight": {
"measure": 0.57,
"unit": "lb"
},
"assembledProductWidth": {
"measure": 3.15,
"unit": "in"
},
"prop65WarningText": "No",
"manufacturer": "Walmart",
"manufacturerPartNumber": "WM123456",
"count": 1,
"warrantyText": "1 Year",
"color": "Blue",
"aaiaBrandID": "WMTB",
"vehicle_fitment_type": "Specific"
},
"options": {}
}
```
**NOTE**: If item creation **fails** or takes **too long** to process, the **`feed ID`** and any **`error details`** will be returned in the error response.
⚠️ **Important Note on Reading** **`commerce_item`** **Data via Walmart's GET and LIST APIs**
When using Walmart's **GET** or **LIST** endpoints to retrieve `commerce_item` records (i.e., your catalog items), **you will only receive a subset of the full item**. This limitation is imposed by Walmart's public Marketplace APIs and is not configurable.
### What's Missing?
- Fields defined in the **`Visible`** section of your original item feed (e.g., `animalLifestage`, `assembledProductWeight`, `features`, `fabricContent`, etc.) will **not be returned** in full.
- Walmart truncates or omits many **category-specific structured attributes**.
### Why This Happens:
Walmart's GET/LIST endpoints are designed for high-level overviews or simple listing needs, not for reconstructing detailed item payloads. They surface:
- Basic identifiers (e.g., SKU, productName)
- Core logistics info (e.g., price, inventory, brand)
- A _limited set_ of visible attributes
## End-Users, Integrations, and Connections
URL: https://docs.unified.to/guides/end_users_integrations_and_connections
# End-Users, Integrations, and Connections
------
_April 18, 2024_
This guide explains how connections work at [Unified.to](https://unified.to/) and shows you different ways to create connections for your application. You'll learn about the relationship between your end-users, connections, and integrations, as well as how to get started quickly with test connections.
## Overview
At Unified.to, there are three key concepts to understand:
1. **Integrations** - These are the bridges between your application and third-party platforms (a.k.a SaaS apps). These are pre-built for you!
2. **Connections** - These are the authorized links between your end-users and specific integrations
3. **End-users** - These are your application's users whose data you will access from their accounts i.e. HubSpots, Salesforce, Shopify, and so on.

## Understanding integrations
Your application communicates with a SaaS application through an integration.
### Activating an integration in the sandbox environment
1. Navigate to **Integrations** at [app.unified.to/integrations](https://app.unified.to/integrations)
2. Search for the integration you want to use
3. Click on the integration card to view its details
1. For sandbox environment integrations, you don't need to enter any real credentials here.
4. Click **Activate**
All data is synthetic in the sandbox environment, meaning you don't need to sign up for any third-party accounts while you're testing the Unified.to platform.
[Learn more about the sandbox environment →](https://docs.unified.to/concepts/sandbox)
### Activating an integration in production environments
To activate an integration in production environments, you'll need developer credentials from the platform you're integrating with.
### Developer credentials
When activating an integration, you'll be asked to provide credentials like API keys or OAuth 2.0 client IDs and secrets. These credentials should come from **your developer account** with the platform, not your end-users' accounts. Here's how it works:
1. You register your application with the platform (e.g., create a HubSpot developer account and register your app)
2. You receive developer credentials that identify your application
3. You enter these credentials when activating the integration on Unified.to
4. Your end-users will later authorize your application to access their accounts, with or without using their own credentials (depends on the type of auth flow)
For example, if you're building an interview scheduling platform that needs to access your clients' Zoho CRM data:
- You use **your** Zoho developer account credentials to activate the integration
- Your **end-users** will authorize access to their Zoho CRM accounts when creating connections by consenting to give you access to their data
## Understanding connections
Your end-users are connected to integrations through connections_._
A connection is an authorized link between one of your end-users and a specific integration. For example, if your application needs to access a user's HubSpot account, you'll need:
1. The HubSpot integration to be activated in your workspace
2. A connection between that specific user and HubSpot
3. The correct scope configuration for what data you need to access
Each user needs their own connection to each integration you want to use. This ensures proper authorization and data isolation between users.
### About scopes
Scopes determine what data your application can access in your end-users' accounts. You need to:
1. Configure the correct scopes in your developer account with the platform
2. Select the corresponding Unified.to scopes when setting up your integration
For example, if you need to read CRM contacts, you would request the `crm_contact_read` scope. Unified.to has its own set of scopes (called Unified scopes) which have been mapped to the corresponding scopes for every single integration that needs them. When requesting scopes, you only need to pass in Unified scopes in addition to enabling those scopes in your developer account.
[Learn more about configuring scopes →](https://docs.unified.to/concepts/scopes)
## How to create connections
There are three main ways to create connections in Unified.to, each suited for different scenarios:
### 1. Development and testing: Create test connections in the sandbox environment
This is the fastest way to get started and test integrations:
1. Switch to the **Sandbox** environment in the Unified.to app
2. Go to the **Connections** page
3. Click **Create test connection**
4. Select any activated integration you want to test
Test connections are created instantly with mock credentials, perfect for development and testing.
### 2. Production ready: Use the Authorization embedded component
This is the recommended approach for production-ready applications:
1. Navigate to the [**Embedded Authorization**](https://app.unified.to/embed) page
2. Configure your callback settings, permission scopes, and display options
3. Copy the provided code snippet for your preferred tool (React, Angular, Vue, vanilla JavaScript, or an API call)
4. Add the code to your application
5. The Authorization component will be rendered on your app. When users click an integration's logo, they'll be guided through the authorization process
Example code for React:
```javascript
import UnifiedDirectory from '@unified-api/react-directory';
function App() {
return (
);
}
```
### 3. Custom implementation: Build your own authorization flow
For greater control over the user experience:
1. Fetch available integrations using the Unified API
2. Create authorization URLs for selected integrations
3. Handle authorization callbacks
4. Manage connection IDs
This approach requires slightly more implementation work but offers more flexibility. You can find a [complete tutorial for this approach in our documentation](https://docs.unified.to/tutorials/customize-auth-flow).
## Best practices
- **Always test in Sandbox first**: Use test connections to validate your integration logic
- **Store connection IDs securely**: Never store connection IDs in client-side storage in production
- **Handle authorization failures gracefully**: Provide clear feedback when connection attempts fail. Users will be redirected to the failure URL you specified during configuration. [Learn more about handling failure callbacks here](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections#handle-errors-during-connection-creation).
- **Monitor connection health**: Set up [webhook notifications](https://app.unified.to/settings/workspace) for connection status changes
- **Configure scopes correctly**: Request only the permissions your application needs
- **Use separate environments**: Keep your development and production environments separate
## Troubleshooting
Common issues and solutions:
### Connection creation fails
- Verify your developer credentials are correct
- Check that required scopes are configured both in your platform developer account and Unified.to
- Ensure redirect URLs are properly set up in your platform developer settings
### Authorization flow issues
- Confirm your integration is activated
- Verify environment settings (Sandbox vs Production)
- Check that scopes match between your platform developer account and Unified.to
For more guidance, see: [How to troubleshoot unhealthy connections](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections)
## Next steps
- Try creating your first test connection in the sandbox environment
- Explore our [Quickstart guide](https://docs.unified.to/quick-start) for a hands-on example
- Read our detailed guide on [understanding and configuring scopes](https://docs.unified.to/concepts/scopes)
## Enriching Unified Objects with Custom Metadata
URL: https://docs.unified.to/guides/enriching_unified_objects_with_custom_metadata
# Enriching Unified Objects with Custom Metadata
------
_July 23, 2025_
## Overview
The metadata_metadata object is a powerful addition to the Unified data model. It enables the definition of **metadata schemas (custom fields)** that can be attached to various unified objects—such as `commerce_item`, `ats_job`, `hris_employee`, and more.
This document explains its structure, how to use it effectively, and best practices for managing dynamic metadata across models.
---
## Purpose
`metadata_metadata` serves as a **schema registry** for custom fields, allowing users to enrich objects with reusable, structured metadata. These definitions dictate the format and structure of metadata values that live inside the `metadata` field of supported Unified objects.
---
## Object Structure
```typescript
interface IMetadataMetadata {
id?: string;
created_at?: (string | Date | number);
updated_at?: (string | Date | number);
name: string; // Required
slug?: string;
format?: string; // One of the supported formats
original_format?: string;
options?: string[];
object_type: string; // e.g. 'commerce_item', 'ats_job'
objects?: { [objectType: string]: string[] }; // Map of object_type to object IDs
}
```
---
## Key Concepts
### `object_type`
This specifies the **unified model** the metadata is associated with. Examples include:
- `"commerce_item"`
- `"ats_job"`
- `"hris_employee"`
- Other Unified Objects
> When listing metadata_metadata entries, you can filter by **type**, which corresponds to the object_type.
---
### `format`
Defines the data type for the metadata field. Supported formats include:
- `'TEXT'`
- `'NUMBER'`
- `'DATE'`
- `'BOOLEAN'`
- `'FILE'`
- `'TEXTAREA'`
- `'SINGLE_SELECT'`
- `'MULTIPLE_SELECT'`
- `'MEASUREMENT'`
- `'PRICE'`
- `'YES_NO'`
- `'CURRENCY'`
- `'URL'`
---
### `objects`
A mapping of object types to the specific object IDs where this metadata is currently applied.
**Example:**
```json
{
"objects": {
"commerce_item": ["item1", "item2"],
"ats_job": ["job1"]
}
}
```
This makes it easy to track which unified records are using each metadata definition.
---
## Usage in Unified Objects
Each unified object includes a `metadata` array, where each entry corresponds to a metadata definition from the `metadata_metadata` registry.
### Example: `IAtsJob`
```javascript
interface IAtsMetadata {
id?: string;
slug?: string;
value?: unknown;
namespace?: string;
format?: string;
extra_data?: unknown;
}
interface IAtsJob {
...
metadata?: IAtsMetadata[];
...
}
```
---
## Workflow Example
### Step 1: Create a `metadata_metadata` Definition
To begin, define the metadata schema by providing **only** the following required fields:
- `name`: Human-friendly label
- `slug`: Unique programmatic identifier (Picked up from name if not given)
- `format`: Expected data format (e.g., `"TEXT"`, `"NUMBER"`, etc.)
```json
POST /metadata_metadata
{
"name": "Binding Mount",
"slug": "binding_mount",
"format": "TEXT"
}
```
---
### Step 2: Review the Created Metadata
Once the metadata is created, the full record might look like this (after automatic enrichment):
```json
GET /metadata_metadata/1
{
"id": "1",
"name": "Binding Mount",
"slug": "binding_mount",
"format": "TEXT",
"original_format": "single_line_text_field"
}
```
---
### Step 3: Attach Metadata to a Unified Object
```json
PATCH /commerce_item/7
{
"metadata": [
{
"id": "1",
"slug": "binding_mount",
"value": "Optimistic"
}
]
}
```
Make sure the id / slug match the schema created in Step 1.
### Step 4: Review the Metadata Again
Once the metadata is used in a Unified object, the full record might be updated to look like this:
```json
GET /metadata_metadata/1
{
"id": "1",
"name": "Binding Mount",
"slug": "binding_mount",
"format": "TEXT",
"original_format": "single_line_text_field",
"object_type": "commerce_item",
"objects": {
"commerce_item": ["7"]
}
}
```
This shows the metadata definition has been associated with the unified object type `commerce_item` and is linked to object with ID `7`.
---
## CRUDL Support
Full **Create, Read, Update, Delete, and List** operations are supported on `metadata_metadata`. This enables:
- Schema-driven form builders
- Reusable definitions across multiple objects
- Consistent data validation and audit trails
---
## Use Cases
| Use Case | Example |
| --------------------------------- | -------------------------------------- |
| Custom field on ATS Job | `"interview_style": "technical panel"` |
| Enrich commerce item with specs | `"binding_mount": "Optimistic"` |
| Add legal identifiers to employee | `"national_id": "IN123456"` |
## Extended observability — pushing API logs to Grafana / Loki
URL: https://docs.unified.to/guides/extended_observability_pushing_api_logs_to_grafana_loki
# Extended observability — pushing API logs to Grafana / Loki
------
_April 27, 2023_
We're excited to add another option for external observability: you can send **API call logs** from [Unified.to](https://unified.to/) directly to a **Grafana Loki**–compatible endpoint. directly to a **Grafana Loki**–compatible endpoint. This is the same class of logs we already support for [Datadog](https://docs.unified.to/guides/extended_observability_pushing_api_logs_to_your_datadog_instance); you choose where they go in your [workspace settings](https://app.unified.to/settings/workspace).
[**Grafana Cloud**](https://grafana.com/products/cloud/) and [**Grafana Loki**](https://grafana.com/oss/loki/) are commonly used for log aggregation and querying. Unified delivers logs using Loki's standard HTTP push API.
Setting it up is easy! Here's how:
- **Prepare your endpoint and credentials**
- In [**Grafana Cloud**](https://grafana.com/docs/grafana-cloud/), open your stack and note the **logs / Loki** ingest base URL (no path) — often `https://logs-prod-XX.grafana.net` (your region may differ). If you use **self-hosted Loki**, use that base URL instead.
- Create or copy a **logs token** (or the username + token pair your provider documents for Basic auth). **Set** **`grafana_site`** **explicitly** for EU/regional Grafana Cloud stacks or self-hosted Loki; if you leave it blank, our API falls back to a default US Grafana Cloud logs host, which may not match your region.
- **Enter values in workspace settings**
- Open [Workspace settings](https://app.unified.to/settings/workspace) (or use the Workspace API) and choose **Grafana** under external logging. Configure:
- **Loki base URL** (`grafana_site`): Base URL only (for example [https://logs-prod-XX.grafana.net](https://logs-prod-xx.grafana.net/))
- **Authentication: Basic** (Grafana Cloud: user/instance id + token), **Bearer** (if your gateway expects a bearer token), or **Legacy** (single field — see below).
- **Token / API key** (`grafana_apikey`): Your secret or legacy `user:token` string, depending on mode.
- **Confirm logs are flowing**
- Make a sample call to the Unified API (for example, list an object on a connection). In **Grafana Explore** (or any Loki client), run LogQL such as `{job="unified-api"}`. Log lines include JSON with the route **`path`**, `integration_type`, `status`, and other API-call fields.
## Why does Grafana / Loki matter for [Unified.to](https://unified.to/) users?
By sending your logs directly to your own Loki stack or Grafana Cloud, you get immediate visibility into what's happening in your environment. This allows you to quickly debug issues, get notified about errors, and more.
Currently, we support both inbound API calls and webhook calls.
However, we're constantly improving our product, and we're looking forward to supporting more log types in the future. So, please [send us feedback](https://unified.to/contact) so we can better understand what you need.
## About Unified.to
If you're not familiar with Unified API, it's a single API that you can use to read and write data to third-party integrations using our unified data model. This makes it easy to integrate with multiple systems and eliminates the need for separate API calls for each integration.
We hope this new feature makes your life easier and your work more efficient!
[Get started with Unified.to](https://app.unified.to/)
## Extended Observability - Pushing API Logs to your Datadog Instance
URL: https://docs.unified.to/guides/extended_observability_pushing_api_logs_to_your_datadog_instance
# Extended Observability - Pushing API Logs to your Datadog Instance
------
_April 27, 2023_
We're excited to announce the latest addition to increase observability for our customers — the ability to send API call logs within [Unified.to](https://unified.to/) directly to your Datadog instance.
[Datadog](https://www.datadoghq.com/) is a cloud-based monitoring and analytics platform that provides real-time insights into the performance of IT infrastructure, applications, and logs.
Setting it up is easy! Here's how:
1. Get your Datadog API key: To start sending logs to Datadog, you need to obtain your API key. Here's a [detailed article](https://docs.datadoghq.com/account_management/api-app-keys/) that walks you through the process.
2. Once you have your API key, simply paste it into your Unified.to [workspace settings](https://app.unified.to/settings/workspace).
3. Finally, make a sample API call to Unified API to confirm that logs are being sent to your Datadog instance.

## Why does Datadog matter for Unified.to users?
By sending your logs directly to Datadog, you get immediate visibility into what's happening in your environment. This allows you to quickly debug issues, get notified about errors, and more.
Currently, we support both inbound API calls and webhook calls.
However, we're constantly improving our product, and we're looking forward to supporting more log types in the future. So, please [send us feedback](https://unified.to/contact) so we can better understand what you need.
## About Unified.to
If you're not familiar with Unified API, it's a single API that you can use to read and write data to third-party integrations using our unified data model. This makes it easy to integrate with multiple systems and eliminates the need for separate API calls for each integration.
We hope this new feature makes your life easier and your work more efficient!
[Get started with Unified.to](https://app.unified.to/)
## Fireflies Integration Guide
URL: https://docs.unified.to/guides/fireflies_integration_guide
# Fireflies Integration Guide
------
_November 5, 2025_
# Fireflies Integration Guide
Learn how to connect an end-customer's [**Fireflies.ai**](https://fireflies.ai/) account to your application using [**Unified.to**](https://unified.to/).
This guide covers how to enable the integration, embed the authorization component, and verify the connection end-to-end.
## Overview
The Fireflies integration allows your app's end users to authorize [Unified.to](https://unified.to/) to access their Fireflies accounts for meeting recordings and user data.
Once connected, your application can access the Fireflies API securely through [Unified.to](https://unified.to/) — without your users ever leaving your app.
## Prerequisites
Before getting started, ensure you have:
- A [**Unified.to**](https://unified.to/) **developer account** and active API credentials
- Your end-customer has a [**Fireflies.ai**](https://fireflies.ai/) **paid plan** (API access required)
- Access to your app's frontend or backend to embed the Unified.to authorization component so that you can get your end-customer to authorize access to Fireflies
- Optional: API Explorer or MCP Server for testing connections
Please note that the Fireflies API is only available to users on a **paid Fireflies plan**. Free-tier accounts cannot authorize API access.
## Step-by-Step Setup
### 1. Enable the Fireflies Integration in [Unified.to](https://unified.to/)
1. Log in to your [Unified.to](https://unified.to/) developer dashboard.
2. Navigate to **Integrations**.
3. Locate **Fireflies** in the catalog.
4. Click **Activate Integration**.
Once enabled, Fireflies becomes available for your end-customer to authorize within your own app.
---
### 2. Embed the Authorization Component
Use the **Embedded Authorization (Authorize Optimization)** component from [Unified.to](https://unified.to/) to create a seamless in-app experience.
1. Add the component to your application UI where users connect integrations.
2. Preview the embedded experience - this is what your customers see when connecting Fireflies.
3. The component displays a button (e.g., 'Authorize Fireflies') and short instructions.
---
### 3. Obtain the Fireflies API Key
Your end-customers/users will need their **Fireflies API key** to complete authorization.
**Steps for your end user:**
1. Log in to their [**Fireflies.ai**](https://fireflies.ai/) account.
2. Go to **Settings → Integrations**.
3. Locate **API Key** and click **Copy**.
4. Return to your app's authorization screen and paste it into the field provided.
---
### 4. Authorize Access
1. In the authorization window, paste the Fireflies API key.
2. Click **Authorize**.
3. [Unified.to](https://unified.to/) will automatically:
- Validate the API key
- Connect to Fireflies
- Run a **test connection**
If successful, [Unified.to](https://unified.to/) creates a new **Connection Object** linked to that customer.
---
### 5. Verify the Connection
Once the test passes, a new connection record appears in your [Unified.to](https://unified.to/) dashboard.
| Field | Description |
| ----------------- | ------------------------------------ |
| **Connection ID** | Unique identifier for API requests |
| **Integration** | [Fireflies.ai](https://fireflies.ai/) |
| **Status** | Connected |
| **Created At** | Timestamp of creation |
Use the **Connection ID** to make authenticated API calls to Fireflies through [Unified.to](https://unified.to/).
---
### 6. Test the Connection (Optional)
You can test the integration in two environments:
a) Using [Unified.to](https://unified.to/) API Explorer
- Open **API Explorer** in your dashboard.
- Use your new Connection ID to query endpoints such as:
- `/hris/{id}/employee` – list Fireflies users
- `/calendar/{id}/recording` – retrieve meeting data
b) Using MCP Server (for AI Agents)
If your use case involves AI agents, test the connection within your **MCP Server** using the same Connection ID.
---
## Example Use Cases
- Retrieve meeting recordings for transcription or analysis
- Sync Fireflies user data into your CRM or analytics system
- Feed Fireflies meeting data into AI summarization or tagging workflows
---
## Troubleshooting
| Issue | Description | Solution |
| -------------------------- | -------------------------------- | ------------------------------------------------------- |
| **Authorization failed** | Invalid or expired API key | Re-copy the key from Fireflies and retry authorization. |
| **Test connection failed** | API access not enabled | Ensure user is on a paid Fireflies plan. |
| **No data returned** | Key valid but no accessible data | Check that the Fireflies workspace contains recordings. |
*Fireflies must have existing data (e.g., recorded meetings) to return results when testing API endpoints.
---
## Next Steps
- Explore the [Unified.to API Reference](https://www.notion.so/api-reference) to interact with `/hris/{id}/employee` and `/calendar/{id}/recording`.
- Monitor performance and connection status under **Integration Health** in your dashboard.
- Repeat these steps for other integrations or customer environments.
---
## Related Articles
- [Embedding the Authorization Component](https://www.notion.so/guides/embedded-authorization)
- [Testing Connections via API Explorer](https://www.notion.so/guides/testing-connections)
- [Managing Customer Connections](https://www.notion.so/guides/manage-connections)
---
_Last updated: November 2025_
## Getting started with Workable
URL: https://docs.unified.to/guides/getting_started_with_workable
# Getting started with Workable
------
_January 11, 2024_
Workable is one of 190+ integrations that you no longer have to build from scratch thanks to our unified API developer platform. In this guide, you will learn about Unified.to's [Workable integration](https://unified.to/integrations/workable), including how to activate Workable, implementation steps, example use cases, and how your users can authorize the integration in your app.
At the end of this guide, you will have everything you need to activate a Workable integration and begin creating user connections in production.
## Prerequisites
To activate a Workable integration, you will need a Unified.to account. [Sign up for a free tester account](https://app.unified.to/login) and follow the onboarding steps. Workable and all our pre-built integrations are available on every [Unified.to plan](https://unified.to/pricing).
This article assumes you are familiar with [Workable](https://www.workable.com/) and product integration concepts. You do not have to be an expert in integration development or our platform.
## Activating Workable
Once you've created your Unified.to account and completed onboarding, you can activate Workable and any other integrations you'd like to add to your app. Activation refers to the process of enabling and configuring an integration.
Sign in to Unified.to, and select **Integrations > Active Integrations**. Use the search bar or click the **ATS** tab to find Workable and other integrations within the same category. You can also locate Workable in the **HR** tab if you are interested in HR data.

**Authorization**
Select **Workable** to begin the activation process. On the authorization page, you can specify the integration categories (ATS or HR) and the authorization method (API key or OAuth 2). If you'd like your Workable integration to support OAuth 2, you will need to enter your OAuth 2 Client ID.

**OAuth 2**
If you want your Workable integration to support OAuth 2, select the **OAuth 2** tab to view where to set your OAuth2 redirectUri to and the unified scopes to enable in your application. You may need all or some depending on your use case.

If you don't yet have your Workable OAUth 2 credentials, fill out [this form](https://www.workable.com/partnership-program/apply?utm_source=unified.to&utm_medium=unified.to%20blog&utm_campaign=unifedto_partner) to start a Workable partnership and generate your credentials. Return to the **Authorization** tab, paste your credentials, and select **Activate**. If you need support with your Workable partnership, please email hello@unified.to or reach out on [Discord](https://discord.gg/85z7HF7JbD).
**Feature support**
Select the **Feature Support** tab to view a detailed breakdown of the objects and fields our Workable integration currently supports. Green represents fields we support, grey represents fields Workable does not support, and the fields in red are the features we don't support yet.

We're always open to feedback. If you have questions or ideas, reach out on [Discord](https://discord.gg/85z7HF7JbD). To see full feature support for Workable, [sign in or sign up for Unified.to for free](https://app.unified.to/login).
## Your workspace
When you create a Unified.to account, we automatically create a workspace for you. This will be true for any team members in your company that individually sign up for Unified.to. You can create multiple workspaces for different environments (QA, staging, production, etc.) with unique access and configuration rules.
You will need to know your workspace ID when using our embedded authorization widget. You can find your workspace ID under **Settings > API Information**.
## Setting up authorization
To add Workable and other integrations to your app, navigate to **Settings > Embed** to set up user authorization. Configure your authorization settings and copy the line of code to insert the embedded authorization widget into your app along with your workspace ID.
If you do not wish to use our authorization widget, you can call our API directly to get a list of integrations to display in your app and build your own front-end.

You can also interact with the authorization preview to simulate your end user's Workable authorization experience. You'll see the same authorization page that your end user will experience when they enable an integration in your app.
You can run further authorization tests by adding a [test user connection](https://unified.to/blog/start_here_how_to_add_a_test_connection) or by using our [sandbox environment](https://unified.to/blog/how_to_test_unified_apis_using_sandbox_connections).
## Creating user connections
Once a customer authorizes a Workable integration from your app, they are redirected to the success URL that you defined earlier when setting up authorization.
That URL will have an id={connection_id} appended to the URL. Store that connection_id in your database and associate it with your customer's account. You can also send in a state query parameter with your customer's ID.
**Important:** Make sure you associate your app's user ID/account ID with the newly created Unified.to connection ID. We recommend using the state parameter in our embedded authorization widget or authorization endpoint. That state will be sent back to your success_url along with the connection_id. Store our connection_id in your database and associate it with your user.
## Perform actions with your user connections
Here's the exciting part! Once your user authorizes your Workable integration, you can start accessing our API with your user connections. User connections represent a specific authentication of an integration, which means you can now:
- Access your customers' third-party data in Workable
- Build new features and support more users
- Leverage Workable data to support use cases like [candidate sourcing](https://unified.to/blog/how_to_build_a_candidate_sourcing_or_job_board_app_with_unified) and [candidate assessments](https://unified.to/blog/how_to_build_a_candidate_assessment_product_with_unified)
If you'd like to simulate performing actions with user connections, you can create user connections in our Sandbox environment.
Pass 'X-Mock: 1' in a header in your API request to receive mock data from our API. [See documentation](https://docs.unified.to/overview/restapi/mock).
**Note:** We have SDKs available for Node/Typescript, Python, PHP, Java, Go, C#, and Ruby. [Learn more](https://docs.unified.to/overview/sdks).
Have a question for us? Complete [this form](https://unified.to/contact) or chat with us directly on [Discord](https://discord.gg/85z7HF7JbD).
## Handling Delegated vs. Application Scopes in Microsoft Integrations
URL: https://docs.unified.to/guides/handling_delegated_vs_application_scopes_in_microsoft_integrations
# Handling Delegated vs. Application Scopes in Microsoft Integrations
------
_December 9, 2025_
Microsoft Graph offers two fundamentally different permission models — **Delegated** and **Application** scopes. Understanding how these scopes behave is important when connecting Microsoft integrations through Unified.
Some Microsoft APIs only support delegated permissions, while others require strictly application-level permissions. Mixing them in a single OAuth flow will cause failures.
In Unified, you can create separate Microsoft connections so you can cleanly target the scopes required for the endpoints you plan to use.
This guide explains the difference between the permission types, why certain Unified endpoints require one or the other, and how to configure your Unified connections correctly.
## **1. Delegated vs. Application Permissions**
### **Delegated Permissions**
Delegated permissions are used when a **signed-in Microsoft user** is present in the OAuth flow.
Use delegated permissions when:
- The API needs to act **on behalf of a user**
- You want to access data the user normally has permission to view
- The Microsoft Graph endpoint explicitly supports delegated scopes
### **Application Permissions**
Application permissions are used when **no user is logged in**, and the app accesses Microsoft Graph directly as itself.
Use application permissions when:
- A Microsoft endpoint **does not support delegated scopes**
- The API involves system-level or tenant-wide data
- The OAuth token must come from the **client_credentials** flow
Examples of Unified endpoints that only support application scopes:
- **`uc_call`** (MS Teams call records API)
- Microsoft Graph requires `CallRecords.Read.All` as an **application permission**, not delegated.
---
## **2. Why You Cannot Mix Delegated and Application Scopes**
Microsoft Graph enforces strict separation between permission types:
- Delegated scopes must be requested during a **user login** OAuth flow
- Application scopes must be requested using **client credentials**
- A single OAuth authorization cannot request both delegated and application permissions
If both are selected together:
- Microsoft returns `AADSTS650053` or similar errors
- The OAuth token cannot be issued
- Your Unified connection will fail to authenticate
Because of this, selecting both scope types for the same Unified connection will not work.
## HiBob & Unified: connection and time off guide
URL: https://docs.unified.to/guides/hibob_and_unified_connection_and_time_off_guide
# HiBob & Unified: connection and time off guide
------
_April 22, 2026_
This guide explains how to connect **HiBob** to **Unified** for HRIS use cases, which credentials to use, and how **time off**works in our implementation, including:
- `hris_employee` time off fields
- the `hris_timeoff` object
**Official HiBob API reference:** [https://apidocs.hibob.com/docs](https://apidocs.hibob.com/docs)
---
## Creating credentials in HiBob
1. In HiBob, go to **Settings → Integrations → Automation → Service Users**
2. Create or select a **service user** intended for API access
3. Copy the **Service User ID**
4. Generate or copy the **Service User Token**
5. Paste:
- the **ID** into the first credential field in Unified
- the **Token** into the second credential field in Unified
**Note:** Store the token securely and rotate it if exposed.
---
## hris_timeoff – Required Permissions
Before accessing time off data, ensure the **service user has the correct permissions**.
### Visibility Rules
Time off policies can be:
- Public
- Public with custom name
- Private
- Private requests are only visible if permission is granted
- Responses include a `visibility` field:
- `Public`
- `Private`
- `Custom name`
### Required Permissions
### Get Requests
- **People's Data → Time off → See who's out today → See who's out**
### Private Requests
- **People's Data → Time off → See who's out today → See who's out because of a private policy or policies with a custom name**
- Some endpoints require:
- `includePrivate: true`
### Pending Requests
- **People's Data → Time off → Requests → Create, edit all fields, and cancel people's requests that haven't been approved yet**
- Some endpoints require:
- `includePending: true`
### Get Request by ID
- **People's Data → Time off → Requests → Create, edit, and cancel people's requests that haven't been approved yet**
### Request Attachments
- **People's Data → Time off → Requests → Upload, edit and view attachments in people's requests**
### Policies & Policy Types
- **Features → Time off → Settings → Manage company's time off settings**
### Create Request
Requires BOTH:
- **Features → Time off → Settings → Manage company's time off settings**
- **People's Data → Time off → Requests → Create, edit, and cancel people's requests that haven't been approved yet**
### Time Off Balance
- **People's Data → Time off → Balance → See selected people's time off and sick leave balances**
- **People's Data → Time off → Balance → Adjust selected people's time off balances and bank their overtime**
---
## hris_employee – Required Permissions
To access time off balances and policies:
### Time Off Balance
- **People's Data → Time off → Balance → See selected people's time off and sick leave balances**
- **People's Data → Time off → Balance → Adjust selected people's time off balances and bank their overtime**
### Policies & Policy Types
- **Features → Time off → Settings → Manage company's time off settings**
## How long are logs retained
URL: https://docs.unified.to/guides/how_long_are_logs_retained
# How long are logs retained
------
_April 2, 2024_
**API call logs are retained for 60 days.** Our system removes log entries for API calls that are older than 60 days.
To retain a record of all of your API calls, we suggest that you make a backup copy of your API call log on a regular basis. You can do this by using our [Unified.to Admin API](https://docs.unified.to/unified/apicall/Returns_API_Calls).
For example, if you use our Node SDK, you could perform a backup using the `listUnifiedApicalls()` method:
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
const results = await sdk.unified.listUnifiedApicalls({ });
```
## How to access employees and users
URL: https://docs.unified.to/guides/how_to_access_employees_and_users
# How to access employees and users
------
_June 25, 2024_
How do you get information on a customer account's users and employees?
The essential idea is that regardless of whether the integration involves an e-commerce, CRM, or accounting application, user information for a corporate account is consistently accessible through our unified HR API. This is achieved using the HR [Get Employee](https://docs.unified.to/hris/employee/Retrieve_an_employee) and [List Employees](https://docs.unified.to/hris/employee/List_all_employees) API endpoints.
```javascript
GET /hris/{connection_id}/employee
GET /hris/{connection_id}/employee/{id}
```
There are approximately [120 ](https://docs.unified.to/hris/integrations)[integrations](https://docs.unified.to/hris/integrations)[ ](https://docs.unified.to/hris/integrations)that support the HR Employee endpoints.
### Employee/User IDs in all unified data models
Here is a list of different objects across all our unified API categories that link back to an HR Employee via their IDs. Note: We use the terms 'employee' and 'user' interchangeably.
| Category | Data Model | Field | Description |
| ----------- | ---------------------------------------------------------- | --------------------------- | --------------------------------------------------------------- |
| ATS | [Activity](https://docs.unified.to/ats/activity/model) | user_ids | The recruiters associated with this activity |
| ATS | [Candidate](https://docs.unified.to/ats/candidate/model) | user_id | The recruiter or hiring manager associated with this candidate |
| ATS | [Document](https://docs.unified.to/ats/document/model) | user_id | The user who created this document |
| ATS | [Interview](https://docs.unified.to/ats/interview/model) | user_ids | The employees that were in this interview |
| ATS | [Job](https://docs.unified.to/ats/job/model) | recruiter_ids | |
| ATS | [Job](https://docs.unified.to/ats/job/model) | hiring_manager_ids | |
| ATS | [Scorecard](https://docs.unified.to/ats/scorecard/model) | interviewer_id | |
| ATS | [Company](https://docs.unified.to/ats/company/model) | recruiters_ids | |
| Call Center | [Call](https://docs.unified.to/uc/call/model) | user_id | The agent |
| CRM | [Company](https://docs.unified.to/crm/company/model) | user_id | The salesperson that is responsible for this company/account |
| CRM | [Contact](https://docs.unified.to/crm/contact/model) | user_id | The salesperson that is responsible for this contact |
| CRM | [Deal](https://docs.unified.to/crm/deal/model) | user_id | The salesperson that is responsible for this deal/opportunity |
| CRM | [Event](https://docs.unified.to/crm/event/model) | user_id | |
| CRM | Lead[ ](https://docs.unified.to/crm/lead/model) | user_id | The salesperson that is responsible for this lead |
| CRM | [Lead](https://docs.unified.to/crm/lead/model) | creator_user_id | The user that created this lead |
| HR | [Employee](https://docs.unified.to/hris/employee/model) | manager_id | This user's reporting manager |
| HR | [Group](https://docs.unified.to/hris/group/model) | user_ids | The group's members |
| HR | [Group](https://docs.unified.to/hris/group/model) | manager_ids | The group's managers |
| HR | [Payslip](https://docs.unified.to/hris/payslip/model) | user_id | The employee that this pay slip pertains to |
| HR | [Timeoff](https://docs.unified.to/hris/timeoff/model) | user_id | The employee that this time off request pertains to |
| HR | [Timeoff](https://docs.unified.to/hris/timeoff/model) | approver_user_id | The employee that approved this time off request |
| KMS | [Space](https://docs.unified.to/kms/space/model) | user_id | The creator of this space |
| KMS | [Page](https://docs.unified.to/kms/page/model) | user_id | The creator of this oage |
| Messaging | [Message](https://docs.unified.to/messaging/message/model) | author_member.user_id | |
| Messaging | [Message](https://docs.unified.to/messaging/message/model) | destination_members.user_id | |
| Messaging | [Message](https://docs.unified.to/messaging/message/model) | hidden_members.user_id | |
| Messaging | [Message](https://docs.unified.to/messaging/message/model) | mentioned_members.user_id | |
| Storage | [File](https://docs.unified.to/storage/file/model) | user_id | The owner of this file/folder |
| Tasks | [Project](https://docs.unified.to/task/project/model) | user_ids | The users that belong to this project |
| Tasks | [Task](https://docs.unified.to/task/task/model) | assigned_user_id | |
| Tasks | [Task](https://docs.unified.to/task/task/model) | creator_user_id | |
| Tasks | [Task](https://docs.unified.to/task/task/model) | follower_user_ids | |
| Ticketing | [Note](https://docs.unified.to/ticketing/note/model) | user_id | The user who created this note |
| Ticketing | [Ticket](https://docs.unified.to/ticketing/ticket/model) | user_id | The user who created this ticket |
Here are several practical use cases that leverage our unified HR API for non-HR use-cases:
**Streamlined Onboarding:** Automate your customers' onboarding process by syncing employee data across HR, task management, and training platforms. Ensure that new hires are promptly added to all necessary systems and have access to the right resources from day one.
**Assigning Leads and Deals:** Assign newly created leads and/or deals to a specific sales-rep using their user ID
**Notify Hiring Managers:** When adding a new candidate and their job application, notify the hiring manager by retrieving their information and emailing them
By integrating employee data across multiple SaaS platforms, organizations can improve operational efficiency, enhance data accuracy, and provide a better employee experience.
## Resources
-
- [Overview of our unified HR API documentation ](https://docs.unified.to/hris/overview)
- [Full list of supported HR integrations ](https://unified.to/hris)
## How to add API support for the Create Activity in Crelate
URL: https://docs.unified.to/guides/how_to_add_api_support_for_the_create_activity_in_crelate
# How to add API support for the Create Activity in Crelate
------
_September 25, 2025_
Here's how to configure Activity Types properly.
First off, you need to set "Available On" for Activity Types:
1. Navigate to Settings → Activities
2. Select the specific Activity Type you want to configure
3. Find the Available On section - this determines which record types the Activity will appear on
4. Critical: By default, Activity Types are set to no records and must be configured or they'll be unavailable for use

The settings shown in the image above will enable the maximum number of API features.
## How to Associate a Connection ID with Your End-User
URL: https://docs.unified.to/guides/how_to_associate_a_connection_id_with_your_end_user
# How to Associate a Connection ID with Your End-User
------
_April 4, 2024_
This guide explains how to associate connections you create through Unified.to with end-users in your own application.
When your end-users authorize access to third-party applications through Unified.to, you need a way to keep track of which connections belong to which users in your application. This guide covers two ways to accomplish this, with recommendations on when to use each approach.
If you prefer to learn through video, check out our guide on YouTube:
## Before you begin
This guide assumes you have:
- A basic understanding of [end-users, integrations, and connections](https://unified.to/blog/end_users_integrations_and_connections)
- Familiarity with OAuth 2.0 authorization flows
## Method 1: Use the state parameter (recommended)
The state parameter provides a secure way to maintain context throughout the OAuth flow by passing data between your authorization request and the callback. The state parameter _does not_ change during authorization. Whatever value you put into the state parameter in the authorization URL is the same value that will be extracted from the success URL.
1. Create a state object with your user's ID, passing in additional security measures like a hashing signature. For example:
```javascript
const stateObject = {
user_id: "xyz789",
nonce: generateRandomString(), // Add randomness for security
timestamp: Date.now(), // Optional: Add timestamp for expiry checking
sig: generateSignature() // Optional: Add signature for verification
};
```
2. Encode the state object as a base64-string:
```javascript
const encodedState = Buffer.from(JSON.stringify(stateObject)).toString('base64');
```
3. Add the encoded state to your authorization URL
```javascript
const authUrl = `https://api.unified.to/unified/integration/auth/${workspace_id}/${integration}?state=${encodedState}`;
```
4. Handle the callback after successful authorization. The connection ID is included in the URL as `id`. For example:
```javascript
// In your callback handler
function handleCallback(req, res) {
// Get connection ID from the callback URL
const connectionId = req.query.id;
// Decode and verify the state
const decodedState = JSON.parse(Buffer.from(req.query.state, 'base64').toString());
// Verify the state is valid (check signature, nonce, timestamp if used)
if (verifyState(decodedState)) {
// Associate the connection ID with the user ID in your database
await saveUserConnection(decodedState.user_id, connectionId);
}
}
```
## Method 2: Use the external ID parameter
This method leverages Unified.to's built-in external ID (`uid`) field to store your user associations. The success URL contains another parameter, `uid`, also known as the **External ID** parameter, which can be used to map to your end-user's ID.
1. Add the external ID parameter to your authorization URL
```javascript
const authUrl = `https://api.unified.to/unified/integration/auth/${workspace_id}/${integration}?uid=${user_id}`;
```
2. Once a connection is created, that external ID is stored in the connection object. You can now query connections using the external ID:
```javascript
const options = {
method: 'GET',
url: 'https://api.unified.to/unified/connection',
headers: {
'authorization': `Bearer ${unified_api_key}`
},
params: {
external_xref: user_id
}
};
```
## Best practices for implementation
1. **Always validate the state parameter**
- Check signatures if used
- Verify nonces haven't been reused
2. **Securely store connection associations**
- Use a database to map user IDs to connection IDs
- Consider encrypting sensitive data
3. **Handle error cases**
- Missing parameters
- Failed authorization attempts
## How to build a Candidate Assessment product with Unified.to
URL: https://docs.unified.to/guides/how_to_build_a_candidate_assessment_product_with_unified
# How to build a Candidate Assessment product with Unified.to
------
_October 23, 2023_
There are two different ways that you can build a candidate assessment solution. Some ATS providers will allow for an assessment solution to register with them and then call your server with an assessment request. Unfortunately, not many ATS providers support this method, so [Unified.to](https://unified.to/) only supports this method that is widely supported.
In the high-paced realm of recruitment, where talent acquisition meets the evolving landscape of HR technology, the need for efficient and robust Candidate Assessment tools has never been more crucial. As companies strive to identify top talent, incorporating candidate assessment software that facilitates critical activities like background checks, skills tests, candidate analytics analysis and more not only expedites the screening process but also ensures a comprehensive and data-driven approach to talent acquisition.
This guide explore how to develop robust and scalable Candidate Assessment software by leveraging third-party integrations through Unified.to's Unified API developer platform to enhance the value of your candidate assessment solution.
For teams building AI-driven screening or semantic resume matching, this same architecture can also power retrieval-augmented generation (RAG) workflows on top of ATS data.
## Who
You have built a `Candidate Assessment` solution for recruiters and hiring managers, enabling them to evaluate candidates who are applying for positions within their companies.
## Why (your goal)
- You need to access your customers' `ATS` (application tracking systems) to automate the data flow of new applications so that your solution can test and assess their candidates.
- Retrieve resumes and job descriptions to power RAG-based candidate ranking and semantic matching.
## What
You will need to integrate seamlessly with the leading ATS solutions utilized by your customers, such as [Greenhouse](https://unified.to/integrations/greenhouse), [Lever](https://unified.to/integrations/lever), [SmartRecruiters](https://unified.to/integrations/smartrecruiters), [SAP SuccessFactors](https://unified.to/integrations/successfactors), and [more](https://unified.to/ats). This will provide your users with a streamlined user experience, ensuring efficient data flow and synchronization between your Candidate Assessment solution and their preferred Applicant Tracking Systems (ATS).
## How to add ATS integrations to your product
Before we start, be sure to first read:
[Getting Started with Unified](https://unified.to/blog/start_here_getting_started_with_unified)
1. **Get an initial list of applications**
1. When the customer authorizes an ATS connection, [read the initial set of active applications](https://docs.unified.to/ats/application/List_all_applications)
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { AtsApplication, AtsApplicationStatus } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function getApplications(connectionId: string, trigger_status: AtsApplicationStatus, jobId: string, updatedGte?: Date) {
const applications: AtsApplication[] = [];
const limit = 100;
let offset = 0;
while (true) {
const result = await sdk.ats.listAtsApplications({
updatedGte,
jobId,
offset,
limit,
connectionId,
});
const apps = result.atsApplications || [];
applications.push(...apps.filter((application) => application.status === trigger_status));
if (apps.length === limit) {
offset += limit;
} else {
break;
}
}
return applications;
}
```
2. **Set up to get updated applications**
1. If you want to use webhooks to get new/updated applications in the future, [create a webhook](https://docs.unified.to/unified/webhook/Create_webhook_subscription) with that `connection_id`
```typescript
import { createHmac } from 'crypto';
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { AtsApplication, AtsApplicationStatus, Event, ObjectType, WebhookType } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
interface IncomingWebhook {
id: string;
created_at: Date;
updated_at: Date;
workspace_id: string;
connection_id: string;
hook_url: string;
object_type: TObjectType;
interval: number;
checked_at: Date;
integration_type: string;
environment: string;
event: Event;
runs: string[];
fields: string;
webhook_type: WebhookType;
is_healthy: boolean;
page_max_limit: number;
}
interface IWebhookData {
data: T[]; // The data array will contact an array of specific objects according to the webhook's connection. (eg. CRM Contacts)
webhook: IncomingWebhook; // The webhook object
nonce: string; // random string
sig: string; // HMAC-SHA1(workspace.secret, data + nonce)
type: 'INITIAL-PARTIAL' | 'INITIAL-COMPLETE' | 'VIRTUAL' | 'NATIVE';
}
export async function createApplicationsWebhook(connectionId: string, myWebhookUrl: string) {
const result = await sdk.unified.createUnifiedWebhook({
webhook: {
hookUrl: myWebhookUrl,
objectType: ObjectType.AtsApplication,
event: Event.Updated,
connectionId,
},
});
return result.webhook;
}
export async function handleUnifiedWebhook(incoming: IWebhookData, trigger_status: AtsApplicationStatus) {
if (incoming.webhook.object_type !== 'ats_application') {
return; // not for us
}
const sig =
createHmac('sha1', process.env.WORKSPACE_SECRET)
.update(JSON.stringify(incoming.data))
.update(String(incoming.nonce))
.digest('base64');
if (sig !== incoming.sig) {
return; // Houston, we have a problem... with security
}
return incoming.data?.filter((application: AtsApplication) => application.status === trigger_status);
}
```
2. Alternatively, create a polling schedule to [get new/updated applications](https://docs.unified.to/ats/application/List_all_applications), which would be similar to the code in 1a)
3. **Filter applications on a specific status**
1. The [application](https://docs.unified.to/ats/application/model) has a standardized `status` across all ATS integrations, so decide which one makes sense for you to trigger the assessment
2. Application.Status options are `NEW` `REVIEWING` `SCREENING` `SUBMITTED` `FIRST_INTERVIEW` `SECOND_INTERVIEW` `THIRD_INTERVIEW` `BACKGROUND_CHECK` `OFFERED` `ACCEPTED` `HIRED` `REJECTED` `WITHDRAWN`. Of these, `REVIEWING`, `SCREENING`, `FIRST_INTERVIEW`, `SECOND_INTERVIEW`, or `BACKGROUND_CHECK` should be considered.
4. **Email the candidate with the assessment based on the job**
1. [Read the candidate](https://docs.unified.to/ats/candidate/Retrieve_a_candidate) from the `Application.candidate_id` field
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function readCandidate(connectionId: string, candidateId: string) {
return await sdk.ats.getAtsCandidate({
id: candidateId,
connectionId,
});
}
```
2. Optional: [read the job](https://docs.unified.to/ats/job/Retrieve_a_job) from the `Application.job_id` field
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function readJob(connectionId: string, jobId: string) {
return await sdk.ats.getAtsJob({
id: jobId,
connectionId,
});
}
```
3. Determine which test to send to the candidate based on the [`Job.id`](https://job.id/), `Job.descripton` or `Job.name` fields
5. **Once the candidate has completed the assessment/test, notify the hiring manager and/or recruiter**
1. [Read the employee](https://docs.unified.to/hris/employee/Retrieve_an_employee) information based on the `Job.hiring_managers_ids` and/or `Job.recruiter_ids` fields and use the `Employee.emails` field to email them the notification
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function readEmployee(connectionId: string, employeeId: string) {
return await sdk.hris.getHrisEmployee({
id: employeeId,
connectionId,
});
}
```
2. Optional: [push back a PDF document](https://docs.unified.to/ats/document/Create_a_document) into the customer's ATS that is associated with that application
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { AtsDocumentType } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function createDocument(connectionId: string, applicationId: string, documentUrl: string, filename: string, type: AtsDocumentType) {
return await sdk.ats.createAtsDocument({
atsDocument: {
applicationId,
type,
documentUrl,
filename,
},
connectionId,
});
}
```
3. Optional: [create an activity](https://docs.unified.to/ats/activity/Create_an_activity) associated with the application or candidate
4. Optional: [update a candidate](https://docs.unified.to/ats/candidate/Update_a_candidate) with certain `tags`
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function updateCandidateTags(connectionId: string, candidateId: string, tags: string[]) {
return await sdk.ats.updateAtsCandidate({
atsCandidate: {
tags,
},
id: candidateId,
connectionId,
});
}
```
### Advanced: Adding RAG-Based Resume Matching
Beyond triggering assessments, you can extend this workflow into a RAG architecture:
1. Retrieve the candidate's resume (`AtsCandidate.resume_url`) and job description.
2. Chunk and embed both documents.
3. Store embeddings in your vector database with identifiers like `connection_id`, `candidate_id`, and `job_id`.
4. Retrieve the most relevant resume segments against the job description before generating screening insights or ranking scores.
Unified handles ingestion and normalization across ATS providers; embeddings and retrieval remain in your infrastructure.
## Keep learning:
- [How to add a test connection ](https://unified.to/blog/start_here_how_to_add_a_test_connection)
- [How your customers add integrations from your application](https://unified.to/blog/start_here_how_your_customer_can_add_an_integration_in_your_application)
- [How to generate OAuth 2 credentials ](https://unified.to/blog/start_here_how_to_generate_oauth2_credentials)
## A unified API to integrate them all
Unified.to is a complete solution to streamline your integration development process and power your Candidate Assessment product with critical third-party candidate data. You're reading this article on the [**Unified.to**](https://unified.to/) blog. We're a Unified API developer platform for SaaS customer-facing integrations. We're excited to continue to innovate at Unified.to and solve hard, critical integration-related problems for our customers. If you're curious about our integrations-as-a-service solution, consider [**signing up for a free account**](https://app.unified.to/login) or [**meet with an integrations expert**](https://calendly.com/michelle-unified/discovery-via-blog)**.**
## How to build a candidate sourcing or job board app with Unified.to
URL: https://docs.unified.to/guides/how_to_build_a_candidate_sourcing_or_job_board_app_with_unified
# How to build a candidate sourcing or job board app with Unified.to
------
_January 4, 2024_
## Why build with a unified API?
API integrations are necessary for any modern SaaS app, especially for recruitment solutions like candidate sourcing and job boards that require a high volume of integrations to improve product value and support a broader user base.
Instead of spending months or years developing ATS integrations individually, developers can leverage a unified API to integrate once to launch multiple integrations simultaneously. Unified APIs simplify integration efforts by offering consistent endpoints and data formats that reduce the complexity of managing numerous APIs.
Unified APIs accelerate development cycles, enabling quicker deployment of new features and product updates for your recruitment software. We built Unified.to to make integration development as easy as possible for SaaS developers.
## Prerequisites
Before starting, be sure to review our [Getting Started with Unified](https://unified.to/blog/start_here_getting_started_with_unified) article. It will walk you through registering for a free account and our onboarding. Once you have completed the onboarding steps, go ahead and as you like.

Next, deploy the [Unified.to](https://unified.to/) Embedded Authorization widget in your product's user-interface. Check out our to learn more.
## Overview
These are main steps that you will need to support to build your candidate sourcing or job board app:
1. read active jobs to search relevant candidates for
2. apply a candidate to a job
Let's go into more detail for each.
## Step 1: Read Jobs
Before searching or finding relevant candidates, you'll need to know which jobs your customer wants you to search for.
[Unified.to](https://unified.to/) can help your app seamlessly import jobs from your customer's ATS.
To get a list of existing jobs from your end user's ATS, simply call the `listJobs` [API endpoint](https://docs.unified.to/ats/job/List_all_jobs).
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: "",
},
});
export async function getJobs(connectionId: string) {
return await sdk.ats.listAtsJobs({
connectionId,
});
}
```
The connection_id comes in from your end user's authorization of their ATS and is stored on your end. You can watch our for more detail.
A `Job` will have specific fields such as `name` , `description`, `compensation`, and `location` that will be relevant to your software. Reference our [API docs](https://docs.unified.to/ats/job/model) for more information.

Once you have the list of jobs, ask your customer to identify which jobs your app should identify candidates for.
## Step 2: Create an application
Once you have a candidate, there are two ways to create a job application.
1. Send the interested candidate to the job's public web page to apply directly. Most `Job` objects will have a `public_job_url` field that contains a list of URLs. The downside to this method is that you lose control of the candidate's experience and not all ATS providers supply the public website. The upside of this method is that it is very easy to implement.
2. Create an application and candidate in the ATS directly using our Unified ATS API. This is easily accomplished by using the `createCandidate` [API endpoint](https://docs.unified.to/ats/candidate/Create_a_candidate) and `createApplication` [API endpoint](https://docs.unified.to/ats/application/Create_an_application).
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { AtsCandidate } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function createCandidate(connectionId: string, atsCandidate: AtsCandidate) {
const result = await sdk.ats.createAtsCandidate({
atsCandidate,
connectionId,
});
return result.atsCandidate?.id;
}
export async function createApplication(connectionId: string, candidateId: string, jobId: string) {
const result = await sdk.ats.createAtsApplication({
atsApplication: {
candidateId,
jobId,
},
connectionId,
});
return result.atsApplication?.id;
}
```
## Step 3: uploading a resume/CV/cover letter (optional)
You can also upload the candidate's documents. You will need to set the following fields in the `Document` [object](https://docs.unified.to/ats/document/model):
- job_id
- application_id
- document_url
Some ATS providers may require additional fields to be present. Be sure to check their [feature support page](https://app.unified.to/integrations/greenhouse?tab=support) for more information. For example, Greenhouse also requires `type` and `filename` fields.
## Step 4: Review the application status (optional)
If you need to be informed on the status of the candidate's job application, you can use the `getApplication` [API endpoint](https://docs.unified.to/ats/application/Retrieve_an_application) or you can set up a webhook for that connection's modified applications.
## Conclusion
It is easy to support many ATS integrations in your recruiting application and allow your end user to seamlessly move data between the two. Use Unified.to's [Unified ATS API](https://unified.to/ats) to launch integrations in days and harness recruitment data for your candidate sourcing or job board app.
## Additional resources
- [Unified ATS API](https://unified.to/ats)
- [API Documentation for the Unified ATS API](https://docs.unified.to/ats/overview)
- [SDKs for Unified.to](https://docs.unified.to/overview/sdks)
-
-
## How to build a Discord support bot with Unified.to and Langbase
URL: https://docs.unified.to/guides/how_to_build_a_discord_support_bot_with_unified_and_langbase
# How to build a Discord support bot with Unified.to and Langbase
------
_December 12, 2024_
Want to build an AI-powered support bot that can answer questions with information from an knowledge base?
In this guide, we'll show you how to combine Unified.to's data ingestion capabilities with Langbase's AI agent framework to create a powerful support bot. We'll pull content from your customers' knowledge management system (like Notion) through Unified.to, process it with Langbase's Memory Agents, and make it available to a Discord bot, powered by Unified.to's Messaging API, so it can answer support questions.
We'll be using React and Typescript in these examples, which you can adapt to fit your framework of choice.
## Overview
This guide walks you through how to:
- Pull knowledge base content using Unified.to's KMS API
- Process and prepare this content using Langbase's Memory Agents
- Create an AI agent using Langbase Pipes
- Send responses through Discord using Unified.to's Messaging API
By combining Unified.to's unified APIs with Langbase's AI capabilities, we get the best of both worlds: easy access to your data sources and powerful AI features without the complexity of building everything from scratch.
## Before you begin
Make sure you have:
- A [Unified.to](https://app.unified.to/) account
- A [Langbase](https://langbase.com/) account
- Access to a knowledge management system (e.g., Notion) containing your support content
- Unified.to allows you to easily access your customers' data and that's normally what you would use in production, but for the purposes of this guide you can follow along with your own Notion account
- A Discord server where you want to deploy your bot
> If you don't want to use real data from Notion or Discord, you can also take advantage of Unified.to's [sandbox environment](https://docs.unified.to/concepts/sandbox) and work with synthetic data instead.
The following steps assume you already have your Unified.to account with your workspace ID and API key available. If you're new, check out our [Quick Start guide](https://docs.unified.to/quick-start) to get familiar with the platform.
## Set up your connections
The first thing you'll do is activate your preferred KMS platform to be the source of data for your bot. We'll use Notion in this guide, but our [KMS API](https://unified.to/kms) supports 8 platforms today and more are being added on a regular basis.
**Note:** If you're using the sandbox environment, for the steps below that ask you to enter your OAuth 2 credentials, just enter any random value e.g., `test123`.
### Activate Notion and Discord integrations
1. On app.unified.to, go to the [Integrations page](https://app.unified.to/integrations)
2. Search for and select Notion
3. Choose OAuth 2, enter your credentials (which can be anything if you're using the sandbox environment), and then click **Activate**
Repeat steps 1-3 but for Discord. If you want to use real data from Notion and Discord, check out our how-to guides on setting up with them:
- [How to set up and configure Notion](https://docs.unified.to/guides/how_to_set_up_and_configure_notion)
- [How to get your Discord OAuth 2 credentials and bot token](https://docs.unified.to/guides/how_to_get_your_discord_oauth_2_credentials_and_bot_token)
### (Sandbox environment) Create a test connection from the web app
If you're using the sandbox environment, then you can create test connections easily from the web app.
1. On app.unified.to, go to the [Connections](https://app.unified.to/connections) page
2. Click on **Create test connection** at the top right corner of the page (note: this will only show up if your current environment is the sandbox environment)
3. Click on the **Notion** and **Discord** integrations
4. Click **Done**

Skip ahead to the 'Pull content from your knowledge base' step below.
### (Production environments) Add the Authorization component to your app
To ask for your customers' permission to access their Notion and Discord accounts, you can add Unified.to's Authorization component to your front-end:
1. Add the following React component to your app. Make sure to replace `WORKSPACE_ID` with your actual workspace ID.
```javascript
// TODO: Make sure to run 'npm install @unified-api/react-directory'
import UnifiedDirectory from '@unified-api/react-directory';
```
We also offer pre-built embedded components in Angular, Vue, and Svelte. See other ways to display the Authorization component in your app [here](https://app.unified.to/embed?tab=Authorization).
- If your customers' mainly interact with you through message-based platforms, you could even send them the Authorization URL through an AI chat agent instead of a web front-end!
The Authorization component displays a list of your integrations. When your user clicks on one of these integrations, the authorization flow will open up in a new tab. Your users will follow the steps to grant you access to their accounts. When that is done, they will be redirected back to your app (the default success URL location).
1. Handle the callback after a user authorizes an integration. The connection ID will be sent back in the success URL - we need to save this to make API calls later on. In this example, we'll save the connection ID to local storage:
```javascript
function handleAuthCallback() {
const urlParams = new URLSearchParams(window.location.search);
const connectionId = urlParams.get('id');
if (connectionId) {
console.log('New connection created:', connectionId);
// Store this connection ID securely - you'll need it for API calls
localStorage.setItem('unifiedConnectionId', connectionId);
}
}
window.addEventListener('load', handleAuthCallback);
```
For this guide, you can be your own test user:
1. Click on the Notion integration in the Authorization component
2. Grant access to your Notion workspace
3. Repeat for Discord
4. Save both connection IDs - you'll need them for the next steps
Note: In production, you should associate these connection IDs with your user's account in your database. For more information, see our guide on [How to associate connection IDs with your users](https://docs.unified.to/guides/how_to_associate_a_connection_id_with_your_end_user).
## Pull content from your knowledge base
Now that you've got a connection ID, we can start making calls to the Unified API!
First, let's write some functions to recursively fetch all content from your KMS:
```javascript
async function fetchContent(connectionId: string, apiKey: string) {
const spaces = await fetchAllSpaces(connectionId, apiKey);
const allContent = [];
for (const space of spaces) {
const pages = await fetchPagesInSpace(connectionId, apiKey, space.id);
allContent.push(...pages);
}
return allContent;
}
async function fetchAllSpaces(connectionId: string, apiKey: string) {
const response = await fetch(
`https://api.unified.to/kms/${connectionId}/space?limit=100`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
}
}
);
if (!response.ok) {
throw new Error(`Failed to fetch spaces: ${response.statusText}`);
}
return response.json();
}
async function fetchPagesInSpace(connectionId: string, apiKey: string, spaceId: string) {
const response = await fetch(
`https://api.unified.to/kms/${connectionId}/page?limit=100&space_id=${spaceId}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
}
}
);
if (!response.ok) {
throw new Error(`Failed to fetch pages: ${response.statusText}`);
}
return response.json();
}
// Usage
const content = await fetchContent(CONNECTION_ID, API_KEY);
```
**API reference**: [KMS](https://docs.unified.to/kms/overview)
Call `fetchContent` in your app to see the results. You should see a collection of [Pages](https://docs.unified.to/kms/page/model).
## Process the content with Langbase Memory Agents
Now that we have our knowledge base content, we need to prepare it for AI consumption. Instead of building a complex RAG pipeline ourselves, we'll use Langbase's [Memory Agents](https://langbase.com/docs/memory) to handle all the heavy lifting - from text processing to embedding generation and storage.
First, let's install the Langbase SDK:
```shell
npm install langbase
```
Now we can create memory to store our knowledge base:
```typescript
import { Langbase } from 'langbase';
const langbase = new Langbase({
apiKey: process.env.LANGBASE_API_KEY!,
});
async function createKnowledgeBase() {
// Create a new memory
const memory = await langbase.memory.create({
name: 'support-bot-knowledge',
description: 'Knowledge base for our support bot',
embedding_model: 'openai:text-embedding-3-large'// Using OpenAI's latest model
});
return memory;
}
```
**API reference:** [Create memory](https://langbase.com/docs/sdk/memory/create)
Now let's process our Unified.to content and upload it to Langbase:
```typescript
async function uploadContentToMemory(pages, memoryName: string) {
for (const page of pages) {
// Download the page content from Unified.to's download_url
const content = await fetch(page.download_url).then(res => res.text());
// Create a buffer from the content
const documentBuffer = Buffer.from(content);
// Upload to Langbase with metadata
await langbase.memory.documents.upload({
memoryName,
documentName: page.title,
document: documentBuffer,
contentType: 'text/markdown', // Adjust based on your content type
meta: {
source: 'unified_kms',
created_at: page.created_at,
updated_at: page.updated_at,
space_id: page.space_id
}
});
}
}
```
**API reference:** [Upload document](https://langbase.com/docs/sdk/memory/document-upload)
Langbase's Memory Agents will automatically:
1. Process the text (Unified.to's data models have already cleaned the data for you!)
2. Split content into optimal chunks
3. Generate embeddings using state-of-the-art models
4. Store and index everything for fast retrieval
Once your content is uploaded, you can create a Pipe to handle natural language interactions. Pipes are custom AI agents that can be exposed as APIs, making them perfect for our Discord bot:
```typescript
async function createSupportBotPipe(memoryName: string) {
const pipe = await langbase.pipe.create({
name: 'Support Bot',
type: 'chat',
memory: memoryName,
description: 'Support bot powered by our knowledge base'
});
return pipe;
}
```
**API reference**: [Create pipe](https://langbase.com/docs/sdk/pipe/create)
This gives you a fully-functional RAG system with just a few API calls. No need to manage embeddings, vector stores, or complex AI infrastructure - Langbase handles all of that for you.
## Set up real-time message monitoring
To make your support bot responsive, you'll need to set up webhooks to receive notifications when users send messages in Discord channels.
1. Create a webhook to monitor Discord messages:
```javascript
async function createDiscordWebhook(connectionId: string, apiKey: string, hookUrl: string) {
const response = await fetch(
'https://api.unified.to/unified/webhook',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
connection_id: connectionId,
hook_url: hookUrl,
object_type: 'messaging_message',
event: 'created'
})
}
);
if (!response.ok) {
throw new Error(`Failed to create webhook: ${response.statusText}`);
}
return response.json();
}
createDiscordWebhook(CONNECTION_ID, API_KEY, 'https://localhost:8000/webhook')
```
**API reference**: [Create a webhook](https://docs.unified.to/unified/webhook/Create_webhook_subscription)
1. Set up an endpoint on your server to handle incoming webhook events. For example:
```javascript
app.post('/webhook', async (req, res) => {
// Verify webhook signature
const { data, webhook, nonce, sig } = req.body;
// Handle new messages
for (const message of data) {
// Ignore messages from the bot itself
if (message.author_member.id === BOT_USER_ID) continue;
// Process the message and generate a response
const response = await generateBotResponse(message.message);
// Send the response back to the channel
await sendDiscordResponse(
DISCORD_CONNECTION_ID,
API_KEY,
message.channel_id,
response
);
}
res.sendStatus(200);
});
```
1. Validate incoming webhooks (recommended):
```javascript
const crypto = require('crypto');
function verifyWebhookSignature(data, nonce, signature, workspaceSecret) {
const hmac = crypto.createHmac('sha1', workspaceSecret);
const calculatedSignature = hmac
.update(JSON.stringify(data) + nonce)
.digest('base64');
return calculatedSignature === signature;
}
```
With this setup, your bot can:
1. Receive notifications whenever a new message is sent in the monitored channels
2. Process those messages through your AI pipeline
3. Send responses back to the appropriate channel
For more details on webhooks at Unified.to, see our [guide to webhooks](https://docs.unified.to/reference/webhooks).
## Send responses through Discord
When you're ready to respond to a user's question, use our Messaging API to send the message back to Discord. Here's an example implementation with error handling and message formatting:
```javascript
async function sendDiscordResponse(connectionId: string, apiKey: string, channelId: string, response: string) {
const formattedResponse = formatDiscordMessage(response);
const apiResponse = await fetch(
`https://api.unified.to/messaging/${connectionId}/message`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: channelId,
message: formattedResponse,
message_html: formattedResponse, // Optional: Use for formatted text
})
}
);
if (!apiResponse.ok) {
if (apiResponse.status === 401) {
throw new Error('Authentication failed - check your API key');
} else if (apiResponse.status === 404) {
throw new Error('Channel not found - verify channel ID');
}
throw new Error(`Failed to send message: ${apiResponse.statusText}`);
}
return apiResponse.json();
}
function formatDiscordMessage(response) {
// Example formatting for Discord markdown
return `**Bot Response:**\n${response}`;
}
// Example usage with different message types
async function sendBotResponses(connectionId, apiKey, channelId) {
// Simple text response
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'Here is the answer to your question...'
);
// Response with code block
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'```javascript\nconst example = "code block";\n```'
);
// Response with rich formatting
await sendDiscordResponse(
connectionId,
apiKey,
channelId,
'**Bold text** and *italic text* with a [link](https://example.com)'
);
}
```
**API reference**: [Messaging](https://docs.unified.to/messaging/overview)
## Putting it all together
Here's a high-level overview of how to combine all the components into a working support bot:
```typescript
import { Langbase } from 'langbase';
async function initializeSupportBot() {
// Initialize APIs
const langbase = new Langbase({
apiKey: process.env.LANGBASE_API_KEY!
});
// Get connection IDs - implement these based on how you manage your users
const KMS_CONNECTION_ID = await getKMSConnection(user_id);
const DISCORD_CONNECTION_ID = await getDiscordConnection(user_id);
const UNIFIED_API_KEY = process.env.UNIFIED_API_KEY;
// Step 1: Fetch content from KMS
const kmsContent = await fetchContent(KMS_CONNECTION_ID, UNIFIED_API_KEY);
// Step 2: Set up Langbase Memory and upload content
const memory = await langbase.memory.create({
name: `support-kb-${user_id}`,
description: 'Knowledge base for support bot'
});
await uploadContentToMemory(kmsContent, memory.name);
// Step 3: Create an AI agent using Langbase Pipe
const pipe = await langbase.pipe.create({
name: `discord-support-bot`,
type: 'chat',
memory: memory.name,
description: 'Support bot powered by our knowledge base'
});
// Step 4: Set up Discord webhook for real-time messages
const webhook = await createDiscordWebhook(
DISCORD_CONNECTION_ID,
UNIFIED_API_KEY,
'https://your-server.com/webhook'
);
// Step 5: Handle incoming questions
async function handleQuestion(channelId: string, question: string) {
try {
// Use the Pipe to generate a response
const response = await langbase.pipe.chat({
pipe: pipe.name,
messages: [{ role: 'user', content: question }]
});
// Send the response back to Discord
await sendDiscordResponse(
DISCORD_CONNECTION_ID,
UNIFIED_API_KEY,
channelId,
response.message
);
} catch (error) {
console.error('Error handling question:', error);
}
}
return {
memory,
pipe,
webhook,
handleQuestion
};
}
// Usage example
async function startBot() {
const bot = await initializeSupportBot();
// Your webhook endpoint would call this when receiving messages
app.post('/webhook', async (req, res) => {
const { data, webhook, nonce, sig } = req.body;
// Handle new messages
for (const message of data) {
// Ignore messages from the bot itself
if (message.author_member.id === BOT_USER_ID) continue;
await bot.handleQuestion(message.channel_id, message.message);
}
res.sendStatus(200);
});
}
```
This implementation:
1. Initializes both Unified.to and Langbase clients
2. Creates a Memory AGent to store your knowledge base content
3. Sets up a Pipe to handle natural language interactions
4. Configures Discord webhooks for real-time messaging
The bot will now:
- Automatically receive messages from Discord through webhooks
- Process questions using your knowledge base through Langbase Pipes
- Send responses back to the appropriate Discord channel
All of this happens with minimal infrastructure management on your part - Unified.to handles the integrations while Langbase manages the AI components.
## Conclusion
Congratulations! You've built a powerful support bot by combining Unified.to's integration capabilities with Langbase's AI features. In this guide, you learned how to:
- Pull knowledge base content from any system using Unified.to's KMS API
- Process and embed that content using Langbase Memory Agents
- Create an AI agent using Langbase Pipes
- Handle real-time messages through Discord using Unified.to's Messaging API and webhooks
As an added bonus, this architecture is incredibly flexible. Since you're using Unified.to's APIs, you can easily expand your knowledge sources - want to pull content from Confluence instead of Notion? Or deploy your bot on Slack? It's just a matter of changing the connection - your core logic stays the same.
And with Langbase handling the AI components, you can enhance your bot's capabilities without managing complex infrastructure:
- Experiment with multiple Memory Agents for different types of content
- Customize your Pipe's behaviour through prompt engineering
- Scale your bot without worrying about vector store management
Happy building!
## How to Build a Fintech Application with Unified's Payments API
URL: https://docs.unified.to/guides/how_to_build_a_fintech_application_with_unified_payments_api
# How to Build a Fintech Application with Unified's Payments API
------
_September 9, 2025_
_Last Updated: July 2026_
With Unified, you can build fin-tech products that work with your end customers payment providers. With a single integration you can connect payment processors like Stripe, PayPal, GoCardless and more!
You can do various things such as creating payments, generating payment links, and reading refund and payout records for reconciliation, all without building custom integrations for each payment provider. Refunds and payouts are read-only in the unified model: you retrieve them to reconcile, and issue them through the payment provider itself.
In this guide, we will show you how to create and list payments as well as some related data. For the example we will be using Stripe, but this approach works for any of the payment integrations supported by Unified.
[See the full list of supported payment integrations.](https://docs.unified.to/payment/integrations)
---
## Prerequisites
- Node.js (v18+)
- Unified account with a payment integration enabled (e.g., Stripe)
- Unified API key
- Your customer's payment processor connection ID
---
## Step 1: Setting up your project
Set up your dependencies
```bash
mkdir payments-demo
cd payments-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
```
Add your credentials to `.env`:
```plain text
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_STRIPE=your_customer_stripe_connection_id
```
---
## Step 2: Initialize the SDK
```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
const { UNIFIED_API_KEY, CONNECTION_STRIPE } = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
```
---
## Step 3: How to Get Your Customer's Connection ID
Before you can list payments, your customer must authorize your app to access their payment provider via Unified's embedded auth flow.
Once authorized, you'll receive a connection ID for each integrations.
## Step 4: Listing Payments
```typescript
export async function listPayments(connectionId: string) {
const payments = await sdk.payment.listPaymentPayments({
connectionId,
limit: 10,
});
return payments; // PaymentPayment[]
}
```
---
## Step 5: Creating a Payment Link
```typescript
export async function createPaymentLink(connectionId: string, amount: number, currency: string) {
const link = await sdk.link.createPaymentLink({
connectionId,
paymentLink: {
amount,
currency,
isActive: true,
successUrl: "",
// Stripe typically requires line items on links
lineitems: [
{
itemName: "Order #123",
unitAmount: amount,
unitQuantity: 1,
itemSku: "SKU-123",
},
],
},
});
return link; // PaymentLink
}
```
---
## Step 6: Listing Refunds
```typescript
export async function listRefunds(connectionId: string) {
const refunds = await sdk.refund.listPaymentRefunds({
connectionId,
limit: 10,
});
return refunds; // PaymentRefund[]
}
```
---
## Step 7: Listing Payouts
```typescript
export async function listPayouts(connectionId: string) {
const payouts = await sdk.payout.listPaymentPayouts({
connectionId,
limit: 10,
});
return payouts; // PaymentPayout[]
}
```
---
## Step 8: Listing Subscriptions
```typescript
export async function listSubscriptions(connectionId: string) {
const subs = await sdk.subscription.listPaymentSubscriptions({
connectionId,
limit: 10,
});
return subs; // PaymentSubscription[]
}
```
---
## Step 9: Example Usage
Here's how you might use these functions in your payment workflow:
```typescript
async function main() {
// 1. Create a payment
const payment = await createPayment(CONNECTION_STRIPE!, 1000, "USD");
// 2. List payments
const payments = await listPayments(CONNECTION_STRIPE!);
// 3. Create a payment link
const link = await createPaymentLink(CONNECTION_STRIPE!, 1000, "USD");
// 4. List links
const links = await sdk.link.listPaymentLinks({ connectionId: CONNECTION_STRIPE!, limit: 10 });
// 5. List refunds
const refunds = await listRefunds(CONNECTION_STRIPE!);
// 6. List payouts
const payouts = await listPayouts(CONNECTION_STRIPE!);
// 7. List subscriptions (creation may not be implemented for some providers)
const subscriptions = await listSubscriptions(CONNECTION_STRIPE!);
console.log("Payment:", payment);
console.log("Payments count:", payments.length);
console.log("Payment Link URL:", link.url);
console.log("Links count:", links.length);
console.log("Refunds count:", refunds.length);
console.log("Payouts count:", payouts.length);
console.log("Subscriptions count:", subscriptions.length);
}
main();
```
---
Happy building! 🎉
## How to Build an E-Commerce Product Integration with Unified
URL: https://docs.unified.to/guides/how_to_build_an_e_commerce_product_integration_with_unified
# How to Build an E-Commerce Product Integration with Unified
------
_September 8, 2025_
Unified makes it possible for developers to build ecom products that work with your end customers preferred ecom platforms!
With our Unified Commerce API, you can connect to dozens of ecom systems (like Shopify, BigCommerce, WooCommerce and more).
This article will give you knowledge on how to create and list products, and to even create product images from the product description using Unified's GenAI integration.
## Requirements
- Node.js (v1
- Unified account with ecom integration
- Unified API key
- Your end customer's e-commerce connection ID
- Unified GenAI connection ID
## Step 1: Setting up your project
```bash
mkdir ecommerce-demo
cd ecommerce-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
```
Add your credentials to `.env`:
```shell
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_SHOPIFY=your_customer_shopify_connection_id
CONNECTION_GENAI=your_genai_connection_id
```
---
## Step 2: Initialize the SDK
```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
const {
UNIFIED_API_KEY,
CONNECTION_SHOPIFY,
CONNECTION_GENAI
} = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
```
---
## Step 3: How to Get Your Customer's Connection ID
Before you can create or list products, your customer must authorize your app to access their e-commerce store via Unified's authentication flow.
Once authorized, you'll receive a **connection ID** for that customer's integration (e.g., Shopify).
Store this connection ID securely as shown above and use it in all API calls for that customer.
See the [full list of supported integrations](https://unified.to/commerce).
---
## Step 4: Generate a Product Image from Description (with GenAI)
You can use Unified GenAI to generate a product image based on the product description.
```typescript
export async function generateProductImage(description: string) {
const prompt = `Generate a high-quality product image for the following product description: ${description}`;
const result = await sdk.genai.createGenaiPrompt({
connectionId: CONNECTION_GENAI!,
prompt: {
messages: [{ role: "USER", content: prompt }],
maxTokens: 1024,
temperature: 0.7,
responseFormat: "image_url", // or whatever the GenAI API expects for image output
},
});
return result.choices?.[0]?.message?.content || "";
}
```
_Explanation:_
- This function sends the product description to Unified GenAI and gets back an image URL.
- You can use this image URL when creating the product in Shopify (or any other e-commerce platform).
---
## Step 5: Create a Product (with AI-generated Image)
Here's how to create a product in Shopify, using the generated image.
```typescript
export async function createProduct(connectionId: string, name: string, description: string, price: number) {
// 1. Generate product image
const imageUrl = await generateProductImage(description);
// 2. Create the product
const createProductResult = await sdk.commerce.createCommerceItem({
connectionId,
commerceItem: {
name,
description,
price,
media: imageUrl ? [{ url: imageUrl }] : [],
isActive: true,
// Add any other fields as needed (e.g., tags, variants, metadata)
},
});
return createProductResult; // commerceItem
}
```
_Explanation:_
- Generates an image from the description.
- Creates a product in Shopify (or any Unified-supported e-commerce platform) with the image and other details.
---
## Step 6: List Products
You can list all products for a customer's e-commerce connection:
```typescript
export async function listProducts(connectionId: string) {
const productsResult = await sdk.commerce.listCommerceItems({
connectionId,
limit: 10,
});
return productsResult; // commerceItem[]
}
```
_Explanation:_
- Fetches the first 10 products for the customer's store.
- You can filter, paginate, or sort as needed.
---
## Step 7: Example Usage
Here's how you might use these functions in your product workflow:
```typescript
async function main() {
const name = "AI-Generated T-Shirt";
const description = "A stylish t-shirt with a unique, AI-generated design.";
const price = 29.99;
// 1. Create the product with an AI-generated image
const product = await createProduct(CONNECTION_SHOPIFY!, name, description, price);
// 2. List products
const products = await listProducts(CONNECTION_SHOPIFY!);
console.log("Created product:", product);
console.log("All products:", products);
}
main();
```
---
## How to build an invoicing system with Unified.to
URL: https://docs.unified.to/guides/how_to_build_an_invoicing_system_with_unified
# How to build an invoicing system with Unified.to
------
_January 3, 2024_
When building a SaaS app for invoicing, you'll need to exchange data within your customers' accounting app of choice. In this guide, we'll cover the basics of how to access the data you need for your invoicing app with the help of [Unified.to](https://unified.to/)'s unified accounting API.
## Prerequisites
Before starting, be sure to review our [Getting Started with Unified](https://unified.to/blog/start_here_getting_started_with_unified) article. It will walk you through registering for a free account and onboarding. Once you have completed the onboarding steps, go ahead and as you like.

The next step is to deploy the [Unified.to](https://unified.to/) Embedded Authorization widget in your product's user interface (UI). Check out our to learn more.
## Overview
Here are the steps that you will need to support to build your invoicing app:
1. create an invoice
2. pay an invoice or cancel an invoice
Let's go into more detail for each.
## Step 1: Create an invoice
To generate an invoice, gather specific details from your end user, including essential information like the amount, line items/products, due date, and customer details.
[Unified.to](https://unified.to/) can help with two of those values: customer and product.
**Customers**
To get a list of existing customers from your end user's accounting system, simply call the `listCustomers` [API endpoint](https://docs.unified.to/accounting/customer/List_all_customers).
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function getCustomers(connectionId: string) {
return await sdk.accounting.listAccountingContacts({
connectionId,
});
}
```
The connection_id comes in from your end user's authorization of their accounting app and is stored on your end. You can watch our to learn more.
If you need to create a new customer, use our `createCustomer` [API endpoint](https://docs.unified.to/accounting/customer/Create_a_customer).
A `Customer` will have specific fields such as `name` , `emails`, `billing_address`, and tax information that will be relevant to your software. See our [API Documentation page](https://docs.unified.to/accounting/customer/model) for more information.

**Products**
Much like getting a list of existing customers, it is easy to get a list of existing products in your end user's accounting app using the `listItems` API endpoint. We call a product, an `Item`, and you can find the full data-model on our [API Documentation page](https://docs.unified.to/accounting/item/model).
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function getItems(connectionId: string) {
return await sdk.commerce.listCommerceItems({
connectionId,
});
}
```
**Chart of accounts & tax rates**
Each line item in an invoice will be associated with a customer, item, account and tax rate in your end user's accounting application.
You can get a list of accounts by calling our `listAccounts` [API endpoint](https://docs.unified.to/accounting/account/List_all_accounts) which returns a list from their 'Chart of Accounts'.
You can also get a list of tax rates by calling our `listTaxrates` [API endpoint](https://docs.unified.to/accounting/taxrate/List_all_taxrates).
**Invoices**
Once the end user has created an invoice, you will want to send that invoice into their accounting system as well.
This is easily accomplished by using the `createInvoice` [API endpoint](https://docs.unified.to/accounting/invoice/Create_a_invoice).
```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { AccountingInvoice } from '@unified-api/typescript-sdk/dist/sdk/models/shared';
const sdk = new UnifiedTo({
security: {
jwt: '',
},
});
export async function createInvoice(connectionId: string, invoice: AccountingInvoice) {
const result = await sdk.accounting.createAccountingInvoice({
accountingInvoice: invoice,
connectionId,
});
return result.accountingInvoice?.id;
}
```
## Step 2: Paying invoices
Once that invoice is paid, you will need to communicate this to the accounting application. This is accomplished by using the `createPayment` [API endpoint](https://docs.unified.to/accounting/payment/Create_a_payment) which associates a payment with an invoice.
At a minimum, a `Payment` will need an `invoice_id` , a `customer_id` and an `amount`. Some integrations may require additional values to be present, so check that [integration's feature support page](https://app.unified.to/integrations/quickbooks?tab=support). For example, Quickbooks also requires a `currency` field.
## Conclusion
It's easy to support multiple accounting applications in your invoicing app and allow your end user to seamlessly move data between the two. By using Unitied.to's [Unified Accounting API](https://unified.to/accounting) you can add accounting integrations to your product and support your invoicing use case within days.
## Additional Resources
- [Unified Accounting API](https://unified.to/accounting)
- [API Documentation for the Unified Accounting API](https://docs.unified.to/accounting/overview)
- [SDKs for Unified.to](https://docs.unified.to/overview/sdks)
-
-
## How to build Enterprise Search using RAG
URL: https://docs.unified.to/guides/how_to_build_enterprise_search_using_rag
# How to build Enterprise Search using RAG
------
_November 19, 2025_
In this article, we will be going into detail on how to build an enterprise search application as well as a Q&A bot using a RAG pipeline with Unified's data integrations and OpenAI embedding models.
[RAG](https://developer.nvidia.com/blog/rag-101-demystifying-retrieval-augmented-generation-pipelines/) or Retrieval-Augmented-Generation is a powerful approach that can be leveraged to take a large amount of information and index it efficiently so that it can be searched quickly. This design pattern is very beneficial for enterprise search, Q&A bots, or any agent. A Q&A bot can be used to answer questions across a company's knowledge base, while an enterprise search application can be used to search corporate information from any internal data source.
We will be using a few of Unified.to's integrations to access internal company knowledge in [file storage](https://unified.to/storage) (eg. Google Drive) and [knowledge management systems](https://unified.to/kms) (eg. Notion), embed their content using [OpenAI's models](https://unified.to/genai), and then store them in a vector DB and used to answer questions.
## **Prerequisites**
- Node.js 19+
- Unified account with integrations enabled for Google Drive and Notion
- Unified.to API key and GenAI Connection ID
- OpenAI API key (As we are using OpenAI for embedding)
- A vector DB (eg. Pinecone, or Chroma. For the demo we are using a in memory vector store)
## **Step 1: Setting your project up**
```typescript
mkdir rag-bot
cd rag-bot
npm init -y
npm install dotenv @unified-api/typescript-sdk
```
Add your credentials and keys to the `.env`:
```plain text
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_GOOGLEDRIVE=your_gdrive_connection_id
CONNECTION_NOTION=your_notion_connection_id
CONNECTION_GENAI=your_genai_connection_id
```
## **Step 2: Initialize the SDK**
```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
const { UNIFIED_API_KEY, CONNECTION_GOOGLEDRIVE, CONNECTION_NOTION, CONNECTION_GENAI } = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
```
## **Step 3: Fetch all the files from Google Drive**
We will now use Unified's storage API to retrieve files from a Google Drive.
```typescript
async function getAllGoogleDriveFiles(connectionId: string) {
const files: any[] = [];
let offset = 0;
const limit = 100;
let hasMore = true;
while (hasMore) {
const response = await sdk.storage.listStorageFiles({
connectionId,
offset,
limit,
});
pages.push(...(response.files || []));
hasMore = (response.files?.length || 0) === limit;
offset += limit;
}
return files;
}
const file = await sdk.storage.getStorageFile({ connectionId, id: fileId });
const downloadUrl = file.download_url;
const content = await fetch(downloadUrl).then(res => res.text());
return content;
}
```
## **Step 4: Fetch Notion Pages**
Now that we have the data from Google Drive, we will use Unified's connection to Notion to fetch data from Notion as well.
```typescript
async function getAllNotionPages(connectionId: string) {
const pages: any[] = [];
let offset = 0;
const limit = 100;
let hasMore = true;
while (hasMore) {
const response = await sdk.storage.listStorageFiles({
connectionId,
offset,
limit,
});
files.push(...(response.files || []));
hasMore = (response.files?.length || 0) === limit;
offset += limit;
}
return pages;
}
async function getNotionPageContent(connectionId: string, pageId: string) {
const page = await sdk.kms.getKmsPage({
connectionId,
id: pageId,
});
const content = await fetch(page.download_url).then(r => r.text());
return content;
}
```
## **Step 5: Building the vector DB**
For the purpose of the demo, we will be creating a simple in memory vector store. But for production, you would use a cloud-based vector database like Pinecone, Chroma or Weaviate.
```typescript
type Document = { id: string; source: string; content: string; embedding?: number[] };
const vectorDB: Document[] = [];
```
## **Step 6: Embedding the documents**
For the purpose of the example, we will be using OpenAI as the embedding provider in this step. Unified has [many integrations](https://unified.to/genai) that support embedding, but you can also use a single provider's SDK. Please remember to chunk the content properly and not embed the entire content at once.
### Chunking strategies:
Chunking large documents effectively is super important to maintain context and relevance. Here are a few best practices for chunking:
1. **Semantic Chunking**: Instead of splitting by fixed lengths, break the document into chunks based on natural language boundaries, like paragraphs or sections, ensuring each chunk is coherent and meaningful.
2. **Overlap Between Chunks**: Include a slight overlap between adjacent chunks. This helps maintain continuity and context, especially for complex ideas that span multiple chunks.
3. **Fixed-Length Chunking**: If you use fixed-length chunking, aim for a length that balances between too small (losing context) and too large (losing efficiency). Typically, a few hundred tokens per chunk works well.
4. **Using Document Structure**: If your document has headings, subheadings, or other structural elements, use those to guide the chunking. This ensures that each chunk is logically complete.
5. **Contextual Embeddings**: Sometimes, you can embed the entire document first and then create embeddings for chunks that are derived from the document's embedding, ensuring that each chunk stays contextually relevant.
6. **Pre-processing and Cleaning**: Before chunking, clean up the text by removing unnecessary whitespace, special characters, or any irrelevant information to improve the quality of the embeddings.
By following these strategies, you'll maintain the semantic integrity of the document and ensure that the vector database can retrieve meaningful context.
There are several popular libraries that help with chunking and embedding in a streamlined way. A few notable ones include:
1. **LangChain**: This is a popular framework that integrates various language models and vector databases. It provides built-in utilities for chunking, embedding, and storing data in vector stores.
2. **Haystack**: Developed by deepset, Haystack is another robust framework for building search pipelines. It supports intelligent chunking, document retrieval, and integration with vector databases like FAISS, Milvus, and Pinecone.
3. **Transformers and Datasets from Hugging Face**: Hugging Face offers tools that can help with both embedding and chunking. You can use their Transformers library for embedding and their Datasets library to preprocess and chunk documents.
4. **SentenceTransformers**: This library is great for generating embeddings and can be combined with custom chunking logic to break documents into meaningful pieces before embedding.
5. **GPT-Index (formerly LlamaIndex)**: This library is designed to help build large language model applications and provides utilities for chunking, indexing, and querying documents effectively.
These libraries often provide a lot of built-in functionality, making it easier to handle complex documents and ensure that your embeddings and vector storage are efficient and meaningful.
```typescript
import { Configuration, OpenAIApi } from "openai";
const openai = new OpenAIApi(new Configuration({ apiKey: process.env.OPENAI_API_KEY }));
async function embedText(text: string): Promise {
const response = await openai.createEmbedding({
model: "text-embedding-3-large", //You can choose to use text-embedding-3-small too
input: text,
});
return response.data.data[0].embedding;
}
```
## **Step 7: Indexing Everything**
Now that we have all the data collected, it is time to index all of it!
When you store vectors in a vector database like Pinecone or Weaviate, you can attach `metadata` to each vector. This metadata can include things like a unique ID, the original text, source information, or any other relevant attributes.
When you perform a similarity search and get the top matching vectors, the database returns the metadata along with the vectors. That way, you can easily link back to the original content.
For instance, when you index a piece of text, you'd store the vector along with metadata like a document ID, the title, or even the source URL. When you retrieve the nearest neighbors, the metadata is returned, and you can use it to identify or fetch the original content from your database or any external source.
```typescript
async function indexAllContent() {
// Google Drive
const gdriveFiles = await getAllGoogleDriveFiles(CONNECTION_GOOGLEDRIVE!);
for (const file of gdriveFiles) {
const content = await getFileContent(CONNECTION_GOOGLEDRIVE!, file.id);
const embedding = await embedText(content);
vectorDB.push({
id: file.id,
source: "gdrive",
content,
embedding,
filename: file.name,
date_created: file.created_at,
owner_id: file.user_id,
});
}
// Notion
const notionPages = await getAllNotionPages(CONNECTION_NOTION!);
for (const page of notionPages) {
const content = await getNotionPageContent(CONNECTION_NOTION!, page.id);
const embedding = await embedText(content);
vectorDB.push({ id: page.id, source: "notion", content, embedding });
}
}
```
## **Step 8: Finding Answers**
Now that we have the data ready, it is time to implement the Bot or enterprise search that will use this data to answer questions from a user.
The basic flow would be:
1. Embed the question asked
2. Find similar docs in the Vector DB
3. Send the top `n` amount of docs as context paired with the question to Unified's GenAI endpoint.
4. Return the answer to the user.
For the purpose of the example, we will be sending the top 3 most similar docs.
```typescript
function cosineSimilarity(a: number[], b: number[]) {
const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
const normA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
const normB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
return dot / (normA * normB);
}
async function answerQuestion(question: string) {
const questionEmbedding = await embedText(question);
// For the purpose of the example we will be sending the top 3 docs
const scored = vectorDB.map(doc => ({
...doc,
score: cosineSimilarity(questionEmbedding, doc.embedding!)
}));
const topDocs = scored.sort((a, b) => b.score - a.score).slice(0, 3);
const context = topDocs.map(doc => doc.content).join('\n---\n');
const prompt = `
You are a helpful company knowledge bot. Use the following context to answer the question.
Context:
${context}Question:
${question}Answer in a concise and accurate way.
`;
const result = await sdk.genai.createGenaiPrompt({
connectionId: CONNECTION_GENAI!,
prompt: {
messages: [{ role: "USER", content: prompt }],
maxTokens: 256,
temperature: 0.2
}
});
return result.choices?.[0]?.message?.content || "No answer generated.";
}
```
## **Step 9: Usage**
```typescript
(async () => {
await indexAllContent();
const answer = await answerQuestion("How do I request vacation days?");
console.log("Bot answer:", answer);
})();
```
At Unified, most of our customers are using this simple RAG Pipeline flow to add AI-powered intelligence to their application. It can be used across industries and verticals as Unified supports 21 B2B SaaS categories and more than 350 integrations to retrieve end-customer data from.
## How to configure Bullhorn Redirect URI for creating a connection
URL: https://docs.unified.to/guides/how_to_configure_bullhorn_redirect_uri_for_creating_a_connection
# How to configure Bullhorn Redirect URI for creating a connection
------
_April 8, 2026_
When connecting Bullhorn to Unified, each customer must complete a one-time setup in their own Bullhorn account.
## Why this is required?
Bullhorn requires every connected application to use its own API user, client ID, client secret, and approved redirect URI. The redirect URI cannot be updated through the Bullhorn web interface, so Bullhorn Support must be contacted to approve and configure the redirect URI.
## Steps your customer must complete
### 1. Create an API User in Bullhorn
The customer must create a dedicated API user for the application they want to connect.
During this process, Bullhorn will provide:
- Client ID
- Client Secret
These credentials will later be used when creating the connection in Unified.
**Note**: A new API user is required each time a new application is connected to Bullhorn.
### 2. Contact Bullhorn Support
After creating the API user, the customer must contact Bullhorn Support and request that the redirect URI for the API user be updated.
The customer should provide:
- The API user or application they created
- The Unified redirect URI
Example request:
Please update the redirect URI for our Bullhorn API user to the following Unified redirect URI: **`https://api.unified.to/oauth/code`**
**Note**: if you are using CNAME (custom domain) the redirect URI will look like this **`https://YOUR_CUSTOM_DOMAIN/oauth/code`**
### 3. Wait for Bullhorn to approve and apply the Redirect URI change
Bullhorn does not currently allow redirect URIs to be configured through the Bullhorn web UI.
Because of this, the redirect URI change must be completed by Bullhorn Support before the connection can work.
### 4. Create the Connection in Unified
Once Bullhorn Support confirms that the redirect URI has been configured on your customer's account, you can then proceed with creating a Bullhorn connection.
## Important Notes
- Every customer must request the redirect URI change individually for their own Bullhorn account.
- This is a Bullhorn limitation and cannot be configured directly in the Bullhorn UI or programmatically via Unified.
- This is a one-time setup for each of your customers using Bullhorn
- If the customer creates a new API user in the future, they will need to go through this process again.
## How to configure webhooks in HubSpot
URL: https://docs.unified.to/guides/how_to_configure_webhooks_in_hubspot
# How to configure webhooks in HubSpot
------
_February 23, 2024_
This guide walks you through how to configure webhooks in HubSpot to work with Unified.to. These webhooks allow you to get notifications about new events and changes from HubSpot e.g. new leads, updated deals, or deleted contacts.
## Before you begin
- Make sure you have a HubSpot developer account.
- Get your client ID, client secret, and developer API key.
- Point the **Redirect URL** in your HubSpot app to `api.unified.to/oauth/code`
- If you're using the EU data region, use instead`api-eu.unified.to/oauth/code`
- Follow our [HubSpot developer guide](https://docs.unified.to/guides/how_to_register_a_hubspot_developer_app_and_get_your_oauth_2_credentials#how-to-register-a-hubspot-developer-app-and-get-your-oauth-2-credentials) to see how to do all of the above.
## Setting up Webhooks in Hubspot Legacy Apps
### Select the appropriate Legacy App to setup the webhook

### Set the target URL for the webhook
HubSpot uses a single webhook URL per app, which means you need to configure your app to send any events to the Unified.to servers.
1. Navigate to your [HubSpot developer account](https://app.hubspot.com/).
2. Click **Apps.**
3. Select the app you wish to use to access your customers' data.
4. Click **Webhooks.**

5. Under **Target URL**:
1. If you are using the US data region, copy and paste in: `api.unified.to/webhook/workspace/hubspot`
2. If you using the EU data region, copy and paste in: `api-eu.unified.to/webhook/workspace/hubspot`
If your URL is greyed out and you have subscriptions under **Event subscriptions**, then you'll need to unsubscribe from all of them. _WARNING: If these subscriptions and the target URL are used by other applications, this action will break them. In that case, you may want to consider creating a new developer app to use with Unified.to._
6. You do **not** have to create any subscriptions manually. Unified.to will do that on your behalf.
### Add scopes for your webhooks
In order for Unified.to to receive webhook data from HubSpot, you need to add scopes for the events you are interested in as well as a few others.
1. With your app page still open, click **Basic Info** and then click **Auth.**
2. Scroll down to **Scopes**:
1. Add `crm.objects.owners.read`as **Required**.
2. For any events where you want to read data, add them as **Required.**
1. Note: This applies to events that you'll be subscribed to as well as those that you will only call via the API. For example, if you want to create a Contacts webhook and also fetch Companies via the API, you'll need to add both `crm.objects.contacts.read` and `crm.objects.companies.read` as **Required** because Unified.to will pass all of these as required scopes in each auth flow.
3. For any events where you want to write data, add them as **Optional**.
4. For example, if you want to receive notifications about updates to HubSpot companies, you'll need to add `crm.objects.companies.read` as **Required**. If you want to write data to companies, add `crm.objects.companies.write` as **Optional**.
5. To see the full list of HubSpot scopes that Unified.to supports, see: [https://app.unified.to/integrations/hubspot?tab=oauth2](https://app.unified.to/integrations/hubspot?tab=oauth2).
3. The `oauth` scope is also required for all HubSpot integrations. This will be added by default if your app was created after April 2024.

### Select permission scopes on Unified.to
When prompting your customers to authorize access to their third-party accounts, you will need to select the corresponding scopes in [Unified.to](https://unified.to/) as well. The following steps show you how to do that when using our Embedded Authorization component.
1. On app.unified.to, click on **Settings** and then [**Embedded Authorization**](https://app.unified.to/settings/embed?tab=Authorization)**.**
2. You will see a preview of the Embedded Authorization component on the left and a list of configuration options on the right. Click on **Options**.
3. Scroll down to **Permission scopes** and select **webhook** as well as any other permission scopes that you need e.g. the same scopes you added in HubSpot. You do not need to select `oauth` or `crm.objects.owners.read` on [Unified.to](https://unified.to/) as well will map these automatically on our end.
1. For example, using the examples from above for reading Contacts and Companies, the scopes in Unified.to would be `crm_contact_read` and `crm_company_read`

2. A mapping of Unified scopes to their HubSpot counterparts can be found [here](https://app.unified.to/integrations/hubspot?tab=oauth2).
3. Tip: Under **Integration categories,** select **CRM** to only see scopes related to CRM data objects.
## Setting up Webhooks in Hubspot Development Platform
### Create Webhook configuration file
In your hubspot project folder, create a new folder `webhook` and inside add in the `webhook-hsmeta.json` file.
```javascript
project-folder/
└── src/
└── app/
├── app-hsmeta.json
└── webhooks/
└── webhook-hsmeta.json
```
### **Setup Webhook configuration**
Now that you have the webhook config file ready, let's setup the configuration as follows:
```javascript
{
"uid": "webhooks",
"type": "webhooks",
"config": {
"settings": {
"targetUrl": "https://api.unified.to/webhook/workspace/hubspot",
"maxConcurrentRequests": 10
},
"subscriptions": {
"legacyCrmObjects": [
{
"subscriptionType": "deals.propertyChange",
"propertyName": "dealstage",
"active": true
},
{
"subscriptionType": "deals.associationChange",
"active": true
}
]
}
}
}
```
For the `targetUrl` :
1. If you are using the US data region, copy and paste in: `api.unified.to/webhook/workspace/hubspot`
2. If you using the EU data region, copy and paste in: `api-eu.unified.to/webhook/workspace/hubspot`
For the `subscriptions` we need to use the `legacyCrmObjects` to define which object we want to get events for.
### Deploy Hubspot Project
Once you've setup your webhook configuration, you can now upload the project via:
```javascript
hs project upload
```
Once uploaded, your configuration should now show up in your app's webhook list:

## See also
- [Introduction to webhooks](https://docs.unified.to/concepts/webhooks#introduction-to-webhooks)
- [Create a webhook subscription](https://docs.unified.to/unified/webhook/Create_webhook_subscription)
- [How to set up your scopes in HubSpot](https://docs.unified.to/guides/how_to_set_up_your_scopes_in_hubspot)
## How to Connect Anthropic to Real-Time SaaS Data with Unified.to MCP Server
URL: https://docs.unified.to/guides/how_to_connect_anthropic_to_real_time_saas_data_with_unified_mcp_server
# How to Connect Anthropic to Real-Time SaaS Data with Unified.to MCP Server
------
_August 28, 2025_
Anthropic's Claude models can power sophisticated workflows — but most products still need access to live customer data from CRMs, ATSs, HRIS, or accounting systems. That usually means writing brittle glue code, normalizing APIs, and maintaining webhook jobs.
With Unified.to's MCP server, you can skip that complexity. Unified.to exposes 317+ SaaS integrations as real-time, callable tools that Claude can use directly — no custom integration logic required.
In this guide, we'll walk through how to connect Anthropic to Unified.to MCP so your application can give Claude real-time access to customer SaaS data and actions.
## Authentication
Every request to the Unified.to MCP server must include a token. You can pass this either as a URL parameter (`?token=...`) or in the `Authorization: bearer {token}` header.
There are two authentication flows:
- **Private (workspace key + connection)**
Use your Unified.to workspace API key and add a `connection` parameter. This should never be exposed publicly.
- **Public (end-user safe)**
Generate a token in the format `{connectionID}-{nonce}-{signature}` using your workspace secret. Safe to share with customers.
## Integrating with Anthropic API
**Manual Tool Orchestration:**
1. Call Unified's `/tools` endpoint and pass the tool list to Claude:
```python
resp = client.messages.create(
model="claude-3-5-sonnet-20241022",
tools=tools,
input="list the candidates and then analyse the resumes from their applications",
)
```
2. Claude will return a `tool_use` block:
```json
[
{
"type": "tool_use",
"id": "toolu_...",
"name": "list_candidates",
"input": { "limit": "100" }
}
]
```
3. Call Unified's `/tools/{id}/call` endpoint with the arguments.
4. Return the result to Claude as a `tool_result` block in your next message:
```json
[
{
"type": "tool_result",
"tool_use_id": "toolu_...",
"content": "..."
}
]
```
## Controlling Tool Access
Unified.to MCP gives you granular control over how tools are exposed to Anthropic:
- `permissions` → restrict available scopes.
- `tools` → allowlist specific tool IDs.
- `aliases` → add synonyms so Claude better matches tool names.
- `hide_sensitive=true` → automatically strip PII (emails, phone numbers, etc).
- `include_external_tools=true` → expose all vendor API endpoints, not just Unified.to's normalized models.
These options help you keep Anthropic outputs predictable and secure in production-grade workflows.
### Sample Snippet
```javascript
import Anthropic from '@anthropic-ai/sdk';
// Claude model version
const modelVersion = 'latest';
// Unified connection Id that you want to have the mcp to use tools for
const connection = 'UNIFIED_CONNECTION_ID';
// location from where your unified account was created
const dc = 'us';
// Optional: list of specific tools that you want to use
const toolIds = ['get_messaging_message','list_messaging_message']
const params = new URLSearchParams({
token: process.env.UNIFIED_API_KEY || '',
connection,
dc,
include_external_tools: includeExternal ? 'true' : 'false',
});
if (toolIds.length > 0) {
params.append('tools', toolIds.join(','));
}
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const serverUrl = `${process.env.UNIFIED_MCP_URL}/sse?${params.toString()}`;
let latestModel;
if (modelVersion === 'latest') {
// get the latest model from anthropic
const models = await anthropic.models.list();
latestModel = models.data[0].id;
} else {
latestModel = modelVersion;
}
const completion = await anthropic.beta.messages.create({
model: latestModel,
max_tokens: 1024,
messages: [
{
role: 'user',
content: message,
},
],
stream: false,
mcp_servers: [
{
type: 'url',
url: `${process.env.UNIFIED_MCP_URL}/sse?${params.toString()}`, // change url as needed
name: 'unifiedMCP',
},
],
betas: ['mcp-client-2025-04-04'],
});
console.log('response', JSON.stringify(completion, null, 2));
```
## Coverage and Infrastructure
Unified.to MCP is built for real-world AI use cases:
- **20,421+ real-time tools** (growing weekly)
- **335+ integrations across 21 categories** (ATS, CRM, HRIS, Accounting, Messaging, File Storage, and more)
- **Zero-storage architecture** — no caching, no liability
- **Scoped security controls** — permissions, aliases, and PII redaction
- **Multi-region deployment** — US, EU, and AU data centers for compliance
Unified.to MCP works across all major LLM providers: **OpenAI, Anthropic, Google Gemini, and Cohere.** That means you can build once and connect to any agent client.
[Explore the MCP docs](https://docs.unified.to/mcp?utm_source=chatgpt.com) or [book a demo](https://docs.unified.to/mcp?utm_source=chatgpt.com)
> _Note: Unified.to MCP is currently in beta and should not be used in production systems yet. Contact us if you'd like to explore production use._
## How to Connect Cohere to Real-Time SaaS Data with Unified.to MCP Server
URL: https://docs.unified.to/guides/how_to_connect_cohere_to_real_time_saas_data_with_unified_mcp_server
# How to Connect Cohere to Real-Time SaaS Data with Unified.to MCP Server
------
_August 28, 2025_
Cohere's LLMs can power rich workflows — but most products still need access to live customer data from CRMs, ATSs, HRIS, or accounting systems. That usually means writing brittle glue code, normalizing APIs, and maintaining webhook jobs.
With Unified.to's MCP server, you can skip that complexity. Unified.to exposes 317+ SaaS integrations as real-time, callable tools that Cohere can use directly — no custom integration logic required.
In this guide, we'll walk through how to connect Cohere to Unified.to MCP so your application can give Cohere real-time access to customer SaaS data and actions.
## Authentication
Every request to the Unified.to MCP server must include a token. You can pass this either as a URL parameter (`?token=...`) or in the `Authorization: bearer {token}` header.
There are two authentication flows:
- **Private (workspace key + connection)**
Use your Unified.to workspace API key and add a `connection` parameter. This should never be exposed publicly.
- **Public (end-user safe)**
Generate a token in the format `{connectionID}-{nonce}-{signature}` using your workspace secret. Safe to share with customers.
## Integrating [Unified.to](https://unified.to/) MCP Server with Cohere
Cohere's chat API can directly consume tools defined by Unified.to MCP.
**Manual Tool Orchestration:**
1. Call Unified's `/tools?type=cohere` endpoint and pass the tool list to Cohere:
```python
response = co.chat(
model="command-a-03-2025", messages=messages, tools=tools
)
```
2. When Cohere requests a tool call, call Unified's `/tools/{id}/call` endpoint.
3. Pass the tool result back to Cohere in your next message
### Example Prompt and Response
**Prompt:**
```plain text
"Score this candidate for the Software Engineer job."
```
**What happens:**
- Cohere discovers the available tools from Unified MCP (e.g., `fetch-candidate`, `fetch-job`, `score-candidate`).
- Cohere calls `fetch-candidate` and `fetch-job` tools to get the data.
- Cohere calls `score-candidate` with the data.
- Cohere returns a response like:
**Response:**
```plain text
Candidate Jane Doe scored 92/100 for the Software Engineer job. Strengths: Python, distributed systems. Recommended for interview.
```
## Advanced MCP Options
Unified.to MCP gives you granular control over how tools are exposed to Cohere:
- `permissions` → restrict available scopes.
- `tools` → allowlist specific tool IDs.
- `aliases` → add synonyms so Cohere better matches tool names.
- `hide_sensitive=true` → automatically strip PII (emails, phone numbers, etc).
- `include_external_tools=true` → expose all vendor API endpoints, not just Unified.to's normalized models.
These options help you keep Cohere outputs predictable and secure in production-grade workflows.
### Sample Snippet
```javascript
import { CohereClientV2 } from 'cohere-ai';
// Unified connection Id that you want to have the mcp to use tools for
const connection = 'UNIFIED_CONNECTION_ID';
// location from where your unified account was created
const dc = 'us';
// Optional: list of specific tools that you want to use
const toolIds = ['get_messaging_message','list_messaging_message']
const cohereClient = new CohereClientV2({
token: process.env.COHERE_API_KEY || '',
});
const params = new URLSearchParams({
token: process.env.UNIFIED_API_KEY || '',
connection,
type: 'cohere',
dc,
include_external_tools: includeExternal ? 'true' : 'false',
});
if (toolIds.length > 0) {
params.append('tools', toolIds.join(','));
}
const tools = await fetch(`${process.env.UNIFIED_MCP_URL}/tools?${params.toString()}`);
const toolsJson = await tools.json();
const completion = await cohereClient.chat({
model: 'command-a-03-2025',
messages: [
{
role: 'user',
content: message,
},
],
tools: toolsJson,
});
for (const toolCall of completion?.message?.toolCalls || []) {
// call mcp server with toolCallId
const toolCallResponse = await fetch(`${process.env.UNIFIED_MCP_URL}/mcp/tools/${toolCall.function?.name}/call?${params.toString()}`, {
method: 'POST',
body: toolCall.function?.arguments || '{}',
});
const toolCallResponseJson = await toolCallResponse.json();
console.log(JSON.stringify(toolCallResponseJson, null, 2));
}
```
## The Most Complete MCP Server for AI-Native Teams
Unified.to MCP is the most complete hosted MCP server available today:
- **20,082+ real-time tools** (growing weekly)
- **335+ integrations across 21 categories** (ATS, CRM, HRIS, Accounting, Messaging, File Storage, and more)
- **Zero-storage architecture** — no caching, no liability
- **Scoped security controls** — permissions, aliases, and PII redaction
- **Multi-region deployment** — US, EU, and AU data centers for compliance
This ensures your Cohere workflows aren't just demos — they're secure, scalable, and designed for production-grade use cases.
## Next Steps
Unified.to MCP works across all major LLM providers: **OpenAI, Anthropic, Google Gemini and Cohere.** That means you can build once and connect to any agent client.
[Explore the MCP docs](https://docs.unified.to/mcp?utm_source=chatgpt.com) or [book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified?utm_source=chatgpt.com) to see it live in action.
> _Note: Unified.to MCP is currently in beta and should not be used in production systems yet. Contact us if you'd like to explore production use._
## How to Connect Google Gemini to Real-Time SaaS Data with Unified.to MCP Server
URL: https://docs.unified.to/guides/how_to_connect_google_gemini_to_real_time_saas_data_with_unified_mcp_server
# How to Connect Google Gemini to Real-Time SaaS Data with Unified.to MCP Server
------
_August 28, 2025_
Google Gemini can power powerful workflows — but most products still need access to **live customer data** from CRMs, ATSs, HRIS, or accounting systems. That usually means writing brittle glue code, normalizing APIs, and maintaining webhook jobs.
With **Unified.to's MCP server**, you can skip that complexity. Unified.to exposes 317+ SaaS integrations as **real-time, callable tools** that Gemini can use directly — no custom integration logic required.
In this guide, we'll walk through how to connect Gemini to Unified.to MCP so your application can give Gemini real-time access to customer SaaS data and actions.
## Authentication
Every request to the Unified.to MCP server must include a token. You can pass this either as a URL parameter (`?token=...`) or in the `Authorization: bearer {token}` header.
There are two authentication flows:
- **Private (workspace key + connection)**
Use your Unified.to workspace API key and add a `connection` parameter. This should never be exposed publicly.
- **Public (end-user safe)**
Generate a token in the format `{connectionID}-{nonce}-{signature}` using your workspace secret. Safe to share with customers.
## Integrating with Google Gemini API
**Manual Tool Orchestration:**
1. Call Unified's `/tools` endpoint and pass the tool list as `function_declarations` to Gemini.
2. Gemini will return a function call request:
```plain text
function_call {
name: "list_candidates"
args { fields { key: "limit" value { string_value: "100" } } }
}
```
3. Call Unified's `/tools/{id}/call` endpoint.
4. Respond to Gemini with the tool result as a `functionResponse` in your next message:
```json
{
"role": "user",
"parts": [
{
"functionResponse": {
"name": "list_candidates",
"response": { ... }
}
}
]
}
```
## Controlling Tool Access
Unified.to MCP gives you granular control over how tools are exposed to Gemini:
- `permissions` → restrict available scopes.
- `tools` → allowlist specific tool IDs.
- `aliases` → add synonyms so Gemini can perform better matches for tool names.
- `hide_sensitive=true` → automatically strip PII (emails, phone numbers, name, gender, etc).
- `include_external_tools=true` → expose all vendor API endpoints, not just Unified.to's normalized models.
These options help you keep Gemini outputs predictable and secure in production-grade workflows.
### Sample Snippet:
```javascript
import { GoogleGenAI, mcpToTool } from '@google/genai';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
// Construct a new GEMINI client via GoogleGenAI
// Gemini model version
const modelVersion = 'latest';
// Unified connection Id that you want to have the mcp to use tools for
const connection = 'UNIFIED_CONNECTION_ID';
// location from where your unified account was created
const dc = 'us';
// Optional: list of specific tools that you want to use
const toolIds = ['get_messaging_message','list_messaging_message']
const gemini = new GoogleGenAI({
apiKey: process.env.GEMINI_API_KEY || '',
});
// Construct the required params
const params = new URLSearchParams({
token: process.env.UNIFIED_API_KEY || '',
connection,
type: 'gemini',
dc,
include_external_tools: true, // set to false if you don't want to include external tools
});
if (toolIds.length > 0) {
params.append('tools', toolIds.join(','));
}
const serverUrl = `${process.env.UNIFIED_MCP_URL}/mcp?${params.toString()}`;
const transport = new StreamableHTTPClientTransport(new URL(serverUrl));
const client = new Client({
name: 'unified-mcp',
version: '1.0.0',
});
await client.connect(transport);
let latestModel;
// Optional
if (modelVersion === 'latest') {
// get the latest model from gemini
const models = await gemini.models.list();
latestModel = models.page.filter((model: any) => model.name.includes('gemini') && !model.name.includes('embedding')).pop()?.name || 'gemini-2.0-flash';
} else {
latestModel = modelVersion;
}
const completion = await gemini.models.generateContent({
model: latestModel?.replace('model/', ''),
contents: message,
config: {
tools: [mcpToTool(client)],
},
});
for (const chunk of completion?.candidates || []) {
console.log(JSON.stringify(chunk, null, 2));
if (chunk.content?.parts) {
for (const part of chunk.content.parts) {
if (part.text) {
console.log(part.text);
}
}
}
}
```
## Coverage and Infrastructure
Unified.to MCP is built for real-world AI use cases:
- **20,421+ real-time tools** (growing weekly)
- **335+ integrations across 21 categories** (ATS, CRM, HRIS, Accounting, Messaging, File Storage, and more)
- **Zero-storage architecture** — no caching, no liability
- **Scoped security controls** — permissions, aliases, and PII redaction
- **Multi-region deployment** — US, EU, and AU data centers for compliance
Unified.to MCP works across all major LLM providers: **OpenAI, Anthropic, Google Gemini, and Cohere.** That means you can build once and connect to any agent client.
- [Explore the MCP docs](https://docs.unified.to/mcp?utm_source=chatgpt.com)
- [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified?utm_source=chatgpt.com)
> _Note: Unified.to MCP is currently in beta and should not be used in production systems yet. Contact us if you'd like to explore production use._
## How to Connect LLMs to Real-Time SaaS Data with Unified.to MCP Server
URL: https://docs.unified.to/guides/how_to_connect_llms_to_real_time_saas_data_with_unified_mcp_server
# How to Connect LLMs to Real-Time SaaS Data with Unified.to MCP Server
------
_August 25, 2025_
[Unified's MCP](https://docs.unified.to/mcp) server allows your application to give an LLM API real-time access to your customers' SaaS data and perform actions on that data — without you having to write custom business-logic code for every integration, which can often make things messy.
For example, imagine you're building a [candidate assessment workflow](https://docs.unified.to/guides/how_to_build_a_candidate_assessment_product_with_unified), we'd usually call the API to fetch the data, and feed it into the LLM for it to analyze that data. With the Unified MCP server, we can enable OpenAI (or another LLM API) to directly access the data and perform actions making the workflow much simpler and cleaner.
## The Flow
Here's how an LLM API connects to Unified's MCP server:
- Your app connects to the MCP server using the HTTP endpoint.
- You authenticate using you [Unified.to](https://unified.to/) API token generated and provider one of your customer's connection ID.
- The LLM can then discover and call tools (like 'fetch candidate', 'score candidate', 'update job status') in real time.
## Authentication
You must provide a token to the MCP server as a URL parameter (`?token={token}`) or in the Authorization header (`Authorization: bearer {token}`).
**Private (Direct LLM API):**
Use your [Unified.to](https://unified.to/) workspace API key as the token, and include a `connection` parameter for the customer's connection ID.
_Example:_
```plain text
&connection=
```
_Note: Do not expose this token publicly._
## Building the application with an LLM API and MCP
If your application is using an LLM API that supports MCP (like OpenAI), you can use the [`mcp-use`](https://github.com/mcp-use/mcp-use) Python package to connect to Unified MCP and access your customer's data and actions.
### Dependencies
The full list of dependencies and setup you need to do to get things running.
```bash
mkdir unified-mcp-client
cd unified-mcp-client
python -m venv .venv
source .venv/bin/activate
pip install mcp-use python-dotenv requests openai
touch client.py
```
### Setting up your secrets
Add your Unified API key, your customer's connection ID, and your workspace secret to a `.env` file:
```bash
echo "UNIFIED_API_KEY=" >> .env
echo "CONNECTION_ID=" >> .env
echo ".env" >> .gitignore
```
# The MCP Client Class
Here's an example using the `mcp-use` package:
```python
# client.py
import os
import hashlib
import secrets
from dotenv import load_dotenv
from mcp_use import MCPClient
load_dotenv()
CONNECTION_ID = os.getenv("CONNECTION_ID")
TOKEN = os.getenv("API_KEY")
MCP_URL = f""
client = MCPClient(MCP_URL)
# List available tools
tools = client.list_tools()
print("Available tools:", [tool['id'] for tool in tools])
# Call a tool (example: call the first tool with no arguments)
if tools:
tool_id = tools[0]['id']
result = client.call_tool(tool_id, {})
print("Tool result:", result)
```
## Integrating with OpenAI (LLM API)
Once your MCP client is set up, you can connect it to OpenAI's API, which natively supports remote MCP servers.
### Configure OpenAI to Use Unified MCP
When you send a chat completion request, specify the MCP server as a tool source.
Here's a **candidate assessment example**:
```python
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "Score this candidate for the Software Engineer job."}
],
tools=[{
"type": "mcp",
"url": MCP_URL, # The Unified MCP URL with your token
}],
tool_choice="auto"
)
print(response.choices[0].message.content)
```
**No backend glue code required—OpenAI orchestrates the tool calls via Unified MCP.**
## Integrating with Anthropic API
**Manual Tool Orchestration:**
1. Call Unified's `/tools` endpoint and pass the tool list to Claude:
```python
resp = client.messages.create(
model="claude-3-5-sonnet-20241022",
tools=tools,
input="list the candidates and then analyse the resumes from their applications",
)
```
2. Claude will return a `tool_use` block:
```json
[
{
"type": "tool_use",
"id": "toolu_...",
"name": "list_candidates",
"input": { "limit": "100" }
}
]
```
3. Call Unified's `/tools/{id}/call` endpoint with the arguments.
4. Return the result to Claude as a `tool_result` block in your next message:
```json
[
{
"type": "tool_result",
"tool_use_id": "toolu_...",
"content": "..."
}
]
```
## Integrating with Google Gemini API
**Manual Tool Orchestration:**
1. Call Unified's `/tools` endpoint and pass the tool list as `function_declarations` to Gemini.
2. Gemini will return a function call request:
```plain text
function_call {
name: "list_candidates"
args { fields { key: "limit" value { string_value: "100" } } }
}
```
3. Call Unified's `/tools/{id}/call` endpoint.
4. Respond to Gemini with the tool result as a `functionResponse` in your next message:
```json
{
"role": "user",
"parts": [
{
"functionResponse": {
"name": "list_candidates",
"response": { ... }
}
}
]
}
```
## Integrating with Cohere
**Manual Tool Orchestration:**
1. Call Unified's `/tools?type=cohere` endpoint and pass the tool list to Cohere:
```python
response = co.chat(
model="command-a-03-2025", messages=messages, tools=tools
)
```
2. When Cohere requests a tool call, call Unified's `/tools/{id}/call` endpoint.
3. Pass the tool result back to Cohere in your next message
### Example Prompt and Response
**Prompt:**
```plain text
"Score this candidate for the Software Engineer job."
```
**What happens:**
- Your chosen LLM discovers the available tools from Unified MCP (e.g., `fetch-candidate`, `fetch-job`, `score-candidate`).
- The LLM calls `fetch-candidate` and `fetch-job` tools to get the data.
- The LLM calls `score-candidate` with the data.
- The LLM returns a response like:
**Response:**
```plain text
Candidate Jane Doe scored 92/100 for the Software Engineer job. Strengths: Python, distributed systems. Recommended for interview.
```
## How to Connect OpenAI to Real-Time SaaS Data with Unified.to MCP Server
URL: https://docs.unified.to/guides/how_to_connect_openai_to_real_time_saas_data_with_unified_mcp_server
# How to Connect OpenAI to Real-Time SaaS Data with Unified.to MCP Server
------
_August 28, 2025_
OpenAI's LLMs can power rich workflows — but most products still need access to **live customer data** from CRMs, ATSs, HRIS, or accounting systems. That usually means writing brittle glue code, normalizing APIs, and maintaining webhook jobs.
With Unified.to's MCP server, you can skip that complexity. Unified.to exposes 317+ SaaS integrations as real-time, callable tools that OpenAI can use directly — no custom integration logic required.
In this guide, we'll walk through how to connect OpenAI to Unified.to MCP so your application can give OpenAI real-time access to customer SaaS data and actions.
## Authentication
Every request to the Unified.to MCP server must include a token. You can pass this either as a URL parameter (`?token=...`) or in the `Authorization: bearer {token}` header.
There are two authentication flows:
- **Private (workspace key + connection)**
Use your Unified.to workspace API key and add a `connection` parameter. This should never be exposed publicly.
- **Public (end-user safe)**
Generate a token in the format `{connectionID}-{nonce}-{signature}` using your workspace secret. Safe to share with customers.
## Integrating with OpenAI (LLM API)
Once your MCP client is set up, you can connect it to OpenAI's API, which natively supports remote MCP servers.
### Configure OpenAI to Use Unified MCP
When you send a chat completion request, specify the MCP server as a tool source.
Here's a **candidate assessment example**:
```python
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "Score this candidate for the Software Engineer job."}
],
tools=[{
"type": "mcp",
"url": MCP_URL, # The Unified MCP URL with your token
}],
tool_choice="auto"
)
print(response.choices[0].message.content)
```
No backend glue code required—OpenAI orchestrates the tool calls via Unified MCP.
## Advanced MCP Options
Unified.to MCP gives you granular control over how tools are exposed to OpenAI:
- `permissions` → restrict available scopes.
- `tools` → allowlist specific tool IDs.
- `aliases` → add synonyms so OpenAI better matches tool names.
- `hide_sensitive=true` → automatically strip PII (emails, phone numbers, etc).
- `include_external_tools=true` → expose all vendor API endpoints, not just Unified.to's normalized models.
These options help you keep OpenAI outputs predictable and secure in production-grade workflows.
### Sample Snippet
```javascript
import OpenAI from 'openai';
import Anthropic from '@anthropic-ai/sdk';
// OpenAI model version
const modelVersion = 'latest';
// Unified connection Id that you want to have the mcp to use tools for
const connection = 'UNIFIED_CONNECTION_ID';
// location from where your unified account was created
const dc = 'us';
// Optional: list of specific tools that you want to use
const toolIds = ['get_messaging_message','list_messaging_message']
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || '',
});
const params = new URLSearchParams({
token: process.env.UNIFIED_API_KEY || '',
type: 'openai',
dc,
connection,
include_external_tools: includeExternal ? 'true' : 'false',
});
if (toolIds.length > 0) {
params.append('tools', toolIds.join(','));
}
const serverUrl = `${process.env.UNIFIED_MCP_URL}/sse?${params.toString()}`;
let latestModel;
if (modelVersion === 'latest') {
// get the latest model from open ai
const models = await openai.models.list();
latestModel = models.data[0].id;
} else {
latestModel = modelVersion;
}
const completion = await openai.responses.create({
model: latestModel,
tools: [
{
type: 'mcp',
server_label: 'unifiedMCP',
server_url: serverUrl, // change url as needed
require_approval: 'never',
},
],
instructions: `You are a helpful assistant`,
input: message,
});
for await (const chunk of completion.output) {
console.log('chunk', chunk);
console.log(JSON.stringify(chunk, null, 2));
}
```
## Coverage and Infrastructure
Unified.to MCP is the most complete hosted MCP server available today:
- **20,421+ real-time tools** (growing weekly)
- **335+ integrations across 21 categories** (ATS, CRM, HRIS, Accounting, Messaging, File Storage, and more)
- **Zero-storage architecture** — no caching, no liability
- **Scoped security controls** — permissions, aliases, and PII redaction
- **Multi-region deployment** — US, EU, and AU data centers for compliance
This ensures your OpenAI workflows aren't just demos — they're secure, scalable, and designed for production-grade use cases.
Unified.to MCP works across all major LLM providers: **OpenAI, Anthropic, Google Gemini, and Cohere.** That means you can build once and connect to any agent client.
- [Explore the MCP docs](https://docs.unified.to/mcp?utm_source=chatgpt.com)
- [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified?utm_source=chatgpt.com)
> _Note: Unified.to MCP is currently in beta and should not be used in production systems yet. Contact us if you'd like to explore production use._
## How to Connect Zoom and Zoom Phone
URL: https://docs.unified.to/guides/how_to_connect_zoom_and_zoom_phone
# How to Connect Zoom and Zoom Phone
------
_July 2, 2026_
This guide walks you through creating a Zoom OAuth app, configuring the right permissions, and connecting your Zoom account. You will need a Zoom account with admin access for some features (user directory, call logs, and recordings).
---
## **What you need**
- A [**Zoom Marketplace**](https://marketplace.zoom.us/) developer account
- Zoom account admin access (required for directory, group, and Zoom Phone admin scopes)
- A Zoom Phone license if you plan to use the Zoom Phone integration
- Your Unified workspace (to store credentials and start the connection)
> **Note:** Zoom and Zoom Phone are separate integrations in Unified. You can use one OAuth app for both, as long as that app includes the scopes for every feature you need.
---
## **Step 1: Create a Zoom OAuth app**
1. Sign in to the [**Zoom App Marketplace**](https://marketplace.zoom.us/).
2. Go to **Develop** → **Build App**.
3. Choose **General App** and click **Create**.
4. Enter an app name and basic details.
On the **Basic Information** page:
1. Open **App Credentials** and note your **Development** Client ID and Client Secret.
- Use development credentials while the app is still in testing.
- After the app is published, switch to **Production** credentials.
2. Under **OAuth Information**, configure:
- **OAuth Redirect URL** — use the exact URL for your Unified region (see below).
- **OAuth Allow List** — add the same URL (or the base domain if Zoom accepts it).
3. If **Strict Mode URL** is enabled, the redirect URL must match character-for-character.
### **OAuth redirect URL by region**
Add the redirect URL that matches where your Unified workspace is hosted:
| **Region** | **Redirect URL** |
| ---------- | -------------------------------------- |
| US | `https://api.unified.to/oauth/code` |
| EU | `https://api-eu.unified.to/oauth/code` |
| AU | `https://api-au.unified.to/oauth/code` |
> **Important:** Copy the URL exactly. A trailing slash, wrong subdomain, or `http` instead of `https` will cause the connection to fail.
---
## **Step 2: Add the required scopes (permissions)**
In your Zoom app, open the **Scopes** page and add the permissions below. Only add scopes for the features you actually use.
For each scope, Zoom may ask you to describe why your app needs it. Keep the explanation tied to your use case (e.g. 'Read call history for CRM logging' or 'List meetings for calendar sync').
### **Required for sign-in (Zoom and Zoom Phone)**
Add these for any connection:
- `user_profile`
- `user_info:read`
- `user:read`
### **Zoom — users and groups**
If you sync users or groups:
| **Feature** | **Scopes to add** |
| ------------ | ---------------------------------------------------- |
| Read users | `user:read:user:admin`, `user:read:list_users:admin` |
| Write users | `user:write:admin` |
| Read groups | `group:read:admin`, `group:read:list_groups:admin` |
| Write groups | `group:write:admin` |
### **Zoom — meetings and webinars**
If you work with meetings or webinars:
| **Feature** | **Scopes to add** |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Read meetings | `meeting:read:list_meetings` |
| Create / update / delete meetings | `meeting:write:meeting`, `meeting:update:meeting`, `meeting:delete:meeting` |
| Read webinars | `webinar:read:list_webinars` |
| Manage webinars | `webinar:write:webinar`, `webinar:update:webinar`, `webinar:delete:webinar`, `webinar:write:registrant`, `webinar:update:registrant`, `webinar:delete:registrant`, `webinar:write:panelist`, `webinar:update:panelist`, `webinar:delete:panelist` |
### **Zoom Phone — calls, recordings, and users**
If you use the Zoom Phone integration:
| **Feature** | **Scopes to add** |
| --------------------- | ---------------------------- |
| Read Zoom Phone users | `phone:read:user:admin` |
| Read call history | `phone:read:call_log:admin` |
| Read call recordings | `phone:read:recording:admin` |
> **Note:** Zoom Phone scopes require a Zoom Phone license on the account you connect.
---
## **Step 3: Add credentials in Unified**
1. In the Unified dashboard, open **Integrations**.
2. Enable **Zoom** and/or **Zoom Phone**.
3. Enter the **Client ID** and **Client Secret** from your Zoom app (development credentials while testing; production credentials after publish).
4. Save the integration.
The Client ID and Client Secret in Unified must match the values shown in the Zoom Marketplace for the same app and environment (Development vs Production).
---
## **Step 4: Connect a Zoom account**
1. Start a new connection for Zoom or Zoom Phone (from the dashboard or your app's auth flow).
2. You will be redirected to Zoom to sign in and approve the requested permissions.
3. After approval, you are redirected back and the connection is created.
> **Tip:** The Zoom user who authorizes the connection should be an account admin if you use admin-level scopes (users, groups, call logs, recordings).
---
## **Testing vs published apps**
Zoom apps start in **development / testing** mode. This affects who can connect.
### **While the app is in testing**
- Only Zoom accounts added as **authorized testers** (or users on the same Zoom account as the app owner) can complete OAuth.
- Use the **Development** Client ID and Client Secret in Unified.
- For each customer Zoom account that needs to connect **before** the app is published, that account must be added as a tester in the Zoom Marketplace:
1. Open your app in the [**Zoom Marketplace Developer portal**](https://marketplace.zoom.us/develop/applications).
2. Find **Add app for testing** / **Local Test** / **Authorized testers** (wording may vary).
3. Add the customer's Zoom email or account.
If a customer sees an error that the app is not authorized, they are usually not on the tester list yet.
### **When the app is published**
- Any Zoom customer can connect without being added as a tester.
- Switch Unified to the **Production** Client ID and Client Secret from the Zoom app.
- Existing connections may need to be **re-authorized** after you switch credentials or publish.
> **Note:** Publishing goes through Zoom's review process. Zoom may require security review for certain scopes and can take several business days. Plan ahead if you need many customers to connect on a fixed timeline.
---
## **Checklist before connecting**
Use this quick checklist if a connection fails:
- [ ] Client ID and Client Secret in Unified match the Zoom app (same environment: Development or Production).
- [ ] OAuth Redirect URL in Zoom is exactly `https://api.unified.to/oauth/code` (or the EU/AU URL for your region).
- [ ] The same URL is on the OAuth Allow List.
- [ ] All scopes your product needs are added in the Zoom app.
- [ ] If the app is still in testing, the connecting user's Zoom account is an authorized tester.
- [ ] The authorizing user has admin rights when using admin scopes.
- [ ] For Zoom Phone: the account has Zoom Phone enabled.
---
## **Troubleshooting**
### **'Invalid redirect URI' or redirect errors**
The redirect URL in Zoom does not match what Unified sends. Confirm the exact URL for your region and that Strict Mode is not blocking a valid redirect.
### **'App not authorized' or customer cannot sign in**
The app is likely still in testing and the customer's Zoom account is not an authorized tester. Add them in the Zoom Marketplace, or publish the app.
### **Connection works but data is missing**
Usually a missing scope. Compare the scopes in your Zoom app with the tables in Step 2 and add any that apply to the features you use.
### **Zoom Phone returns no calls or recordings**
- Confirm `phone:read:call_log:admin` and/or `phone:read:recording:admin` are added.
- Confirm the connected account has Zoom Phone and admin access to phone data.
### **After publishing, connections stopped working**
Update Unified with the **Production** Client ID and Client Secret, then ask affected users to connect again.
---
## **Related Zoom products**
Unified also supports **Zoom Calendar** as a separate integration with its own calendar-specific scopes. If you only need Zoom meetings, webinars, users, and Zoom Phone, follow this guide. If you need Zoom's native calendar product, enable the Zoom Calendar integration and add its scopes in the same (or a separate) Zoom OAuth app.
For Zoom developer documentation, see [**developers.zoom.us**](https://developers.zoom.us/).
## How to create a Connection In Microsoft Teams
URL: https://docs.unified.to/guides/how_to_create_a_connection_in_microsoft_teams
# How to create a Connection In Microsoft Teams
------
_June 5, 2026_
Most Microsoft products follow the same standard process for creating a connection, but Microsoft Teams is slightly different.
To access Unified Communications (UC) objects such as call records, Microsoft requires **application-level permissions** in addition to delegated permissions. This is necessary because Microsoft does not allow access to certain Teams resources using delegated permissions alone.
Before your end-users can authorize a MS Teams connection, you must first register an application in Microsoft Azure and configure the required permissions.
## Step 1: Register Your Microsoft Application
Follow the guide below to register your application and obtain your **Client ID** and **Client Secret**:
[https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365#how-to-get-your-oauth-2-credentials-for-microsoft-dynamics-365](https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365#how-to-get-your-oauth-2-credentials-for-microsoft-dynamics-365)
Once you have completed the application registration, continue with the permission setup steps below.
## Microsoft Teams Permission Types
Microsoft Teams uses two types of permissions:
1. **Delegated Permissions**
2. **Application Permissions**
### Resources That Require Delegated Permissions
The following categories only require delegated permissions:
- Messaging
- HRIS
- Calendar
### Resources That Require Application Permissions
The following category requires application permissions:
- Call Records
---
## Step 2: Configure Delegated Permissions
1. Open your registered application in Azure Portal.
2. Navigate to **API Permissions**.
3. Click **Add a permission**.

4. Select **Microsoft Graph**.

5. Select **Delegated permissions**.

6. Search for and select the required delegated permissions.

7. Click **Add permissions**.

---
## Step 3: Configure Application Permissions (Required for Call Records)
If you need access to Call Records, repeat the steps above but select **Application permissions** instead of Delegated permissions.
Search for and add the following permissions:
```plain text
CallRecords.Read.All
OnlineMeetingRecording.Read.All
OnlineMeetingTranscript.Read.All
CallRecordings.Read.All
CallTranscripts.Read.All
```

After selecting all required permissions, click **Add permissions**.
## Step 4: Create the Connection
After configuring permissions, create your Microsoft Teams connection.
If you have not yet obtained your Client ID and Client Secret, follow this guide:
[https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365#how-to-get-your-oauth-2-credentials-for-microsoft-dynamics-365](https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365#how-to-get-your-oauth-2-credentials-for-microsoft-dynamics-365)
Enter the following credentials:
- Client ID
- Client Secret

Click **Activate**.
---
## You're Ready to Go
Your Microsoft Teams integration is now configured and ready to use.
If you have any questions or encounter any issues during setup, please contact our support team.
## How to create a token-based connection in Highlevel
URL: https://docs.unified.to/guides/how_to_create_a_token_based_connection_in_highlevel
# How to create a token-based connection in Highlevel
------
_April 23, 2026_
This guide walks you through creating a **HighLevel token-based connection** using a **sub-account (location) API key** in Unified.
---
## ⚠️ Important Context
HighLevel operates on two levels:
- **Agency level (company)**
- **Sub-account level (location)** ✅ _(token-based authentication works only here)_
To access **CRM** and **HRIS** data, you must use a **sub-account (location)** API key.
> **Note:** HighLevel offers multiple ways to create connections. To avoid issues, follow every step in this guide carefully.
---
## ✅ Prerequisites
- You must have **Admin** or **Owner** access to the sub-account.
## 🔧 Step-by-Step Setup
1. Navigate to Your Sub-Account
Go to your **HighLevel sub-account (location)**.

2. Open Private Integrations
Go to **Settings → Private Integrations** _(inside the sub-account)_

3. Create a New Integration
Click **Create Integration**

4. Name Your Integration
Provide a clear name for your token (e.g., `Unified Integration`)

5. Select Required Scopes
Choose scopes based on your use case:
| Permission | Resource | HighLevel Scopes |
| ---------- | ------------- | ------------------------------------------------ |
| Read | CRM Contact | `contacts.readonly` |
| Write | CRM Contact | `contacts.write` |
| Read | CRM Company | `businesses.readonly`, `locations/tags.readonly` |
| Write | CRM Company | `businesses.write`, `locations/tags.write` |
| Read | CRM Deal | `opportunities.readonly` |
| Write | CRM Deal | `opportunities.write` |
| Read | HRIS Employee | `users.readonly` |
| Write | HRIS Employee | `users.write` |
| Read | CRM Lead | `contacts.readonly` |
| Write | CRM Lead | `contacts.write` |
| Read | CRM Pipeline | `opportunities.readonly` |
| Read | CRM Event | `forms.readonly`, `contacts.readonly` |
| Write | CRM Event | `contacts.write` |
**Reference:** [https://marketplace.gohighlevel.com/docs/Authorization/Scopes](https://marketplace.gohighlevel.com/docs/Authorization/Scopes)
6. Create and Confirm
Click **Create, t**hen click **Confirm**

7. Copy API Token
You will receive an **API Token**

Store it securely (you won't be able to retrieve it later)

8. Get Location ID
To find your **Location ID**:

Look at the URL of your sub-account
Example:
```plain text
https://app.gohighlevel.com/v2/location/tG38xt5twyoGISw4o92p/settings/private-integrations/...
```
The value after `/location/` is your **Location ID**
→ `tG38xt5twyoGISw4o92p`
9. Connect in Unified
- Go to: [https://app.unified.to/integrations/highlevel](https://app.unified.to/integrations/highlevel)
- Select **API TOKEN**
- Click **Activate**

10. Create Connection
Paste **API Token and Location ID, then c**lick **Create Connection**

---
## ✅ Done
Your HighLevel token-based connection should now be successfully set up in Unified.
---
If it doesn't work, double-check:
- You created the token at the **sub-account level**
- The correct **scopes** are selected
- The **Location ID** matches the sub-account
If you have any more doubts or issues, please contact the [Unified.to](https://unified.to/) team for support.
## How to Create a Unified.to Connection to HighLevel
URL: https://docs.unified.to/guides/how_to_create_a_unified_connection_to_highlevel
# How to Create a Unified.to Connection to HighLevel
------
_March 19, 2026_
This guide explains how to connect an existing HighLevel account to [Unified.to](https://unified.to/). It assumes:
- You already have a Unified.to account
- You already have a HighLevel account
- You have access to a HighLevel Developer (Agency) account
---
## Overview
To create a connection between Unified.to and HighLevel, you will:
1. Create or configure a HighLevel OAuth app
2. Collect required credentials (Client ID, Client Secret, etc.)
3. Use Unified.to to create a connection
4. Authorize the connection via OAuth
---
## Step 1: Create a HighLevel OAuth App
1. Log in to your HighLevel Developer (Agency) account [link](https://marketplace.gohighlevel.com/apps)
2. Navigate to the **App Dashboard**.

1. Click **Create App**
2. Fill in the required details:
- **App Name**: Any name (e.g., Unified Integration)

- **Redirect URL**: In **Advanced settings → Auth**, set the redirect URL to the value shown on the integration's **OAuth2 Credentials** page in [Unified.to](https://unified.to/).


- **Scopes**: Use the scopes listed on the HighLevel app setup page.

In [Unified.to](https://unified.to/), you can also find the required scopes listed below the integration page.

3. Save the app in HighLevel.
---
## Step 2: Get Credentials
After creating the app, go to **Manage → Secrets** and add:

- **Client ID**
- **Client Secret**
To find the **Version ID**, open the install link and copy the Version ID from the resulting page.

Keep these secure. You will need them in Unified.to.
---
## Step 3: Create Connection in Unified.to
1. Log in to your Unified.to dashboard
2. Navigate to **Integrations**.
3. Select **HighLevel** as the integration
4. Enter the following details:
- **Client ID** (from HighLevel)
- **Client Secret** (from HighLevel)
- **Version ID** (from HighLevel)
5. Click Activate
6. Go to **Embedded Components**.
7. Click **HighLevel**.
---
## Step 4: Authorize the Connection
1. You will be redirected to HighLevel
2. Log in (if not already logged in)
3. Select the account/location you want to connect
4. Approve the requested permissions
Once approved, you will be redirected back to Unified.to
---
## Common Issues & Troubleshooting
### 1. Invalid Redirect URI
- Ensure the redirect URL in HighLevel exactly matches the one used by Unified.to
### 2. Unauthorized / 401 Errors
- Double-check Client ID and Client Secret
- Ensure the OAuth app is active
### 3. Missing Data
- Ensure correct scopes are selected in HighLevel
- Some data may depend on the selected location/account
---
## Notes
- HighLevel uses location-based access. Make sure you select the correct location during authorization
- You can create multiple connections for different locations if needed
---
If you run into issues, reach out to the Unified.to support team with:
- Connection ID
- Error logs
- Steps to reproduce
## How to Create a Xero Connection in
URL: https://docs.unified.to/guides/how_to_create_a_xero_connection_in
# How to Create a Xero Connection in
------
_April 2, 2026_
Follow the steps below to create a Xero application and connect it to Unified.to.
## 1. Sign in to the Xero Developer Portal
Go to the Xero Developer portal:
[https://developer.xero.com/app/manage/](https://developer.xero.com/app/manage/)
Sign in with your Xero account.
## 2. Create a New App
From the dashboard, click **Create New App**.
Choose the appropriate app type and fill in the required details:
- App name
- Company or organization name
- Privacy policy URL
- Terms and conditions URL (if required)


## 3. Add the Redirect URI
While creating the app, add the OAuth 2.0 Redirect URI provided by Unified.to.
You can find the correct redirect URI here:
[https://app.unified.to/integrations/xero?tab=oauth2](https://app.unified.to/integrations/xero?tab=oauth2)
> Important: The redirect URI may differ depending on your Unified.to region. Make sure you copy the exact value shown in your Unified.to dashboard.
Examples:
- US region
- EU region
- AU region
If the redirect URI does not match exactly, Xero authentication will fail.
After adding the redirect URI, click **Create App**.
---
##

## 4. Copy the Client ID and Client Secret
After the app is created, open the app and go to the **Configuration** tab.
There you will find:
- Client ID
- Client Secret
> Keep the Client Secret secure. Do not share it publicly or commit it to source control.

## 5. Add Credentials to Unified.to
Open the Xero integration settings page in Unified.to:
https://app.unified.to/integrations/xero?tab=auth
Paste the following values:
- Client ID
- Client Secret
Click **Activate**.

## 6. Create a Connection Using Embedded Components
To create a customer-facing connection flow:
1. Go to **Embedded Components** in Unified.to.
2. Select the **Xero** integration.
3. Launch the embedded authorization flow.
4. Complete the authorization process.
This allows your end users to connect their Xero account directly from your application.
## Notes
### Journals Endpoint Availability
The `Journals` endpoint is a premium Xero feature.
According to Xero's documentation, the Journals API is only available starting with the **Advanced** pricing tier. Customers using lower Xero plans may receive authorization or access errors when calling this endpoint.
### Common Issues
- Ensure the redirect URI in Xero exactly matches the one shown in Unified.to.
- Make sure you are using the correct region-specific Unified.to redirect URI.
- If you regenerate the Client Secret in Xero, you must update it in Unified.to.
- Only users with sufficient permissions in Xero can authorize an organization.
- If you have previously selected an organization for the same Xero app, Xero may automatically reuse that organization during future authorizations.
### Troubleshooting
If authentication fails:
- Verify the Client ID and Client Secret are correct.
- Double-check the redirect URI.
- Re-authorize the connection after saving any changes.
- Remove the existing connection and create a new one if necessary.
## How to create and configure webhooks
URL: https://docs.unified.to/guides/how_to_create_and_configure_webhooks
# How to create and configure webhooks
------
_September 4, 2024_
This guide goes over how to create and configure webhooks with Unified.to. Webhooks allow you to receive real-time data events when there are updates in your customers' accounts.
Looking for help with troubleshooting webhook issues? See: [How to troubleshoot unhealthy webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks).
## Before you begin
This guide assumes you have a basic understanding of [webhooks](https://docs.unified.to/concepts/webhooks).
## Create a webhook
Webhooks and connections are closely related; webhooks can only be created from existing connections and you can create multiple webhooks off a single connection. If you delete a connection, any webhooks that are associated with it will also be deleted.
There are two ways to create a webhook:
### Method 1: Use the Unified API
Send a POST request to `/unified/webhook`(see below for configuration options). For example:
```javascript
POST /unified/webhook?include_all=true
{
hook_url: `${YOUR_WEBHOOK_URL}`,
connection_id: `${CONNECTION_ID}`
object_type: 'ats_candidate', // data to subscribe to
event: 'updated', // type of event to listen for
}
```
**API reference:** [Create a webhook](https://docs.unified.to/unified/webhook/Create_webhook_subscription)
By including the optional parameter `include_all` in the query, the webhook will receive all historical data for the connection (see:[ Get all initial data for a connection](https://docs.unified.to/guides/how_to_create_and_configure_webhooks#get-all-initial-data-for-a-connection)).
This is the payload that your server receives when webhook data comes in:
| Name | Type | Desc |
| --------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data` | _object array_ | An array of objects specific to the webhook's connection. (eg. CRM contacts) |
| `webhook` | [_Webhook_](https://docs.unified.to/unified/webhook/model) | The webhook object. You can use the id to manage or delete your webhook. |
| `nonce` | _string_ | Random string |
| `sig256` | _string_ | A security signature generated using HMAC-SHA1. It combines your workspace secret with the payload data and nonce i.e `HMAC-SHA256(workspace.secret, data + nonce)` . Use this to verify the authenticity of incoming webhooks. |
| `type` | _enum_ | `INITIAL-PARTIAL, INITIAL-COMPLETE, VIRTUAL, NATIVE` |
A note on the `type` enums:
- `INITIAL-PARTIAL`: included with each page of results for the initial sync
- `INITIAL-COMPLETE`: included with last page (e.g. last 5 elements for the limit 100) of initial sync or with an empty list (no more results)
- `VIRTUAL`: included with every page when reading new data from a virtual webhook
- `NATIVE`: included with every page when reading new data from a native webhook
### Method 2: Use the Unified.to app
If you prefer to create webhooks through the UI, you can do so at [app.unified.to](https://app.unified.to/).
1. Navigate to **Integrations** > [**Webhooks**](https://app.unified.to/webhooks).
2. Click **New Webhook**.
3. Click on the input and a list of your connections will appear. Select the connection from which you want to create a webhook.
4. Choose how to configure your webhook (see below).
5. Click **Save**.
Information about the other fields can be found under their respective tooltips in the UI.
### Webhook configuration options
Whether you use the API or our web UI, these are the configuration options you can specify when creating a webhook:
1. **Object:** The data you are interested in e.g. Deal, Company, Application, etc.
2. **Event:** The events you want to subscribe to. Note: The _updated_ event will also be sent when the data object is first _created_.
3. **Type:** For integrations that support both native and virtual webhooks, select the type of webhook you want. Virtual webhooks are our way of supporting webhooks for API providers that don't offer them. For more details, see: [Understanding virtual webhooks](https://docs.unified.to/guides/understanding_virtual_webhooks#understanding-native-and-virtual-webhooks).
4. **Filter (optional):** Specify any properties you want to filter incoming events by. See: [Use filters to refine webhook events](https://docs.unified.to/guides/how_to_create_and_configure_webhooks#use-filters-to-refine-webhook-events)
5. **Fields (optional):** Comma-separated list of fields to include in the webhook payload. Leave blank to include all fields. The 'raw' field is NOT included by default. If you want to include all fields as well raw, just enter 'raw'. To read more about raw fields, see: [Raw and custom fields](https://docs.unified.to/reference/fields).
6. **Interval (virtual webhooks only):** Specify how often you want Unified.to to poll the API provider for new data.
## List your webhooks
To view your webhooks, make a GET request to `/unified/webhook` or visit [app.unified.to](https://app.unified.to/) and navigate to Integrations > [**Webhooks**](https://app.unified.to/webhooks).
**API reference:** [Get all webhooks](https://docs.unified.to/unified/webhook/Returns_all_registered_webhooks)
## Get all initial data for a connection
When you create a webhook, you can choose to fetch all existing data immediately.
1. If you are using the API, add the `include_all` query parameter when creating the webhook.
2. If you are using the UI, check off **Initially sync all data** in the configuration options.
Once the initial sync is complete, you will receive a final payload with `type: INITIAL-COMPLETE` and the requested webhook will continue to work as normal.
**API reference:** [Create a webhook subscription](https://docs.unified.to/unified/webhook/Create_webhook_subscription)
## A note on retrieving webhook data
Our system will POST initial data to your webhook in chunk sizes up to the limit supported by the integration. The limit can be found on the integration details page under **Feature support**.
When performing the initial retrieval, depending on the amount of data being sent, the integration's API's rate-limiting rules, and your server's performance, it could take a very long time. If you do not want to get all of the existing data (for example, you already have it or don't need it), then do not include the `include_all` parameter in the request.
After the initial retrieval is completed, whenever updated data is available, your webhook will be called with a list of data objects, up to the max limit. If there are more entries than the limit, then your webhook URL will be POSTed to multiple times.
For details about the webhook payload that is sent to your server, [click here](https://docs.unified.to/concepts/webhooks#webhook-payload).
## Specify which fields to receive from a webhook
If you only want a subset of fields when receiving updated data, you can define them with the `fields` parameter in both the API and the UI. This is useful if you don't want the entire payload being sent to your servers. For more details, see: [Fields](https://docs.unified.to/reference/fields).
**API reference:** [Create a webhook subscription](https://docs.unified.to/unified/webhook/Create_webhook_subscription)
## Use filters to refine webhook events
_Note: This feature is only available for virtual webhooks. For more information about virtual webhooks, see:_ [_Understanding virtual webhooks_](https://docs.unified.to/guides/understanding_native_and_virtual_webhooks#understanding-native-and-virtual-webhooks)
Some virtual webhooks support filters, allowing you to filter the events you receive. For instance, if you're subscribed to Deal events from a CRM, you could filter by `company_id` to only receive events from that specific company.
Filter availability varies by integration type and data model. To check which filters are supported for a particular webhook, refer to the **List** section under the **Feature Support** tab of the respective integration. Parameters that end in `_id` or `type` can be used as filter options when creating a webhook, helping you tailor your subscriptions to your specific needs.
## Trigger webhooks manually
If you don't want to wait for an event to come in while testing webhooks on a Tester plan, you can trigger a webhook call right away.
1. In the web app, navigate to [Webhooks](https://app.unified.to/webhooks).
2. Click the **Trigger** icon next to your webhook:

This can also be accomplished with the API.
**API reference:** [Trigger a webhook](https://docs.unified.to/unified/webhook/Trigger_webhook)
**Note:** When you trigger a webhook, we will schedule the next dispatch event to go out right away - this is treated like a regular webhook event. Therefore, if your webhook is listening for update events and no new updates have occurred since the last run, then no new events will be sent to your server.
## Frequently asked questions
**Is there a page_max_limit for webhooks?**
An integration's `page_max_limit` typically comes directly from the API provider. You can see that limit under the **Feature Support** tab on the integration details page**.** You don't have to set limits for webhooks; they will use the integration's max limit.
**Does changing the interval for virtual webhooks affect the initial retrieval?**
Increasing frequency of virtual webhooks has no effect on the initial retrieval of historical data. Each page read is scheduled to be read one after another as soon as possible.
**How do I distinguish between an updated event and a created event?**
When a new data object is created, it triggers both created and updated events. If you want to track both of these events but also tell them apart, we recommend subscribing to the updated event and then compare `created_at` and `updated_at` for incoming data. If they are the same, that means the data object was newly created.
**What happens if the webhook dispatch fails? Is there a retry mechanism in place?**
Our retry mechanism will retry 3 times immediately before using a Fibonacci backoff strategy. Read more about it [here](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks#a-note-on-our-webhook-retry-mechanism).
**Why do I receive duplicate webhook events and how should I handle them?**
Duplicate webhook events can occur for several reasons:
- Some integrations (like HubSpot) trigger separate events for each property change on an object
- Rapid changes to objects can trigger multiple events that may arrive out of order due to network conditions
To handle duplicates effectively:
1. Always use the object's latest state when updating your database
2. Implement atomic writes to avoid race conditions. Only update records if the incoming data is newer:
```javascript
-- Pseudo-code example
IF incoming_object.updated_at >= stored_object.updated_at THEN
UPDATE record
ELSE
-- Skip update as we already have newer data
END IF
```
## See also
- [How to troubleshoot unhealthy webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks)
- [Understanding virtual webhooks](https://docs.unified.to/guides/understanding_virtual_webhooks#understanding-native-and-virtual-webhooks)
## How to create Connection with Hubspot
URL: https://docs.unified.to/guides/how_to_create_connection_with_hubspot
# How to create Connection with Hubspot
------
_December 17, 2025_
Hubspot provides Free Developer Sandbox Account. You can create that from following [link](https://offers.hubspot.com/free-cms-developer-sandbox) .
1. Create an account.

1. Provide name of the account

After account is created it will redirect you to following page:-

1. Goto Development page from the size menu

1. Create a project in IDE and install and install using following command

```javascript
npm install -g @hubspot/cli && hs init
```
1. Create your Personal Access Key.
It will be inside the keys menu

1. Paste that key in your terminal when your hubspot application ask for it.

1. Now we will create a project.
run following command in that same project.
```javascript
hs get-started
```

1. Write Y to upload that project in the hubspot application.

1. Again select Y for following

1. It will redirect to hubspot page.
goto to following page:-
Connected Apps→ Manage Location → click the check box → Save.

1. In your project(code) you will see following folder structure.
GOTO→ yourproject (account-testing) → app-hsmeta.json

1. Following will be the scopes and redirect URL. You can remove which you want. However, do remember those removed scopes before creating a connection in unified. Also remember to add redirect URL as follows.
```javascript
{
"uid": "get_started_app",
"type": "app",
"config": {
"description": "A Unified.to token app for testing",
"name": "My Get Started app",
"distribution": "private",
"auth": {
"type": "oauth",
"redirectUrls": [
"https://api.unified.to/oauth/code",
],
"requiredScopes": [
"oauth",
"crm.objects.companies.read",
"crm.objects.contacts.read",
"crm.objects.deals.read",
"crm.objects.owners.read",
"crm.pipelines.orders.read",
"crm.objects.contacts.write",
"crm.pipelines.orders.read",
"crm.pipelines.orders.write",
"crm.objects.deals.write",
"sales-email-read",
"content",
"tickets",
"crm.lists.read",
"settings.users.teams.read"
],
"optionalScopes": [],
"conditionallyRequiredScopes": []
},
"permittedUrls": {
"fetch": ["https://api.hubapi.com"],
"iframe": [],
"img": []
},
"support": {
"supportEmail": "support@example.com",
"documentationUrl": "https://example.com/docs",
"supportUrl": "https://example.com/support",
"supportPhone": "+18005555555"
}
}
}
```
1. You can update those changes using following command in terminal.
```javascript
hs project upload
```
1. When the code is uploaded successfully you can check that by going to following Development→ Project → Select your project.

1. To check your scopes click on the project Component. It will show your app-hsmeta.json

1. To get the client ID and Client Secret click on the Auth button in your project component.

1. Paste the Client ID and Client Secret in the app.unified.to

1. To get developer key goto Development→ keys → developer API key. If no keys are present than generate developer api key.

1. Paste that key in the [app.unified.to](https://app.unified.to/) and click activate.
2. Goto Embedded components→ click on hubspot→ select your project→ click on approve.
Your Hubspot application is created.
## How to customize portal URLs for Stripe and GoCardless
URL: https://docs.unified.to/guides/how_to_customize_portal_urls_for_stripe_and_gocardless
# How to customize portal URLs for Stripe and GoCardless
------
_November 13, 2024_
This guide explains how to customize the portal URLs for Stripe and GoCardless contacts to control payment method flows and redirects. Portal URLs allow your customers to manage their payment methods and perform other operations through a dedicated UI.
## Before you begin
This guide assumes you have:
- A basic understanding of the Unified API
- An active integration with either Stripe or GoCardless
## Understanding portal URLs
When working with the `accounting_contact` object for Stripe and GoCardless integrations, each contact includes a `portal_url` field. This URL provides access to the contact's portal UI where they can perform various operations, such as managing payment methods.
For example, a typical response might look like:
```javascript
{
"id": 77558,
"portal_url": ""
}
```
## Customize the portal URL
You can customize the portal URL's behaviour by appending specific query parameters to your API calls. The following parameters are available:
1. `success_url` (optional): Specifies where users should be redirected after completing their action
2. `type` (optional): Specifies the type of action the link is intended for. Available values:
- `portal`: Generates a standard portal UI link (default)
- `add_payment_method`: Generates a portal UI URL specifically for adding a new payment method
### Add a payment method with custom redirect
To create a URL that allows customers to add a payment method and redirects them upon completion:
1. Start with the original portal URL from the contact object:
```javascript
https://api.unified.to/accounting/{connection_id}/contact/{contact_id}/portal
```
2. Append the `success_url` and `type` parameters:
```javascript
https://api.unified.to/accounting/{connection_id}/contact/{contact_id}/portal?success_url=YOUR_REDIRECT_URL&type=add_payment_method
```
Note: Both `success_url` and `type` parameters are optional. If `type` is not specified, the system defaults to `type=portal`.
## See also
- [Accounting contact object reference](https://docs.unified.to/accounting/contact/model)
## How to filter webhook events
URL: https://docs.unified.to/guides/how_to_filter_webhook_events
# How to filter webhook events
------
_July 24, 2024_
Webhook filters allow you to only receive data about the events you care about. For instance, if you're subscribed to CRM Deal events from ActiveCampaign, you could filter by `company_id` to receive data only about deals for a specific company.
Please note that filters are only supported for **virtual** webhooks at this moment, with few exceptions (e.g. Box supports native webhook filtering). Read more about the differences between native and virtual webhooks here: [Understanding virtual webhooks](https://docs.unified.to/guides/understanding_virtual_webhooks)
Filter availability varies by integration type and data model. To determine which filters are supported for a particular virtual webhook, refer to the 'List' section on the Feature Support page of the respective integration. Parameters that end in `_id` or `type` can be used as filter options when creating a webhook, helping you tailor your subscriptions to your specific needs.
For example, this is the Feature Support list for CRM Deals by ActiveCampaign:

Notice that this data model supports virtual webhooks as specified under ‘Webhook.' Under ‘List Options', the parameter `company_id` is shown, indicating that it is possible to use this as a filter.
When creating webhooks via the [Unified.to](https://unified.to/) web app, you can specify your filter parameters in the webhook creation form:

When creating a webhook programatically via our API, you can include `filters` in the payload with a string dictionary of values to filter by. For example, in Javascript:
```javascript
const options = {
method: 'POST',
url: 'https://api.unified.to/unified/webhook',
headers: {
authorization: 'bearer YOUR_ACCESS_TOKEN'
},
data: {
filters: {
company_id: '12345',
},
// other payload data if needed
},
};
const results = await axios.request(options);
```
Review our complete guide on the webhook API here: [Create webhook subscriptions](https://docs.unified.to/unified/webhook/Create_webhook_subscription).
## How-to Get a Private Support Channel for Your Unified Integration
URL: https://docs.unified.to/guides/how_to_get_a_private_support_channel_for_your_unified_integration
# How-to Get a Private Support Channel for Your Unified Integration
------
_May 1, 2026_
[Unified.to](https://unified.to/) offers customers 3 platforms for support:
- Slack
- Discord
- Microsoft Teams
# Slack
We use Slack Connect to invite our customers into private support channels that we create in our Slack account. Just reach out to your account manager, and they will create a channel for you and invite your product team.
This is our preferred method of working with your team to get them to success faster.
# Discord
Our [Discord server](https://discord.gg/85z7HF7JbD) offers both public and private channels for support. The public channels are used for anyone who is just trying out [Unified.to](https://unified.to/). Once you dedicate your product team's time to integrate Unified.to into your application, please let us know and we will create a private channel for you.
# Microsoft Teams
For enterprise customers, Unified also offers shared private channels in Microsoft Teams. Please let us know if you cannot use Slack nor Discord (as per your IT policy), and we will be happy yo setup Teams access and a private channel.
But, you will need your IT to also setup Teams access to the Unified MS Teams account for your team, by following this guide:
This guide is for your **Microsoft Entra (Azure AD) administrator**. It walks through the configuration needed on your tenant so we can collaborate with you in a Microsoft Teams shared channel.
## Background
Unified.to wants to invite a user from your organization into a Microsoft Teams **shared channel** (sometimes referred to as a "private cross-org channel"). Unlike standard Teams channels, which require external users to be added as guests in your tenant, shared channels use **Microsoft Entra B2B direct connect**. This lets your user access the channel from inside their own Microsoft Teams client, without switching tenants and without being added to our directory.
For B2B direct connect to work, **both tenants must trust each other**. We have already configured our side. This document covers the symmetric configuration required on yours.
## What you'll be enabling
You're enabling B2B direct connect with a single specific external tenant — Unified.to. Nothing else changes:
- We are not added as guests in your directory.
- Your users are not added as guests in ours.
- Your existing default cross-tenant access policy is unaffected for every other organization.
- The settings can be reverted at any time by removing the entry.
## Prerequisites
- A user account in your Entra tenant with one of the following roles:
- Global Administrator, **or**
- Security Administrator, **or**
- Conditional Access Administrator (with Cross-tenant access settings permission)
- Your tenant must have Microsoft Entra ID (any edition — Free is sufficient for the basic configuration described here)
- A modern browser signed in to your Microsoft 365 admin account
## Our tenant details
You'll need these values during the setup:
| Field | Value |
| ------------------------- | -------------------------------------- |
| Organization name | Unified.to |
| Primary domain | `unified.to` |
| Microsoft Entra tenant ID | `4b8acf48-e5c8-4484-896b-95b1b824ddda` |
## Step-by-step setup
The full process takes about five minutes.
1. Open the Microsoft Entra admin center
Go to https://entra.microsoft.com and sign in with your admin account.
2. Navigate to Cross-tenant access settings
In the left navigation, expand **Entra ID** → **External Identities** → **Cross-tenant access settings**.
(If you don't see External Identities directly, click "Show more" or use the search bar at the top: search for "Cross-tenant access settings".)
3. Add Unified.to as an organization
4. Make sure you're on the **Organizational settings** tab (not "Default settings").
5. Click **+ Add organization**.
6. In the side panel, paste either of the following into the "Tenant ID or domain name" field:
- Tenant ID: `4b8acf48-e5c8-4484-896b-95b1b824ddda`
- Domain: `unified.to`
7. Microsoft will resolve the entry and display **Unified.to** with our tenant ID.
8. Click **Add**. You should see a confirmation toast: "Successfully added Unified.to tenant to organizational settings."
Unified.to now appears in the list with both Inbound access and Outbound access showing **"Inherited from default"**. We'll override that next.
### 4. Configure Inbound access
This controls whether Unified.to users can access resources in your tenant via B2B direct connect (such as joining a shared channel that we host).
1. In the 'Unified.to row', click the **Inbound access** link ("Inherited from default").
2. In the panel that opens, click the **B2B direct connect** tab.
3. Select the **Customize settings** radio.
4. On the **External users and groups** sub-tab:
- Access status: **Allow access**
- Applies to: **All Unified.to users and groups**
5. Switch to the **Applications** sub-tab:
- Access status: **Allow access**
- Applies to: **All applications**
6. Click **Save**. The Save button greys out when the save completes.
7. Close the panel (X in the top-right) to return to the Organizational settings list. The Inbound access column for Unified.to should now read **"Configured"**.
### 5. Configure Outbound access
This controls whether your users can access resources hosted by Unified.to (such as the shared channel we'll invite them into).
1. In the 'Unified.to row', click the **Outbound access** link ("Inherited from default").
2. In the panel that opens, click the **B2B direct connect** tab.
3. Select the **Customize settings** radio.
4. On the **Users and groups** sub-tab:
- Access status: **Allow access**
- Applies to: **All [your organization name] users**
5. Switch to the **External applications** sub-tab:
- Access status: **Allow access**
- Applies to: **All external applications**
6. Click **Save**.
7. Microsoft will display a consent dialog titled **"Are you sure?"**, explaining that enabling outbound B2B direct connect lets external organizations access limited contact data about your users for the purpose of sending connection requests. Click **Yes** to confirm.
8. Close the panel. The Outbound access column for Unified.to should now read **"Configured"**.
### 6. Verify
The Unified.to row in your Organizational settings should now show:
| Inbound access | Outbound access |
| -------------- | --------------- |
| Configured | Configured |
Both fields should be hyperlinked. If either still says "Inherited from default", reopen that link and re-save.
## What about Microsoft Teams settings?
In most tenants, the Teams admin center already permits shared channel collaboration with external orgs by default. If a user from your side reports they cannot accept our shared-channel invite, your Teams admin should verify:
- Microsoft Teams admin center → **Teams** → **External access** → "Allow Teams accounts not managed by an organization" or per-domain allow list (if your org uses an allow list, add `unified.to`).
- Microsoft Teams admin center → **Teams** → **Teams policies** → the policy assigned to the affected user has **"Create shared channels"** and **"Invite external users to shared channels"** enabled (or at minimum, **"Join external shared channels"**).
These Teams-level settings are usually permissive by default. The Entra cross-tenant configuration above is the part that's typically missing.
## Replication delay
Cross-tenant configuration can take up to 15 minutes to propagate across Microsoft's services. If we attempt to invite your user into the shared channel right after you finish, and it fails, please ask us to wait ten minutes and retry before troubleshooting further.
## Letting us know you're done
Once Inbound and Outbound both read "Configured" for Unified.to on your side, please let us know. We'll then send the shared-channel invite to the user(s) you specify, and they'll see the invitation appear in their own Microsoft Teams client.
## Reverting later
To remove the trust relationship at any time:
1. Microsoft Entra admin center → External Identities → Cross-tenant access settings → Organizational settings.
2. Find the Unified.to row, click the trash icon at the right, and confirm.
This restores your default policy for our tenant. Any active shared channels become inaccessible to your users at that point.
## Reference
Microsoft's official documentation:
- B2B direct connect overview: [https://learn.microsoft.com/entra/external-id/b2b-direct-connect-overview](https://learn.microsoft.com/entra/external-id/b2b-direct-connect-overview)
- Configure cross-tenant access settings: [https://learn.microsoft.com/entra/external-id/cross-tenant-access-settings-b2b-direct-connect](https://learn.microsoft.com/entra/external-id/cross-tenant-access-settings-b2b-direct-connect)
- Shared channels in Microsoft Teams: [https://learn.microsoft.com/microsoftteams/shared-channels](https://learn.microsoft.com/microsoftteams/shared-channels)
## How to get your 8x8 Connect API Key and Account ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_8x8_connect_api_key_and_account_id_step_by_step_guide
# How to get your 8x8 Connect API Key and Account ID: Step-by-step guide
------
_July 17, 2025_
8x8 Connect supports authentication with API Key and Account ID. To connect, you'll need to generate secure API Key and Account ID from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their 8x8 Connect account.
## **API Keys**
**Step 1:** View the API Keys Section of the Connect Dashboard. This may be accessed [here](https://connect.8x8.com/messaging/api-keys).

**Step 2:** Click the "Create API Key" button.

**Step 3:** Name the API Key in the Pop Up, this can be any value. We recommend using a memorable name related to the API key's intended purpose such as "HealthCareApp_Production". Once the value is entered in, click **save**.

**Step 4:** The new API key should now be located in the list, you can perform a partial search at the top for the name. Only the last 6 characters will be shown, click the document button highlighted in red to reveal the entire API key. You can return to this page to retrieve the API key's value at any time.

## Account ID and SubAccount ID
The **accountId** and **subAccountId** can be found in your 8x8 Connect via **API keys** page.

Above we can see the Accountid is : **test11_9tT31** with SubaccountId : **test11_9tT31_hq.** Your account may have multiple subaccounts which can be seen if you click the drop down in **Subaccount ID.**
You've now created your 8x8 Connect API Key and Account ID - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 660 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your ADP Workforce Now Oauth2 client ID, Oauth2 client secret, Oauth2 PEM certificate and Oauth2 private key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_adp_workforce_now_oauth2_client_id_oauth2_client_secret_oauth2_pem_certificate_and_oauth2_private_key_step_by_step_guide
# How to get your ADP Workforce Now Oauth2 client ID, Oauth2 client secret, Oauth2 PEM certificate and Oauth2 private key: Step-by-step guide
------
_April 24, 2023_
When connecting ADP Workforce Now to other platforms, authentication requires a Oauth2 client ID, Oauth2 client secret, Oauth2 PEM certificate and Oauth2 private key.
Follow these step-by-step instructions to generate one from your ADP Workforce Now account, or provide them to your customers if they need to connect their account.
1. Log into your ADP account and access the ADP API Central portal. You will need the paid `ADP API Central` [application](https://apps.adp.com/en-US/apps/410612).
2. Select a project or create a new project

3. Enter the project details.

4. Select domain

5. Inside the Production integration Page, select the most suitable option for you.
6. After creating a new Project, select Development Credentials

7. This page will Provide the Client ID, Secret Key and Certificates
8. Select Manage Certificate to generate Certificate

9. Fill out the form, which will provide the PEM Key required to create a connection.
10. The processing time for the certificate is few minutes. Generated Certificate will be required to create a connection.
11. If asked, select `api` permission.
For Unified, you need to have access to the following APIS for your App:
- Job Applications API
- Job Applicants API
- Job Requisitions API
- Client Data - Validation Tables API
- Worker Leaves API
- Worker API
To add the API to your application scope, contact your ADP representative. Make sure you provide the following:
- Organization name
- Application name
- List of APIs to be added
Generating a ADP Workforce Now Oauth2 client ID, Oauth2 client secret, Oauth2 PEM certificate and Oauth2 private key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 660 integrations and 32 categories.
## How to get your Amazon S3 AWS Region, AWS S3 Key and AWS S3 Secret: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_amazon_s3_aws_region_aws_s3_key_and_aws_s3_secret_step_by_step_guide
# How to get your Amazon S3 AWS Region, AWS S3 Key and AWS S3 Secret: Step-by-step guide
------
_December 22, 2023_
Developers connecting to Amazon S3 will need an AWS Region, AWS S3 Key and AWS S3 Secret for authentication.
Here's how to generate, configure, and copy your Amazon S3 AWS Region, AWS S3 Key and AWS S3 Secret so you can use it in your integration or share steps with your customers.
# **Generating AWS Access Key ID and Secret Access Key**
You can easily access your AWS S3 account using a Access key and Secret Access key of your AWS account.
1. If you don't have one already then go to your account and click on **My Security Credentials**

2. Then select **Access keys (access key ID and secret access key)** section.

There is an important notification on the section, which recommends you to create an IAM Role instead of creating root access keys.
3. Click on **Create New Access Key**

4. Download the Key pairs to your system for future use.
5. Click on _**Show Access key**_ and you will get your _**Access Key ID**_ and _**Secret Access Key.**_

6. You need to use this _**Access Key ID**_ and _**Secret Access Key**_ to connect to your AWS connect and access the S3 bucket.
With your Amazon S3 AWS Region, AWS S3 Key and AWS S3 Secret in hand, you're ready to connect. But managing keys, tokens, and refresh flows across every vendor quickly becomes a security risk.
Unified.to abstracts all of that behind a single, secure layer. We never store customer data, credentials are always under your control, and our permissioning model removes the risk of misconfigured scopes.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified keeps integrations real-time, compliant, and safe at scale.
## How to get your Ashby API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_ashby_api_key_step_by_step_guide
# How to get your Ashby API Key: Step-by-step guide
------
_April 24, 2023_
Ashby supports authentication with an API Key. To connect, you'll need to generate an secure API Key from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their Ashby account.
1. Sign in to Ashby and click "Admin" in the top navigation bar
2. Navigate to Integrations > API Keys

3. Click + New in the upper right corner
4. Add a name for the new API Key, click Create API Key

5. Set necessary API Scopes, e.g.

To read Applications, you will also need the "Offers" READ permission.
To use Webhooks, you will also need the "API Keys" WRITE permission.
6. Click "Save and Continue"
7. Copy the API Key

Generating a Ashby API Key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 547 integrations and 30 categories.
## How to get your Brex API token: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_brex_api_token_step_by_step_guide
# How to get your Brex API token: Step-by-step guide
------
_March 21, 2025_
If you're building an integration with Brex, you'll need an API token from your customer's Brex account to authenticate requests.
This guide shows you (or your customers) how to generate one from Brex's account settings.
### **Generate a user token**
1. Sign in to [dashboard.brex.com](https://dashboard.brex.com/) as an [account admin or card admin](https://developer.brex.com/docs/roles_permissions_scopes) .
2. Go to [_Developer > Settings_](https://dashboard.brex.com/settings/developer) .
3. Click _Create Token_ .
4. Create a name for your token that will help you identify it. Choose what level of data access you need for your application; these are the [scopes](https://developer.brex.com/docs/roles_permissions_scopes) your token will have.

5. The next screen will confirm your previous selections. Make sure it looks good, then select _Allow Access_ .

6. Your token is now created. Copy and store the token securely. You won't be able to see it again.

7. Back on the developer page of your Brex dashboard, you should see your token listed now. As a security measure, part of the string is obfuscated. If you lose it, create a new one and replace the token.

**Caution**: Your user token is private and should not be shared. Never check it into version control or save it somewhere publicly accessible. If your user token is compromised or leaked, make sure to revoke it.
### **Token revocation and expiration**
User tokens will expire if they are not used to make an API call for 90 days.
If your token is compromised, or you no longer need it, revoke the user token from the developer page in your Brex dashboard. Once revoked, any calls made with this token will immediately begin to fail.
You've now created your Brex API token - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 475 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your Ceridian Dayforce Username, Password and Client Namespace: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_ceridian_dayforce_username_password_and_client_namespace_step_by_step_guide
# How to get your Ceridian Dayforce Username, Password and Client Namespace: Step-by-step guide
------
_April 24, 2023_
Developers connecting to Ceridian Dayforce will need an Username, Password and Client Namespace for authentication.
Here's how to generate, configure, and copy your Ceridian Dayforce Username, Password and Client Namespace so you can use it in your integration or share steps with your customers.
To get the Dayforce client namespace, follow these steps:
1. Log in to Dayforce.
2. Navigate to the "Company" tab.
3. Click on "Settings".
4. Under the "General" section, you should find the "Client Namespace" field. The value in this field is the Dayforce client namespace.
Getting your Ceridian Dayforce Username, Password and Client Namespace is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 514 custom connectors, you get one secure, normalized API layer across 30 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your Crelate API key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_crelate_api_key_step_by_step_guide
# How to get your Crelate API key: Step-by-step guide
------
_August 18, 2023_
To access Crelate data or automate workflows through an integration, an API key is required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
**Where Is My API Key?**
Your API Key can be found in your Settings Tab under "My Settings & Preferences". You will have to enable your API Key. Once Your API is enabled, you will be presented with your API Key.
If you ever need to regenerate a new API Key, you can do so by clicking "Regenerate New Key". However, please note: **Any Change in API KEY (for instance generating a new key or turning API Access off) will result in any exsisting links with the old API key to fail.**
**Managing API**
If you would like to manage your API, you can do so by going into your settings and clicking API Management under (Advanced Settings). You will presented with a screen that will allow you to manage users who have turned on API Access. If you click on a user who has enabled their API, administrators will have the option of turning off API Access for that user. Administrators can do this by clicking "Clear API Key". **Clearing the API key will cause any references to that API Key to fail and will turn off that users API access to that specific API key**. If that user would like API access again, they will have to regenerate another API key.
To start, select your navigation menu and select "**Your Profile & Preferences**"

Next, navigate to **API Access**

You've now created your Crelate API key - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 514 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your Dashlane API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_dashlane_api_key_step_by_step_guide
# How to get your Dashlane API Key: Step-by-step guide
------
_March 26, 2025_
When connecting Dashlane to other platforms, authentication requires an API Key.
Follow these step-by-step instructions to generate one from your Dashlane account, or provide them to your customers if they need to connect their account.
## Generate a Dashlane API key
To access the Dashlane API endpoints, you'll need an API key. This key can be generated in two ways: directly through the **Admin Console** or the **Dashlane CLI**.
### **Generate an API key using Dashlane Admin Console**
1. Open the Admin Console and navigate to the **Integrations** section.
2. Select **Public API** and then **Create key**.

3. Enter a name for the key and select **Generate key**.

4. Copy or download the bearer token. The bearer token will be used to authenticate and access the endpoints.
**Important**: Save your bearer token securely in a Secret on your vault, as it will only be displayed once.
[Add and manage secrets in Dashlane](https://support.dashlane.com/hc/articles/17020356625682)


You've now retrieved your Dashlane API Key. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 475 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your Deel Access Token: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_deel_access_token_step_by_step_guide
# How to get your Deel Access Token: Step-by-step guide
------
_August 26, 2023_
When connecting Deel to other platforms, authentication requires an Access Token.
Follow these step-by-step instructions to generate one from your Deel account, or provide them to your customers if they need to connect their account.
**Generating Access Tokens**
1. Navigate to **Apps & Integrations > Developer Center**.
2. Select the type of Token, whether Organization or Personal. The former ties the keys to the whole organization while the latter ties the keys to an individual - token will expire once this person leaves the company.

1. Add a token name and click on the **Next** button.

1. In the popup, select the scopes for the access token and click **Generate**.
`people:read` , `organizations:read`, `payslips:read`, `time-off:read`
2. Make sure to copy and save your newly generated token because you won't be able to see it again!
Generating a Deel Access Token is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 475 integrations and 29 categories.
## How to get your Dialpad API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_dialpad_api_key_step_by_step_guide
# How to get your Dialpad API Key: Step-by-step guide
------
_April 24, 2023_
Dialpad supports authentication with an API Key. To connect, you'll need to generate an secure API Key from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their Dialpad account.
To create and access your API key, you need to follow the below steps. Note that only Company Admins can create API keys on Dialpad.
1. Navigate to Admin Settings > My Company > Authentication > API Keys
2. Select **Add Key**
3. Name your key and set the expiration terms
4. Select desired **Additional Scopes**
5. Select **Save**
[video](https://use.vg/wWKws7)
Note: API keys can only be created by Dialpad customers on the Pro and Enterprise plans, and you must be a Company Admin to create and access an API key.
You've now retrieved your Dialpad API Key. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 475 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your Discord OAuth 2 credentials and bot token
URL: https://docs.unified.to/guides/how_to_get_your_discord_oauth_2_credentials_and_bot_token
# How to get your Discord OAuth 2 credentials and bot token
------
_May 21, 2024_
_Last updated: May 2026_
This guide explains how to register your application with Discord and obtain your OAuth 2 credentials and bot token. You'll need these credentials to activate the Discord integration and access Discord via Unified.to's unified API.
## Register your application in Discord
1. Sign into Discord and navigate to the [Developer Portal](https://discord.com/developers/applications).

2. Click **New Application** at the top right of the page.
3. In the **Create an application** window:
- Enter a name for your application
- Check the box to agree to Discord's terms of service and developer policy
- Click **Create**

Discord will create a new application and take you to the **General Information** tab for your application's page. Think of this application as the container for both your OAuth 2 credentials and your bot user.
## Get your Discord OAuth 2 credentials
Once your application is registered, you can obtain its OAuth 2 credentials. These authenticate your app and let it access the Discord API through Unified.to.
1. On your application's page, select the **OAuth2** tab from the left sidebar.
2. Under **Client Information**, make note of your **Client ID** and **Client Secret** — you'll need these later.
3. Under **Redirects**, click **Add Redirect**.
4. Enter `https://api.unified.to/oauth/code` as the redirect URL. (If your workspace is hosted in the EU or AU region, use the corresponding regional redirect URL.)
5. Click **Save Changes** at the bottom of the page.

> **Keep these private.** Never commit your Client Secret or Bot Token to source control, share them in screenshots, or include them in client-side code. Store them in environment variables or a secrets manager.
## Retrieve your Discord bot token
The Discord integration also requires a **Bot Token** to access the Discord API.
1. Select **Bot** from the left sidebar.
2. Under **Build-A-Bot > Token**, copy the **Bot Token**.
3. If no token is shown (for a new bot), click **Reset Token** to generate one, confirm in the dialog, and copy the new token that appears.
> **Note:** Resetting the token invalidates the previous one. If a bot is already running in production with the existing token, resetting will break those connections until you update them. You also won't be able to view a token again after it's generated, so store it securely right away.

## Configure bot permissions and intents
Enable the permissions and privileged gateway intents that match the data your integration accesses. Enable only what you need — Unified.to maps each capability to the minimum required access. The Discord integration supports messages and channels (via the Messaging API) and members (via the HRIS API):
- **To list channels** (Messaging Channel `list`/`get`): no privileged intent is required. The bot needs the standard **View Channels** permission in the server.
- **To read or write messages** (Messaging Message `list`/`create`/`update`/`get`/`remove`): enable **Message Content Intent** under **Privileged Gateway Intents**. Note that Message Content Intent is gated by Discord and may require verification once your bot is in a larger number of servers.

- **To read members** (HRIS Employee `list`/`get`, which maps Discord guild members to the unified employee object — a Unified.to abstraction, not a Discord-native concept): enable **Server Members Intent** under **Privileged Gateway Intents**. (Discord's documentation refers to this as the Guild Members Intent; the developer portal UI labels it "Server Members Intent.")
After enabling the intents you need, click **Save Changes** at the bottom of the page. Granting intents or permissions you don't use increases risk and can complicate Discord's review process once your bot reaches a larger number of servers.
## Activate Discord in Unified.to
1. Navigate to [Integrations](https://app.unified.to/integrations) in Unified.to.
2. Find and click on the **Discord** integration card.
3. On the integration details page, select **Your OAuth 2 credentials**, then enter your:
- Discord **Client ID**
- Discord **Client Secret**
- Discord **Bot Token**
4. Click **Activate** to save your changes and enable the Discord integration.

You can now use the Unified API to build new experiences with Discord.
→ [Start your 30-day free trial](https://app.unified.to/login)
→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)
## How to get your ELMO client ID and client secret: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_elmo_client_id_and_client_secret_step_by_step_guide
# How to get your ELMO client ID and client secret: Step-by-step guide
------
_December 11, 2023_
ELMO supports authentication with client ID and client secret. To connect, you'll need to generate secure client ID and client secret from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their ELMO account.
**Authentication Methods**
ELMO uses three main credentials for API authentication:
- **Client ID:** A unique identifier for your application.
- **Client Secret:** A secure key used in conjunction with the Client ID.
**Step-by-Step Setup**
1. **Request Access:** Contact your **ELMO Account Manager** to confirm your organization has API access enabled.
2. **Assign Permissions:**
- Log in to the **ELMO Administration portal**.
- Navigate to **Security Profiles** and add a profile with the **API Access Manager**role.
- Assign this profile to the specific user who will manage the integration.
3. **Generate Credentials:**
- Have a Company Admin access the **API Management console**.
- Generate and securely record the **Client ID** and **Client Secret**.
You've now created your ELMO client ID and client secret - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 475 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your Gainsight Client ID, Client Secret and Gainsight API Domain: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_gainsight_client_id_client_secret_and_gainsight_api_domain_step_by_step_guide
# How to get your Gainsight Client ID, Client Secret and Gainsight API Domain: Step-by-step guide
------
_November 15, 2025_
Gainsight supports authentication with Client ID, Client Secret and Gainsight API Domain. To connect, you'll need to generate secure Client ID, Client Secret and Gainsight API Domain from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their Gainsight account.
To generate the M2M OAuth key:
1. Navigate to **Administration > Connectors 2.0**.
2. Click **Create Connection**. The Create Connection dialog appears.
3. From the **Connector** dropdown list, select **Gainsight API**.
4. In the **Name of the connection** field, enter the name of the connection.
5. In the **Authentication Type**, select the OAuth.
6. Click **Generate OAuth Credentials**.
New OAuth API Key and OAuth API Secret keys are generated, which you can copy and use for authentication in all of the REST API requests to Gainsight.
**Note**:
- M2M OAuth can be created and managed only by super admins.
- M2M OAuth cannot be used for Event APIs.
- Two different M2M connections cannot have the same name for a single connection.

Generating a Gainsight Client ID, Client Secret and Gainsight API Domain is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 475 integrations and 29 categories.
## How to get your Greenhouse API Key and Job board token: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_greenhouse_api_key_and_job_board_token_step_by_step_guide
# How to get your Greenhouse API Key and Job board token: Step-by-step guide
------
_April 24, 2023_
Developers connecting to Greenhouse will need an API Key and Job board token for authentication.
Here's how to generate, configure, and copy your Greenhouse API Key and Job board token so you can use it in your integration or share steps with your customers.
1. To create a Greenhouse "Harvest" API key, click the Configure icon on your Greenhouse navigation bar and select "_Dev Center_" on the left.

1. Click "_API Credential Management_".

1. Click "_Create New API Key_", and select "_Harvest_" for the API Type.

1. Click "_Manage Permissions_".

2. Copy your Harvest API key to a secure location then click _"I have stored the API key"_.

3. Set Jobs, Job Posts, and Candidates, Applications, Scorecards permissions.

4. Click Save
5. Optionally, you can specify the job board token for your organization. Go to the "_Configure Job Boards_" page. (Configure gear icon > Job Boards & Posts)
6. Find and edit the job board. (Ellipsis icon > Edit Board Settings)

1. On the _Edit Your Job Board_ page, find the _URL_ section. Copy this value as this is the token.

Getting your Greenhouse API Key and Job board token is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 558 custom connectors, you get one secure, normalized API layer across 30 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your Helpscout API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_helpscout_api_key_step_by_step_guide
# How to get your Helpscout API Key: Step-by-step guide
------
_June 8, 2024_
Developers connecting to Helpscout will need an API Key for authentication.
Here's how to generate, configure, and copy your Helpscout API Key so you can use it in your integration or share steps with your customers.
To generate, view, or regenerate the API key, click the "person" icon on the top right of your account, next to the search, then click **Your Profile**.

Next, click the **Authentication** link in the menu on the left and select **API Keys** tab

**Note:** If you don't see an API key in your profile, that's because you haven't been granted the "Docs: Create new, edit settings & Collections" permission by the Account Owner or an Administrator.
Generating a Helpscout API Key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 475 integrations and 29 categories.
## How to get your HiBob Service User ID and Service User Token: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_hibob_service_user_id_and_service_user_token_step_by_step_guide
# How to get your HiBob Service User ID and Service User Token: Step-by-step guide
------
_April 24, 2023_
If you're building an integration with HiBob, you'll need an Service User ID and Service User Token from your customer's HiBob account to authenticate requests.
This guide shows you (or your customers) how to generate one from HiBob's account settings.
## **Step 1: Create a service user**
Service Users are created and managed in the Service Users section in Bob.
1. **If you have direct access to Bob:** Go to the Service Users configuration page to create a new user and copy the ID and token. See [Manage service users](https://help.hibob.com/hc/en-us/articles/27875098648465-Manage-service-users).
2. **If you don't have direct access to Bob**: Ask a Bob Admin to generate the credentials for you.
You'll need this ID and token to authenticate your API requests.
## **Step 2: Creating a permission group**
By default, service users have no access permissions.
To assign them access:
1. Create a dedicated Permission Group.
2. Add the Service User to this group.
This group should include only the permissions required for the API operations the service user will perform.
- **If you have access to Bob:** [Create a service user permission group](https://help.hibob.com/hc/en-us/articles/29550415706897).
- **If not**: Ask a Bob Admin to create one and assign the user to it.
## **Step 3: Set permissions**
A new permission group does not have any permissions enabled by default.
**Notes:**
- **Ask Bob Admin** to help determine which permissions are required for this service user. To learn more, see [Manage service users](https://help.hibob.com/hc/en-us/articles/27875098648465).
- **Default employee data permissions**: To read basic employee data via the People search API, assign the Default Employee Fields permissions. This includes access to the "root," "about," "employment," and "work" categories. To learn more, see [Permissions for Default Employee Fields in People Search API](https://help.hibob.com/hc/en-us/articles/27875098648465#h_01JB9DYK9SZEJ7MF4DA7RHJE03).
Permissions are grouped into:
### **3.1 Features**
Grant access to Bob features you need to access via the API you plan to use with this service user. For example, if you want to access reports, you may need to activate some options in **Features > Reports**.
1. From the top right, click **Edit**.
2. Select the area of Bob you'd like to manage.
3. Check or uncheck the permissions this group should have.

### **3.2 People's data**
Grant access to people's data in each area of Bob for the API you plan to use with this service user.
1. From the top right, click **Edit**.
2. Select the area of Bob you'd like to manage.
3. Mark the checkbox(es) to enable or disable the permissions you'd like to give the service users in this group.

**'View', 'edit' and 'view history'**
To access Employee data via the API you need to grant basic view access to each category:
- **View all employees' [Category name] sections**
If you want to access history data (such as all the entries in the Lifecycle or Work table), you also need to grant the history access:
- **View all employees' [Category name] section histories**.
If you want to update data or access sensitive data that requires an extra layer of permissions, you need to grant the edit permission:
- **Edit all employees' [Category name] sections**
### **3.3 Access rights**
Grant access to the people whose data you want to access via the API you plan to use with this service user.
1. From the **People's data** tab, click **Access data for**.
2. Select whose data these permissions apply to (by default, the access rights are set to all employees using the **Lifecycle status equals Any** condition). Options are:
- **Everyone**: all employees marked as Employed in the system.
> **Note**: By default, the service user's permission group grants access to all **active** employees. To access inactive employees you need to use **select by condition** and remove the condition: **Lifecycle status equals Employed** from the permission group's **Access data for** settings.
- **Select people by condition**: click **Edit**, select conditions from the dropdown menu, then click **Apply**.
- **Select people by name**: click **Edit**, select specific employees, then click **Apply**.

To learn more, see [Create a service user permission group](https://help.hibob.com/hc/en-us/articles/29550415706897).
You've now created your HiBob Service User ID and Service User Token - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 546 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your HighLevel API Key and Location ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_highlevel_api_key_and_location_id_step_by_step_guide
# How to get your HighLevel API Key and Location ID: Step-by-step guide
------
_April 24, 2023_
HighLevel supports authentication with API Key and Location ID. To connect, you'll need to generate secure API Key and Location ID from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their HighLevel account.
This guide walks you through creating a **HighLevel token-based connection** using a **sub-account (location) API key** in Unified.
---
## ⚠️ Important Context
HighLevel operates on two levels:
- **Agency level (company)**
- **Sub-account level (location)** ✅ _(token-based authentication works only here)_
To access **CRM** and **HRIS** data, you must use a **sub-account (location)** API key.
> **Note:** HighLevel offers multiple ways to create connections. To avoid issues, follow every step in this guide carefully.
---
## ✅ Prerequisites
- You must have **Admin** or **Owner** access to the sub-account.
## 🔧 Step-by-Step Setup
### 1. Navigate to Your Sub-Account
Go to your **HighLevel sub-account (location)**.

### 2. Open Private Integrations
- Go to **Settings → Private Integrations** _(inside the sub-account)_

### 3. Create a New Integration
- Click **Create Integration**

### 4. Name Your Integration
- Provide a clear name for your token (e.g., `Unified Integration`)

### 5. Select Required Scopes
Choose scopes based on your use case:
| Permission | Resource | HighLevel Scopes |
| ---------- | ------------- | ------------------------------------------------ |
| Read | CRM Contact | `contacts.readonly` |
| Write | CRM Contact | `contacts.write` |
| Read | CRM Company | `businesses.readonly`, `locations/tags.readonly` |
| Write | CRM Company | `businesses.write`, `locations/tags.write` |
| Read | CRM Deal | `opportunities.readonly` |
| Write | CRM Deal | `opportunities.write` |
| Read | HRIS Employee | `users.readonly` |
| Write | HRIS Employee | `users.write` |
| Read | CRM Lead | `contacts.readonly` |
| Write | CRM Lead | `contacts.write` |
| Read | CRM Pipeline | `opportunities.readonly` |
| Read | CRM Event | `forms.readonly`, `contacts.readonly` |
| Write | CRM Event | `contacts.write` |
**Reference:** [https://marketplace.gohighlevel.com/docs/Authorization/Scopes](https://marketplace.gohighlevel.com/docs/Authorization/Scopes)
### 6. Create and Confirm
- Click **Create**
- Then click **Confirm**

### 7. Copy API Token
- You will receive an **API Token**

- Store it securely (you won't be able to retrieve it later)

### 8. Get Location ID
To find your **Location ID**:

- Look at the URL of your sub-account
Example:
```plain text
https://app.gohighlevel.com/v2/location/tG38xt5twyoGISw4o92p/settings/private-integrations/...
```
- The value after `/location/` is your **Location ID**
→ `tG38xt5twyoGISw4o92p`
### 9. Connect in Unified
- Go to: [https://app.unified.to/integrations/highlevel](https://app.unified.to/integrations/highlevel)
- Select **API TOKEN**
- Click **Activate**
---

### 10. Create Connection
- Paste:
- **API Token**
- **Location ID**
- Click **Create Connection**

## ✅ Done
Your HighLevel token-based connection should now be successfully set up in Unified.
---
If it doesn't work, double-check:
- You created the token at the **sub-account level**
- The correct **scopes** are selected
- The **Location ID** matches the sub-account
If you have any more doubts or issues, please contact the [Unified.to](https://unified.to/) team for support.
You've now created your HighLevel API Key and Location ID - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 546 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your HubSpot developer key and OAuth 2 credentials (Legacy Apps)
URL: https://docs.unified.to/guides/how_to_get_your_hubspot_developer_key_and_oauth_2_credentials_legacy_apps
# How to get your HubSpot developer key and OAuth 2 credentials (Legacy Apps)
------
_February 12, 2023_
This guide will show you how to retrieve your OAuth 2 credentials and developer API key in HubSpot.
**Note**: HubSpot allows you to create different types of apps - public and private. This guide is about public apps.
## Register a HubSpot Developer Account
First, you'll need to register for a free account on HubSpot if you haven't already.
1. Navigate to the [HubSpot Developers portal](https://developers.hubspot.com/get-started).
2. Click **Create App Developer Account.**
3. Follow the onboarding to complete your account setup.
## Get your HubSpot Developer API key
Developer account API keys are separate from standard API keys - they can be used to manage subscriptions for webhooks and other HubSpot features.
1. Inside the HubSpot developer dashboard, open the sidebar and click [Apps](https://app.hubspot.com/developer/47094559/applications).
2. Click **Get HubSpot API key**.
3. Follow the instructions to create a Developer API key.
4. Click **Show key** to reveal your key. You will need this for the final step.
## Create a HubSpot app
A HubSpot app is a container for your integration settings and is where you'll create and find your OAuth 2 credentials.
1. On the Development page → Legacy Apps→ click **Create Legacy App →** Select **Public App.**

1. Give your app a name and description.
2. Click on the **Auth** tab.
3. Under **Redirect URLs,** enter: **`https://api.unified.to/oauth/code`**

4. Under **Scopes,** click **Add new scopes** and select the permissions that your application will require. For example, if your application reads Deals, then select `crm.deal.read`

5. Click **Create app** to finish setting up your app.
## (optional) Add the `oauth` scope to your app
For apps created before April 2024, the `oauth` scope may be missing from your app's auth settings. Apps created after this date have it on by default. This scope is required for the HubSpot integration to work.
1. Under **Scopes**, click **Add new scope.**
2. Search for **oauth** and select it.
3. Click **Update**.
## Get your OAuth 2 credentials
1. After creating your app, you will be redirected to the App Info tab. Click again on the **Auth** tab.
2. Your unique **Client ID and Client secret** will be displayed. You will need these for the next step.

## Activate the HubSpot integration in Unified.to with your client ID and secret
1. Open a new tab and navigate to [the HubSpot integration in Unified.to](https://app.unified.to/integrations/hubspot)
2. You will need the following three pieces of information from the earlier steps:
1. **Client ID**
2. **Client secret**
3. **Developer API key** from the developer dashboard
3. Copy and paste these values into their respective fields on the page. Make sure that **Your OAuth 2 credentials** is selected above.

## See also
- [How to set up your scopes in HubSpot](https://docs.unified.to/guides/how_to_set_up_your_scopes_in_hubspot)
- [How to configure webhooks in HubSpot](https://docs.unified.to/guides/how_to_configure_webhooks_in_hubspot)
## How to get your Humaans API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_humaans_api_key_step_by_step_guide
# How to get your Humaans API Key: Step-by-step guide
------
_November 14, 2023_
When connecting Humaans to other platforms, authentication requires an API Key.
Follow these step-by-step instructions to generate one from your Humaans account, or provide them to your customers if they need to connect their account.
1. Log in to the Humaans Admin Dashboard
2. Go to Settings
3. Open API access tokens
4. Click Create new token
5. Give it a name (e.g. "Unified.to")
6. Copy the generated token
7. Paste it into the API Token field
8. Click Authorize
**Note:** You'll need **Admin access** in Humaans to see "API access tokens."
You've now retrieved your Humaans API Key. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 475 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your iCIMS API Username, API Password and Customer ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_icims_api_username_api_password_and_customer_id_step_by_step_guide
# How to get your iCIMS API Username, API Password and Customer ID: Step-by-step guide
------
_April 24, 2023_
When connecting iCIMS to other platforms, authentication requires a API Username, API Password and Customer ID.
Follow these step-by-step instructions to generate one from your iCIMS account, or provide them to your customers if they need to connect their account.
# How to get your iCIMS API credentials
This guide explains how to obtain the required credentials for connecting iCIMS. You'll need to work with iCIMS support to get these credentials.
## Required credentials
To connect iCIMS, you'll need:
- Customer ID (your unique platform identifier)
- API Username
- API Password
## Steps to obtain your credentials
### Step 1: Contact iCIMS support
1. Reach out to your iCIMS account representative or support team
2. Request API access for integration purposes
3. Specify that you need:
- Customer ID
- API Username
- API Password
### Step 2: Receive your credentials
iCIMS support will provide you with:
- Your unique Customer ID
- API Username
- API Password
### Step 3: Verify access
1. Once you receive your credentials, keep them in a secure location
2. Enter the credentials in the inputs on this page to create a connection to your iCIMS account
## Important notes
- These credentials must be provided by iCIMS support
- The Customer ID is unique to your organization
Getting your iCIMS API Username, API Password and Customer ID is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 514 custom connectors, you get one secure, normalized API layer across 30 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your JobDiva Client ID, Username/email and Password: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_jobdiva_client_id_username_email_and_password_step_by_step_guide
# How to get your JobDiva Client ID, Username/email and Password: Step-by-step guide
------
_August 18, 2023_
If you're building an integration with JobDiva, you'll need an Client ID, Username/email and Password from your customer's JobDiva account to authenticate requests.
This guide shows you (or your customers) how to generate one from JobDiva's account settings.
# Obtain a JobDiva Client ID
Contact JobDiva Support to obtain a Client ID to use for your integration.
# Create a JobDiva API User
A JobDiva admin must establish a dedicated API username and password.
In JobDiva:
1. Navigate to Settings > My Team > Add User.
2. Add a new user.
3. Allow to access JobDiva API Calls permission for the user.
With your JobDiva Client ID, Username/email and Password in hand, you're ready to connect. But managing keys, tokens, and refresh flows across every vendor quickly becomes a security risk.
Unified.to abstracts all of that behind a single, secure layer. We never store customer data, credentials are always under your control, and our permissioning model removes the risk of misconfigured scopes.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified keeps integrations real-time, compliant, and safe at scale.
## How to get your Jobvite API Key, API Key Secret and Email: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_jobvite_api_key_api_key_secret_and_email_step_by_step_guide
# How to get your Jobvite API Key, API Key Secret and Email: Step-by-step guide
------
_August 18, 2023_
To access Jobvite data or automate workflows through an integration, a API Key, API Key Secret and Email are required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
## How do I get my Jobvite API key and secret?
Your API key and secret are both provided by Jobvite's Customer Success team. To request API credentials, file a support ticket at:
[https://www.jobvite.com/support/submit-a-case/](https://www.jobvite.com/support/submit-a-case/)
(Note: you will need to be logged in to submit a case. If you don't see a button titled **Submit a Request**, reach out to your account representative i.e. your Customer Success Manager or Account Manager)
### Submit a ticket
- Select the **issue type** from the dropdown menu
- Add a **Subject**
- Add a detailed **description** of the request:
- **API names** that you want to grant access to
- **Email** of a dedicated Jobvite user to determine that an update was made by the API and not an actual user. This email address needs to accept the Jobvite registration process. Assign the **Administrator** role to this user.
Alternatively, you may start a support chat (see bottom right corner of Help Center) to chat with their support team for guidance on retrieving an API Key.
Please make sure that Jobvite gives you access to:
- New User module
- Enable New Contact Details UI
**NOTE:**
Please note that access to the Contacts API requires that the Jobvite Customer has enabled the Jobvite Engage product on their account. The contacts API is not supported for customers who have not enabled the Jobvite Engage product (additional fee required).
JobVite Contacts are required for access to Candidates.
You've now retrieved your Jobvite API Key, API Key Secret and Email. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 652 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your Lever API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_lever_api_key_step_by_step_guide
# How to get your Lever API Key: Step-by-step guide
------
_April 24, 2023_
When connecting Lever to other platforms, authentication requires an API Key.
Follow these step-by-step instructions to generate one from your Lever account, or provide them to your customers if they need to connect their account.
1. To generate API credentials, navigate to Settings > Integrations and API > API Credentials.
2. Click the Generate New Key button

3. Input a name for the key that reflects the service or integration that will be using the key
4. Under the 'Permissions' heading, select "Select all"

5. Click the Copy Key button next to the API key (which can be found next to the 'Key name' field).

6. Click done
Generating a Lever API Key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 606 integrations and 32 categories.
## How to get your Loxo Agency slug, API Key and Agency ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_loxo_agency_slug_api_key_and_agency_id_step_by_step_guide
# How to get your Loxo Agency slug, API Key and Agency ID: Step-by-step guide
------
_August 18, 2023_
If you're building an integration with Loxo, you'll need an Agency slug, API Key and Agency ID from your customer's Loxo account to authenticate requests.
This guide shows you (or your customers) how to generate one from Loxo's account settings.
### Where do I find my API Key?
To get your API Key, we'll need to go to Loxo. Once there, click on your profile icon and then Settings.

In the Settings view, look for the API Keys card and click on it.

In the API Keys view, click the Add button.

### How can I get my Agency Slug?
To get your agency slug, navigate to your agency's career page. You can find it under your profile's menu.

On the Careers page, your agency slug will be the first part of the path, after the domain.
**Example**: `https://app.loxo.co/weblime-consultants` = _weblime-consultants_
You've now retrieved your Loxo Agency slug, API Key and Agency ID. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 524 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your Microsoft Active Directory / Entra ID Client ID, Client Secret and Tenant ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_microsoft_active_directory_entra_id_client_id_client_secret_and_tenant_id_step_by_step_guide
# How to get your Microsoft Active Directory / Entra ID Client ID, Client Secret and Tenant ID: Step-by-step guide
------
_April 24, 2023_
Developers connecting to Microsoft Active Directory / Entra ID will need an Client ID, Client Secret and Tenant ID for authentication.
Here's how to generate, configure, and copy your Microsoft Active Directory / Entra ID Client ID, Client Secret and Tenant ID so you can use it in your integration or share steps with your customers.
# Microsoft AD/Entra ID Client ID and Secret Authentication
## **Step: 1 Register an application**
Registering your application establishes a trust relationship between the 3rd-party application and the Microsoft identity platform. The trust is unidirectional: the application trusts the Microsoft identity platform, and not the other way around. Once created, the application object can't be moved between different tenants.
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
2. If you have access to multiple tenants, use the **Settings** icon in the top menu to switch to the tenant in which you want to register the application from the **Directories + subscriptions** menu.
3. Browse to **Identity** > **Applications** > **App registrations** and select **New registration**.
4. Enter a display **Name** for the application.
5. Specify who can use the application in the **Supported account types** section
6. Select **Register** to complete the initial app registration.

When registration finishes, the Microsoft Entra admin center displays the app registration's **Overview** pane. On this page, the app was assigned values for:
- **Application (client) ID** which uniquely identifies your application in the Microsoft cloud ecosystem, across all tenants.
- **Object ID** which uniquely identifies your application in your tenant.

## Step 2: Generate Client Secret
Credentials are used by [confidential client applications](https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-applications) that access a web API. Examples of confidential clients are web apps, other web APIs, or service-type and daemon-type applications. Credentials allow your application to authenticate as itself, requiring no interaction from a user at runtime.

For this case, go to the Client Secrets tab, to generate a new Client Secret. The generated secret's value will be the one that will be used in Unified's authentication process.
## **Step 3: Configure permissions for Microsoft Graph**
Microsoft Graph exposes [application permissions](https://learn.microsoft.com/en-us/graph/permissions-overview#application-permissions) for apps that call Microsoft Graph with their own identity. These permissions always require administrator consent.
Preconfigure the application permissions the app needs when you register the app. An administrator can consent to these permissions either by using the [Microsoft Entra admin center](https://entra.microsoft.com/) when they install the app in their organization, or you can provide a sign-up experience in the app through which administrators can consent to the permissions you configured. Once Microsoft Entra ID records the administrator consent, the app can request tokens without having to request consent again.
To configure application permissions for the app in the app registrations experience on the Microsoft Entra admin center, follow these steps:
- On the application's **API permissions** page, choose **Add a permission**.
- Select **Microsoft Graph** > select **Application permissions**.
- In the **Select Permissions** dialog, choose the permissions to configure to the app.
The following screenshot shows the **Select Permissions** dialog box for Microsoft Graph application permissions.

## **Step 4: Grant tenant-wide admin consent in Enterprise apps pane**
You can grant tenant-wide admin consent through the **Enterprise applications** pane if the application is already provisioned in your tenant. For example, an app could be provisioned in your tenant if at least one user consents to the application. For more information, see [How and why applications are added to Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/how-applications-are-added).
To grant tenant-wide admin consent to an app listed in **Enterprise applications** pane:
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/) as at least a [Cloud Application Administrator](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#cloud-application-administrator).
2. Browse to **Entra ID** > **Enterprise apps** > **All applications**.
3. Enter the name of the existing application in the search box, and then select the application from the search results.
4. Select **Permissions** under **Security**.

5. Carefully review the permissions that the application requires. If you agree with the permissions the application requires, select **Grant admin consent**.
## Step 5: Setup Authentication in Unified
Setup the Authentication credentials in Unified using the client ID, and generated secret, along with the tenant ID

You've now created your Microsoft Active Directory / Entra ID Client ID, Client Secret and Tenant ID - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 514 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your Microsoft Azure AD OAuth 2 credentials
URL: https://docs.unified.to/guides/how_to_get_your_microsoft_azure_ad_oauth_2_credentials
# How to get your Microsoft Azure AD OAuth 2 credentials
------
_June 3, 2024_
This guide will show you how to obtain your OAuth 2 credentials for Microsoft Azure Active Directory (Azure AD), which are also needed for other integrations in the Microsoft Azure ecosystem e.g. Outlook, Teams, and Dynamics.
## Register an app in Microsoft Azure Portal
1. Navigate to [Microsoft Azure Portal](https://portal.azure.com/)
2. Search for **Azure Active Directory** and select it.

3. From the sidebar, under Manage, click **App registrations** and then **New registration.**

4. Fill in the following form to register a new app:
1. Enter any name you'd like for your app.
2. Select **Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)** to allow anyone to log in.
3. Under **Redirect URI**, enter [`https://api.unified.to/oauth/code`](https://api.unified.to/oauth/code)
4. Click **Register**.

## Get your client ID and client secret
1. After registering your app and on the resulting page, copy the **Client ID** (found next to **Application (client) ID**)
2. Click on **Add a certificate or secret** (found next to **Client credentials)**

3. Under **Client secrets (0)**, click **New client secret**.

4. Click **Add** - do not change anything else in the dialog.

5. On the next page, copy the **Value**

## Enter your OAuth credentials on Unified.to
1. Take note of your Microsoft client ID and client secret from the above steps
2. Navigate to the [app.unified.to](https://app.unified.to/) and find the integration page for the Microsoft integration you are interested in e.g. [https://app.unified.to/integrations/microsoft_ad](https://app.unified.to/integrations/microsoft_ad)
3. Enter your client ID
4. Enter your client secret

5. Make sure that you enable the correct permission scopes for your application.

## Troubleshooting
**I'm getting an 'invalid client secret' error**
This error indicates a problem with the client ID and/or secret that you generated. Please ensure you copied the correct values to the integration settings page on Unified.to, or regenerate these credentials in Microsoft Azure Active Directory and then try again.
## How to get your OAuth 2 credentials for Gmail
URL: https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_gmail
# How to get your OAuth 2 credentials for Gmail
------
_June 25, 2024_
_Last updated: May 2026_
To access Google services such as Gmail (also known as Google Mail) through the [Unified API](https://unified.to/technology), you'll generate and retrieve your [OAuth](https://unified.to/embeddedauth) 2 credentials in the Google Cloud Console, then enter them in Unified.to.
## Create or select a project on Google Cloud Console
1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/).
2. From the top nav bar, select an existing project or create a new one.
- If creating a new project, name it whatever you want and click **Create**.

## Enable the Gmail API
1. With your project selected, open the left sidebar and navigate to **APIs & Services > Library** (or search for **Library** from the top nav bar).
2. Search for **Gmail API** and click the matching result.
3. Click **Enable**.
## Configure your OAuth consent screen
Google requires you to configure the OAuth consent screen before it will let you create an OAuth client ID. The consent screen is what users see when they grant your app access to their Gmail.
1. Open the left sidebar and navigate to **APIs & Services > OAuth consent screen**.
2. Choose the user type:
- **Internal** — for Google Workspace organization-internal apps.
- **External** — for consumer Gmail accounts or public apps.
3. Fill in the required fields: app name, user support email, and developer contact information (and, for production, your authorized domains).
4. Complete the consent screen setup.
## Get your OAuth 2 credentials
1. Navigate to **APIs & Services > Credentials** (or search for **Credentials** from the top nav bar).
2. Click **Create Credentials** and select **OAuth client ID**.
3. Select **Web application** as the application type, and name your app whatever you want.
4. Under **Authorized redirect URIs**, enter `https://api.unified.to/oauth/code` (or the corresponding EU/AU regional URL if your workspace is hosted there).
5. Click **Create**.
6. A dialog will appear displaying your new **Client ID** and **Client Secret**. Copy both — you'll need them for the next step.
> Store your Client Secret securely. Never commit it to source control or include it in client-side code; keep it in environment variables or a secrets manager.
## Activate Gmail in Unified.to
1. Navigate to the [Gmail integration page on Unified.to](https://app.unified.to/integrations/googlemail).
2. On the **Authorization** tab, with **OAuth 2** selected as the authentication method, enter your **Client ID** and **Client Secret**.
3. Click **Update** to save your changes and activate the Gmail integration.
## Set your OAuth 2 reauthentication policy
If you're connecting a **Google Workspace** account, make sure your reauthentication policy is set correctly for the integration to keep functioning. This step requires Google Workspace admin access and doesn't apply to consumer Gmail accounts.
1. In the Google Workspace Admin Console, navigate to **Menu > Security > Access and data control > Google Cloud session control**.
2. On the left, select the organizational unit where you want to set the session length. For all users, select the top-level organizational unit. (An organizational unit initially inherits its parent's settings.)
3. Under **Reauthentication policy**, select **Never require reauthentication**.

You can now use the Unified API to build new experiences with Gmail. Happy building!
→ [Start your 30-day free trial](https://app.unified.to/login)
→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)
## How to get your OAuth 2 credentials for Microsoft Dynamics 365
URL: https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365
# How to get your OAuth 2 credentials for Microsoft Dynamics 365
------
_October 29, 2024_
_Last updated: May 2026_
This guide explains how to obtain your OAuth 2.0 credentials and configure API permissions for Microsoft Dynamics 365 business applications in Microsoft Entra ID (formerly Azure Active Directory).
## Background
Business applications in Microsoft Dynamics 365 use Microsoft's identity platform, Entra ID, for secure authentication. By registering your application, you establish a trusted connection that allows Unified.to to access Dynamics 365 data through the Dynamics 365 Web API (Dataverse) and, where applicable, Microsoft Graph and other Microsoft APIs. This works through:
- **Delegated access**: Your application acts on behalf of the signed-in user. The user is redirected to Microsoft's sign-in page, authenticates, and grants your application consent, with permissions approved by both the administrator and the user.
- **OAuth 2.0 credentials**: A client ID (which identifies your application) and a client secret (which serves as your application's password).
- **Permissions**: Specific capabilities your application requests, which must be approved by an administrator.
For more information, see [What is the Microsoft identity platform?](https://learn.microsoft.com/en-us/entra/identity-platform/v2-overview)
## Before you begin
Ensure you have:
- Access to the Microsoft Entra admin center ([entra.microsoft.com](https://entra.microsoft.com/))
- Administrator access to Microsoft Entra ID
## Register your application
You'll first register an application in the Microsoft Entra admin center. This provides you with a client ID and client secret.
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
2. In the left sidebar, go to **App registrations**, then click **New registration**.

3. Configure your application:
- Enter a meaningful name (for example, your product name).
- Under **Supported account types**, select **Accounts in any organizational directory (Any Microsoft Entra ID directory - Multitenant)**.
- In the **Redirect URI** section:
- Select **Web** as the platform.
- Enter `https://api.unified.to/oauth/code` as the redirect URI (or the corresponding EU/AU regional URL if your workspace is hosted there).
- Click **Register**.
The redirect URI is where Microsoft sends users and authorization codes after they sign in and grant access, so it must exactly match what Unified.to expects.

## Get your Dynamics 365 client ID and secret
1. **Retrieve your client ID:**
- On your application's overview page, locate the **Application (client) ID**.
- Copy this value — this is your client ID.
2. **Create a client secret:**
- In the left menu, select **Certificates & secrets**.
- Under **Client secrets**, click **New client secret**.
- Provide a description (optional, but helpful for tracking).
- Set the expiration to its maximum value, unless your security policy requires shorter rotation.
- Click **Add**.
- Immediately copy the secret **Value** (not the secret ID). You won't be able to view it again after leaving this page, so store it securely right away — never commit it to source control or share it client-side.
## Configure Dynamics 365 API permissions
1. **Set up Microsoft Graph permissions.** These are used to obtain information about your users.
- In the left menu, click **API permissions**.
- Click **Add a permission**, select **Microsoft Graph**, then choose **Delegated permissions**.
- Search for and add the following permissions:
- `openid`
- `email`
- `offline_access`
- Click **Add permissions**.
2. **Set up permissions for your Dynamics 365 business application.**
- From the **API permissions** screen, click **Add a permission** again.
- Under **More Microsoft APIs**, select the API backing your Dynamics instance:
- If you're using **Dynamics Sales**, select **Dynamics CRM**.
- If you're using **Dynamics Customer Engagement**, select **Customer Insights**.
- Choose **Delegated permissions**.
- Search for and add the following delegated permission on the Dynamics API:
- `user_impersonation` (required for basic access)
- Click **Add permissions**.

Depending on the specific Dynamics app or environment, you may see slightly different API names (for example, Dataverse or a specific Dynamics module), but you should always choose the API backing your Dynamics instance and add `user_impersonation`. Consent to these permissions is granted when an administrator connects the integration through Unified.to's authorization flow.
## Enter your Dynamics 365 credentials on Unified.to
1. Go to [app.unified.to/integrations](https://app.unified.to/integrations) and search for the Dynamics 365 integration you're using.
2. Enter your client ID and client secret from the steps above.
3. Save your changes.
You can now use the Microsoft Dynamics 365 integration in your application. Happy building!
→ [How to troubleshoot unhealthy connections](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections)
[→ Start your 30-day free trial](https://app.unified.to/login)
[→ Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)
## How to get your OAuth 2 credentials in PipeDrive
URL: https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_in_pipedrive
# How to get your OAuth 2 credentials in PipeDrive
------
_August 26, 2024_
This guide explains how to create a public developer app in Pipedrive and obtain your OAuth 2 credentials. You'll need these credentials in order to authenticate your app with Unified.to.
_Note: The state parameter is not automatically available in Marketplace Manager. To enable it for your app, please write to marketplace.devs@pipedrive.com._
## Before you begin
Before you create a developer app in Pipedrive, ensure:
- You have a Pipedrive developer sandbox account. If you don't have one, sign up at [Pipedrive's developer portal](https://developers.pipedrive.com/).
## Access the Developer Hub
1. Log in to your Pipedrive account.
2. Click on your profile name in the upper right corner of the top navigation bar.
3. Select **Developer Hub** from the drop-down menu.

## Create a Pipedrive public app
1. Click **Create an app** (or "+ Create an app" if you have existing apps).
2. Select **Create public app**.
3. Fill in your app details under **Basic info**.
1. In the **Callback URL** field, enter: `https://api.unified.to/oauth/code`

4. Click **Save.** You will be redirected to the **OAuth & access scopes** page after your app is created.
## Configure scopes and get your OAuth 2 credentials
1. Select the scopes your app requires. Only choose scopes that are necessary for your app's functionality.
1. For example, if you want to be notified about new or updated deals from Pipedrive, select **Deals** and choose whether you need read-only access or full (i.e. create, update, read, delete) access.
2. Scroll down to the **Client ID** section.
3. Here you'll find your `client_id` and `client_secret`. Make a note of them as you will need it for the final step.
4. Click **Save**.
## Activate the Pipedrive integration in Unified.to with your client ID and secret
1. Open a new tab and navigate to [the Pipedrive integration in Unified.to](https://app.unified.to/integrations/hubspot)
2. You will need the following two pieces of information from the earlier steps:
1. **Client ID**
2. **Client secret**
3. Copy and paste these values into their respective fields on the page. Make sure that **OAuth 2** is selected under **Authentication Method**.
4. Click **Activate**.
## How to get your OpenAI API Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_openai_api_key_step_by_step_guide
# How to get your OpenAI API Key: Step-by-step guide
------
_February 8, 2024_
To access OpenAI data or automate workflows through an integration, an API Key is required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
## How to get an OpenAI API key (LLM + Ads)
### 1) Create / sign in to your OpenAI account
1. Go to [https://platform.openai.com/](https://platform.openai.com/)
2. Sign in (or create an account).
### 2) Create an API key (for LLM / general API use)
1. In the OpenAI Platform, open **API keys**:
- [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys)
2. Click **Create new secret key**.
3. Give it a recognizable name (e.g., `unified-prod`, `unified-dev`).
4. Copy the key immediately and store it in your password manager / secrets vault.
- You won't be able to see the full key again after you close the dialog.
**Use this key for:**
- LLM requests (Responses API / Chat Completions)
- Embeddings
- Ads API
### 3) Add billing (if required)
If you get "insufficient_quota" or similar errors:
1. Open **Billing** in the OpenAI Platform:
- [https://platform.openai.com/account/billing](https://platform.openai.com/account/billing)
2. Add a payment method and/or purchase credits (depending on your plan).
### OpenAI Ads
---
OpenAI does **not** issue a separate "Ads API key". for the Unified API, you use the **same OpenAI Platform API key** created above.
Getting your OpenAI API Key is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 510 custom connectors, you get one secure, normalized API layer across 30 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your Paycom SID/Username and Token/Password: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_paycom_sid_username_and_token_password_step_by_step_guide
# How to get your Paycom SID/Username and Token/Password: Step-by-step guide
------
_August 28, 2023_
To access Paycom data or automate workflows through an integration, a SID/Username and Token/Password are required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
Please request assistance from your Paycom Account Representative for the following items:
1. Enabling the REST API for your Paycom instance
2. Whitelisting your OrgChart server IP address
3. Creating an API Service User
4. Generating valid authentication credentials (SID and Token)
Getting your Paycom SID/Username and Token/Password is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 475 custom connectors, you get one secure, normalized API layer across 29 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your PCRecruiter Username, Password, Database ID, App Key and App ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_pcrecruiter_username_password_database_id_app_key_and_app_id_step_by_step_guide
# How to get your PCRecruiter Username, Password, Database ID, App Key and App ID: Step-by-step guide
------
_August 18, 2023_
Developers connecting to PCRecruiter will need an Username, Password, Database ID, App Key and App ID for authentication.
Here's how to generate, configure, and copy your PCRecruiter Username, Password, Database ID, App Key and App ID so you can use it in your integration or share steps with your customers.
To find your Database ID:
1. Go to **System** in the top navigation bar.
2. Go to **API** in the menu that opens.
3. Go to **Api Settings** under API.
4. This will open a menu that displays the correct **"Api Database Id"**.
Getting your PCRecruiter Username, Password, Database ID, App Key and App ID is the first step. The bigger challenge is supporting dozens of vendors - each with unique auth flows, schemas, and maintenance requirements.
Unified.to is a real-time integration platform built to replace that work. Instead of building and maintaining 579 custom connectors, you get one secure, normalized API layer across 31 SaaS tools.
On top of that, our usage-based pricing scales cleanly with your product. No per-connection fees, no setup charges, and no lock-in, just infrastructure economics that grow with you.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to discover how predictable integration costs should be.
## How to get your SAP SuccessFactors (OpenID Connect) Username, Password, Client ID, Client Secret, Dependency Name, IAS Host and API URL: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_sap_successfactors_openid_connect_username_password_client_id_client_secret_dependency_name_ias_host_and_api_url_step_by_step_guide
# How to get your SAP SuccessFactors (OpenID Connect) Username, Password, Client ID, Client Secret, Dependency Name, IAS Host and API URL: Step-by-step guide
------
_August 13, 2025_
To access SAP SuccessFactors (OpenID Connect) data or automate workflows through an integration, a Username, Password, Client ID, Client Secret, Dependency Name, IAS Host and API URL are required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
## **Procedure**
1. Sign into the administration console of your SAP Cloud Identity Services instance.
2. Go to **Applications and Resources** **[Your OIDC Application]** **Application APIs** **Client Authentication** **Secrets**, add a new secret. Once created, copy the client ID and secret for later use. Note that you won't be able to retrieve the secret from the system later. For more information, see [Configure Secrets for API Authentication](https://help.sap.com/docs/cloud-identity-services/aa08922a434a456ba44982c9f4f4d790/dev-configure-secrets-for-api-authentication).
You've now retrieved your SAP SuccessFactors (OpenID Connect) Username, Password, Client ID, Client Secret, Dependency Name, IAS Host and API URL. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 475 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your SAP SuccessFactors Username, Company ID, Password and API URL: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_sap_successfactors_username_company_id_password_and_api_url_step_by_step_guide
# How to get your SAP SuccessFactors Username, Company ID, Password and API URL: Step-by-step guide
------
_April 24, 2023_
When connecting SAP SuccessFactors to other platforms, authentication requires a Username, Company ID, Password and API URL.
Follow these step-by-step instructions to generate one from your SAP SuccessFactors account, or provide them to your customers if they need to connect their account.
Please follow the following instructions to obtain your SAP SuccessFactors API credentials.
## Step 1: Find your API Server URL
- Go to this [link](https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/93f95815070049ebaaff042d8322d518/af2b8d5437494b12be88fe374eba75b6.html#api-servers) and find your data-center (DC). Remember your API Server URL. For example, if your domain was https://salesdemo2.successfactors.com, search for `salesdemo2`

## Step 2: Find your Username and Company ID
- To find your SAP SuccessFactors `username`, log-in to SuccessFactors, then click on your profile image in the upper-right corner to view your username.

- You SHOULD create a new username/password just for API access so that you do not use your own login credentials.
- To find your `Company ID`, you can click on `Show version information` in the top-right menu

- An alternate method to find your `Company ID` under the company settings

## Step 3: Set Security Configuration
- Set the `Allow Admin to Access OData API through Basic Authentication` permission in `Admin Center > OData API Basic Authentication Configuration` page.
- If you require specific IP Addresses to access the SuccessFactors API, then use the following IP addresses: `44.199.69.244` `3.65.142.239` `13.239.151.208`. This is performed in the `Admin Center > OData API Basic Authentication Configuration` or the `API Center > OData IP Allowlisting` pages.
You've now created your SAP SuccessFactors Username, Company ID, Password and API URL - just one of many if you support multiple vendors. Each one adds new auth flows, schema differences, and ongoing maintenance.
Unified.to removes that overhead with unified objects that work across 514 APIs. Map once, launch everywhere. Your team ships features instead of debugging vendor-specific edge cases.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how much faster you can launch customer integrations with Unified.to.
## How to get your Shopify Admin API access token and Store ID: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_shopify_admin_api_access_token_and_store_id_step_by_step_guide
# How to get your Shopify Admin API access token and Store ID: Step-by-step guide
------
_November 14, 2023_
If you're building an integration with Shopify, you'll need an Admin API access token and Store ID from your customer's Shopify account to authenticate requests.
This guide shows you (or your customers) how to generate one from Shopify's account settings.
## How do I find my Shopify access token and store ID?
Your access token and store ID can be found under **Settings** _>_ **Apps and sales channels** _>_ **Develop Apps** _>_ **Your_App**_._
If you have not used access tokens in the Shopify admin before, this guide will walk you through how to do that as well as how to retrieve your unique store ID. Note: These instructions are adapted from the [Custom apps](https://help.shopify.com/en/manual/apps/app-types/custom-apps#get-the-api-credentials-for-a-custom-app) guide in Shopify.
### Step 1: Activate custom app development
1. In the Shopify admin dashboard, click **Settings** > [**Apps and sales channels**](https://admin.shopify.com/settings/apps).
2. Click **Develop apps**.

3. Read the disclaimer, and then click **Allow custom app development**.
### Step 2: Create a custom app
1. On the same page, click **Create an app**.
2. In the modal that appears, enter the **App name** and select an **App developer**. This can be the store owner or any collaborator with the **Develop apps** permission.

3. Click **Create app**.
### Step 3: Configure Admin API scopes
1. On the same page, click **Configure Admin API scopes**

2. Select all of the following checkboxes (tip: use the search bar to quickly look these up)
- **Customers:** `read_customers`
- **Inventory:** `read_inventory`, `write_inventory`
- **Locations:** `read_locations`
- **Orders:** `read_orders`, `write_orders`
- **Products:** `read_products`, `write_products`

1. Click **Save**
### Step 4: Install your app and retrieve the access token
1. Navigate to **API credentials** and then click **Install app**

2. In the modal that appears, click **Install**
3. The page will now display a form containing your access token.

4. Click on **Reveal token once**
5. Copy and paste the token to the **Sign in and Authorize page**
### Step 6: Retrieve your Shopify store ID
1. While you are in the Shopify admin dashboard, observe the URL in your address bar - it should look something like this: `https://admin.shopify.com/store/9564e1-49`
2. Your unique Shopify store ID is the part that comes after `/store` e.g. `9564e1-49` in the above example
3. Copy and paste the ID to the **Sign in and Authorize** page
You've now retrieved your Shopify Admin API access token and Store ID. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 475 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your Wayfair Client ID, Client Secret and Vendor Encrypted Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_wayfair_client_id_client_secret_and_vendor_encrypted_key_step_by_step_guide
# How to get your Wayfair Client ID, Client Secret and Vendor Encrypted Key: Step-by-step guide
------
_November 19, 2024_
If you're building an integration with Wayfair, you'll need an Client ID, Client Secret and Vendor Encrypted Key from your customer's Wayfair account to authenticate requests.
This guide shows you (or your customers) how to generate one from Wayfair's account settings.
Your `Encrypted API Key` can be found in the UI for Supplier management of Vendor apps.
## Steps:
1. Go to the Supplier Integration Status page
2. Select "Vendor Applications" tab
3. Hit "Manage" on the Catalog API row
4. Select your Vendor from the list
5. Grant the needed permissions (see overview of permissions below)
1. Note: Only the permissions that correspond to capabilities that a given Vendor
has adopted will be viewable + grantable.
6. Overview of permissions:
1. **read-product-classes**; Gets the product classes that suppliers can list items for sale
2. **read-product-catalog**; Export the supplier product catalog
3. **write-product-generic-description**; Update the generic descriptions for products
4. **read-product-generic-description-update-status**; Checks the status of generic description updates
5. **read-product-generic-descriptions-by-part**; Get the existing generic descriptions given a supplier part number
6. **read-media-metadata-tags**; Get the metadata tags associated with media;
7. **read-media-uploaded**; Get the list of media that was uploaded for a product
8. **read-media-uploaded-status**; Get the uploaded status for a given media request
9. **write-media-upload-from-url;** Uploads new media and associates it with a product
10. **read-product-create-submit-status;** Check the status of a new product request
11. **read-product-create-questions**; Get the questions required by a product class for listing new items for
sale
12. write-product-create-submit; Submit a new product to be listed on site
13. read-supplier-brand-associations; Get the supplier brands that are authorized for the supplier to sell
7. On the Vendor application page, can copy their encrypted key by pressing the "Copy Encrypted Key" button.
1. This encrypted key is just an encrypted supplier ID + name. This is more secure
than using inputing SuID into the Vendor's system.
Generating a Wayfair Client ID, Client Secret and Vendor Encrypted Key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 475 integrations and 29 categories.
## How to get your Workable API Token and Subdomain: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_workable_api_token_and_subdomain_step_by_step_guide
# How to get your Workable API Token and Subdomain: Step-by-step guide
------
_April 24, 2023_
Workable supports authentication with API Token and Subdomain. To connect, you'll need to generate secure API Token and Subdomain from your account settings.
This short guide shows you how to generate one, whether you're testing in your sandbox or helping customers connect their Workable account.
## Generate an API access token
This guide shows how to generate an API access token for your Workable account.
To start, open the drop-down menu on the top right of your screen and select "Integrations" or press [here](https://www.workable.com/backend/account/integrations):

Now click on the "Generate new token" button:

Once you click the generate button, you'll see the new access token being generated:

This token type does not support `r_employees` scope. There are also new scopes like `w_employees` and `r_account` that are not currently supported by access tokens already used for ATS related endpoints. You need to contact support to enable them for your account tokens.
### Subdomain
You will also need the account subdomain which you can find in the company profile settings page:

### Revoking The Access Token
Be aware that you can revoke this access token anytime, but if you decide to proceed, every script or application that accesses the Workable API through this token will stop functioning. To keep things running smoothly after revoking your current token, you will have to generate a new access token and update every script or application bound to the previous one.
You've now retrieved your Workable API Token and Subdomain. That's one system covered, but scaling integrations one by one means repeating this process hundreds of times.
Unified.to handles this for you: 547 integrations out of the box, all with normalized schemas, real-time delivery, and Virtual Webhooks. No polling scripts, no retry logic, no maintenance backlog.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how Unified cuts integration work from quarters to days.
## How to get your yotpo APP Key / Store ID and Secret Key: Step-by-step guide
URL: https://docs.unified.to/guides/how_to_get_your_yotpo_app_key_store_id_and_secret_key_step_by_step_guide
# How to get your yotpo APP Key / Store ID and Secret Key: Step-by-step guide
------
_April 4, 2025_
To access yotpo data or automate workflows through an integration, a APP Key / Store ID and Secret Key are required.
This guide walks you through where to find it and how to set it up for development or end-customer use.
# **Retrieving your app key**
**Your app key is sometimes referred to as your Store ID.**
**To retrieve your app key:**
1. **In Yotpo Reviews, click the Profile icon at the top right corner of the screen.**
2. **Select Account Settings >** [**General Settings**](https://settings.yotpo.com/#/general_settings)**. You'll find your App Key at the bottom of the General Settings section.**
**If you need a Secret Key, you'll need to generate it.**
# **Generating your secret key**
**Your secret key is sometimes referred to as your API secret.**
**To generate your secret key:**
1. **Follow the steps above to get to your** [**Account Settings**](https://settings.yotpo.com/#/general_settings)**.**
2. **From your General Settings, click Get secret key. We'll send an email with a verification code to the email address associated with your account.**
**3. Paste the code from the email and click Submit. Your secret key will be revealed on the next screen. Remember, only share your secret key with parties that should have access to your data.**
Generating a yotpo APP Key / Store ID and Secret Key is the first step. The harder part is building pipelines that stay reliable as customers add more systems.
Unified.to gives you options: deliver data via API, streaming webhooks, database sync, or even as MCP tools for AI agents. One integration layer that adapts to your architecture instead of forcing tradeoffs.
[Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified) to see how we simplify integration delivery across 506 integrations and 30 categories.
## How to Handle Attachments in Invoice, Bill and Credit Memo
URL: https://docs.unified.to/guides/how_to_handle_attachments_in_invoice_bill_and_credit_memo
# How to Handle Attachments in Invoice, Bill and Credit Memo
------
_August 5, 2025_
Unified.to provides a standardized way to read, upload, and manage attachments (e.g., invoices, receipts, contracts) across various integrations like accounting, CRM, and etc platforms. Here's everything you need to know to get started with attachments via the `Storage File` endpoint.
---
### 🔍 How to Check If an Integration Supports Attachments
To check if a specific integration supports reading or writing attachments:
1. **Go to the Integration Page** in the Unified.to dashboard.
2. Navigate to **Feature Support**.
3. Select the relevant **endpoint** (e.g., `Accounting → Invoice`).
4. Look under **Readable Fields** — if `attachments` is listed, the endpoint supports reading attachments.
🛠️ **Tip:** You can also check support programmatically using the `/storage` endpoint.
---
### 📂 How to List All Attachments
To fetch all available attachments across an integration:
- Use the `GET /storage` (Storage File List) endpoint.
⚠️ **Note:** Most integrations **do not support** listing all attachments globally. Unified.to reflects this limitation. However, some integrations like **Sage** _do_ support it.
---
### 📌 How to Fetch Attachments for a Specific Record (e.g., Invoice)
To get attachments for a specific entity like an invoice, bill, or candidate:
- Use the `GET /storage` endpoint.
- Pass the corresponding `parent_id` (e.g., `invoice_id`) in the query parameters.
Example:
```plain text
http
CopyEdit
GET /storage-file?parent_id=inv_1234
```
---
### 🧾 How to Get Details of a Specific Attachment
To retrieve metadata (name, file type, size, etc.) of a particular attachment:
- Use either:
- `GET /storage/:id` — to get a single attachment
- `GET /storage` — to filter and retrieve multiple files with query params
---
### 📥 How to Download an Attachment
We return a secure `download_url` in the response when you fetch an attachment. You can use this URL to download the file directly.
⚠️ **Note:** The `download_url` is **time-limited** and expires after a few minutes for security reasons. Always use the URL shortly after fetching.
---
### 📤 How to Upload or Create an Attachment
To upload a new attachment:
1. Go to the **Integration Page** and confirm the `storage-file` endpoint is supported under Feature Support.
2. Use the `POST /storage-file` endpoint with required fields.
Key fields:
- `name`
- `data` (base64 encoded) | `download_url`
- `parent_id` (e.g., `invoice_id`, `bill_id`, `employee_id`)
Unified.to will automatically associate the attachment with the parent entity.
Example payload:
```json
json
CopyEdit
{
"file_name": "invoice-august.pdf",
"file_data": "base64-encoded-data",
"parent_id": "inv_1234"
}
```
---
### 🧷 How to Associate an Attachment with an Entity
When uploading a file, simply set the `parent_id` to the ID of the resource (invoice, bill, employee, etc.). Unified.to handles the rest — no extra linking needed.
---
### 💬 Questions or Requests?
- If you're unsure whether an integration supports attachments for a specific resource type, reach out to our support team.
- If a feature isn't currently supported, let us know! We're always looking to expand support where possible.
## How to migrate or import your integrations into Unified.to
URL: https://docs.unified.to/guides/how_to_migrate_or_import_your_integrations_into_unified
# How to migrate or import your integrations into Unified.to
------
_October 25, 2024_

This guide explains how to import your existing integrations into Unified.to, effectively creating new connections using your existing customer credentials. A connection is a secure link between your application and your customer's third-party account.
While connections are typically created through an auth flow where users grant access through the provider's authorization page, you can also create connections by importing existing credentials that your customers have already provided to you.
## Before you begin
This guide assumes you have:
- A Unified.to account.
- Existing customer credentials for the integrations you want to import.
- Basic understanding of REST APIs, authentication flows, and [connections](https://docs.unified.to/guides/end_users_integrations_and_connections#end-users-integrations-and-connections).
## Understand the authentication types
Before importing your integrations, you need to determine which authentication type each integration uses. Unified.to supports two main authentication flows:
### API token authentication
- Simple token: Requires a single token or key.
- Multi-field token: Requires multiple credentials (e.g. API key + domain).
### OAuth 2 authentication
- Requires client credentials (client ID and secret).
- Requires access tokens and optional refresh tokens.
- May include additional user information e.g. emails, names.
## Check instructions for the integration you want to import
The Unified.to Core API contains information that will help you determine what you need to successfully import your integrations.
1. Make a GET request to the `/unified/integration` endpoint to get information about the integration you want to import, passing in the categories and/or names of the integrations you are interested in.
2. Check the following fields in the response:
- `token_names`: Lists required credential fields for API token authentication.
- `token_instructions`: Provides guidance on where to find these credentials.
For example:
```javascript
const baseUrl = 'https://api.unified.to/unified/integration';
const params = new URLSearchParams({
categories: ['ats', 'crm'].join(',')
});
const url = `${baseUrl}?${params}`;
// Make the request
fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Accept': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(integrations => {
// Loop through each integration and log its auth details
integrations.forEach(integration => {
console.log(`\n=== ${integration.name} ===`);
// Log token information if it exists
if (integration.token_names && integration.token_names.length > 0) {
console.log('Required credentials:', integration.token_names);
console.log('How to find credentials:');
integration.token_instructions?.forEach((instruction, index) => {
console.log(`${index + 1}. ${instruction}`);
});
} else {
console.log('No token credentials required (likely uses OAuth)');
}
});
})
.catch(error => {
console.error('Error fetching integrations:', error);
});
/* Example output:
=== ActiveCampaign ===
Required credentials: ["API Key", "Domain"]
How to find credentials:
1. Your API key can be found in your account on the Settings page under the "Developer" tab...
2. Your API URL can be found in your account on the My Settings page under the "Developer" tab...
=== HubSpot ===
No token credentials required (likely uses OAuth)
*/
```
**API reference:** [Get all integrations](https://docs.unified.to/unified/integration/Returns_all_integrations).
## Import an API token integration
For single-token integrations, construct your connection object:
```json
{
"integration_type": NAME_OF_INTEGRATION,
"permissions": [PERMISSIONS],
"categories": [CATEGORIES],
"environment": "Production", // or any other non-sandbox environment
"auth": {
"token": "your_customer_token"
}
}
```
For multi-field token integrations, use the `other_auth_info` array:
```json
{
"integration_type": NAME_OF_INTEGRATION,
"permissions": [PERMISSIONS],
"categories": [CATEGORIES],
"environment": "Production", // or any other non-sandbox environment
"auth": {
"token": "your_customer_token"
"other_auth_info": [
"first_credential",
"second_credential"
]
}
}
```
**Important**: The order of credentials in `other_auth_info` must match the order of the `token_names` from the integration instructions (see above).
## Import an OAuth 2.0 integration
1. Construct your connection object with the OAuth credentials:
```json
{
"integration_type": NAME_OF_INTEGRATION,
"permissions": [PERMISSIONS],
"categories": [CATEGORIES],
"environment": "Production", // or any other non-sandbox environment
"auth": {
"access_token": "customer_access_token",
"refresh_token": "customer_refresh_token",
"expiry_date": "2024-12-31T23:59:59Z",
"emails": ["user@example.com"],
"name": "User Name"
}
}
```
1. Log into the developer account for the provider whose integrations you are importing. Update your redirect URIs to point to Unified.to: `https://api.unified.to/oauth/code`
**Notes**:
- Set `expiry_date` to the token's expiration date (if known) or today's date.
- Include the authenticating user's email and name if available.
## Create the connection
Make a POST request to create the connection. Example using `curl`:
```shell
curl -X POST "" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"integration_type": "your_integration_type",
"permissions": ["required_permissions"],
"categories": ["integration_category"],
"auth": {
// Your auth object here
}
}'
```
**API reference**: [Create a connection](https://docs.unified.to/unified/connection/Create_connection)
## Test the imported connection
After creating the connection:
1. Note the connection ID from the response.
2. Make a test API call using the new connection ID to verify it works.
3. If the call fails, check the error message and verify your auth credentials.
## Best practices when importing a connection
- Always include relevant `permissions` for the minimum set of data access you require.
- Use a non-sandbox `environment`.
- Store the connection ID securely for future API calls.
- Consider adding `external_xref` to link the connection to your customer's ID.
## See also
- [How to associate a connection ID with your end-user](https://docs.unified.to/guides/how_to_associate_a_connection_id_with_your_end_user#how-to-associate-a-connection-id-with-your-end-user)
- [How to troubleshoot unhealthy connections](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections#how-to-troubleshoot-unhealthy-connections)
- [How to create and configure webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
## How to obtain your GitHub OAuth2 credentials
URL: https://docs.unified.to/guides/how_to_obtain_your_github_oauth2_credentials
# How to obtain your GitHub OAuth2 credentials
------
_June 3, 2024_
In order to set up OAuth2 for Github, you need to create a new OAuth application in your [GitHub developer settings](https://github.com/settings/applications/new):

Make sure to fill in the appropriate details, but more importantly the Authorization Callback URL, set to [`https://api.unified.to/oauth/code`](https://localhost:8080/_renarde/security/github-success).
Now click on `Register application` and you'll be shown your application page:

You need to click on `Generate a new client secret`, confirm your credentials, and write down your Client ID and Client secret (especially that one, because you will not be able to see it again later from that page, but you can always recreate one, do don't worry too much):

Take your GitHub clientID and clientSecret and add them to the [Unified.to](https://unified.to/) integration settings for Github at [https://app.unified.to/integrations/github](https://app.unified.to/integrations/github)

You will now be ready to use GitHub on [Unified.to](https://unified.to/) realtime unified API.
## How to Power Claude with Live Customer Data Using Unified MCP
URL: https://docs.unified.to/guides/how_to_power_claude_with_live_customer_data_using_unified_mcp
# How to Power Claude with Live Customer Data Using Unified MCP
------
_May 28, 2025_

If you're building your software application with Anthropic's LLM API [a](https://claude.ai/)nd want more than a thin agent, your application needs access to real end-customer data — like pulling HubSpot deals, sending a Slack message, or updating a candidate in Greenhouse.
You can wire this up yourself using Unified's API, which will save you a ton of time as it is a single API for multiple SaaS APIs. Or you can point your LLM API at Unified's MCP server and launch even faster.
We'll break down when to use Unified's MCP server, and when to reach for the API instead.
## How Unified's MCP Server Works
Unified's MCP Server exposes real, user-authorized SaaS integrations to any LLM that supports Model Context Protocol.
Anthropic's API, Claude or Cursor can:
- Discover tools based on a given Unified connection
- Call those tools directly through our MCP endpoint
- Use the result in its next generation step
## Two Paths: API vs MCP
Unified supports two integration interfaces:
**1. Unified API (Backend Integration)**
Best for production-grade apps. You fetch and transform data yourself, persist what matters, and optionally embed it in a vector database. Full control, real scalability.
**2. Unified MCP Server (Direct-to-LLM)**
Best for prototyping or agent UXs. You point Anthropic's API, Claude or Cursor at a live MCP endpoint, and it auto-discovers available tools — all backed by your end-user's authorized connections.
## Why Claude + MCP Is the Best Fit
The strongest use case for Unified's MCP Server is direct integration with the Anthropic API. It's already a common pattern among Unified users: AI-native SaaS teams wiring real-time tools into their product experience.
With MCP, you now have two ways to access your customer's data:
1. Use the **Unified API** and call it from your backend
2. Point the **Anthropic API** at Unified's MCP Server and let it fetch tools on its own
This lets your team skip everything between OAuth and LLM — no middleware, no polling, no schema juggling. You stay focused on your product's core logic while Claude handles tool orchestration via Unified.
That's always been the promise of a Unified API: do less glue work and ship faster. MCP just makes that even more direct.
## What to Do if You're Building for Scale
If your product is heading toward scale, especially if it requires logging, observability, or context-aware data access, the better path is:
1. Use the Unified API to fetch and normalize data
2. Store and filter what matters
3. Embed it into a vector database
4. Let your LLM consume it through whatever interface makes sense
MCP is a fast path to working demos. The API is your infrastructure.
## Example Flow
Claude gets this prompt:
> 'Post a message to #sales about the new signed deal.'
It:
1. Identifies a tool call (e.g. `send-slack-message`)
2. Sends the tool and parameters to Unified's MCP Server
3. Unified makes the call to Slack using the user's connection
4. Claude receives the result and continues the conversation
No servers, no APIs, no code in between.
Unified's MCP Server is in beta. It supports any authenticated connection, works with Claude or Cursor, and helps you wire up AI features fast without touching infrastructure.
[Request early access](mailto:support@unified.to) or [read the MCP docs](https://docs.unified.to/mcp).
## How to register a Google developer app and get OAuth 2 credentials
URL: https://docs.unified.to/guides/how_to_register_a_google_developer_app_and_get_oauth_2_credentials
# How to register a Google developer app and get OAuth 2 credentials
------
_May 29, 2025_
> 💡 **Note** that if you use `Restricted` or `Sensitive` Scopes you need to contact Unified's Support to setup a CNAME for the API URL in order to get your App approved by Google
> Check [https://docs.unified.to/guides/how_to_set_up_a_custom_api_url](https://docs.unified.to/guides/how_to_set_up_a_custom_api_url).
1. **Create a Google Cloud Platform project:**
- Go to the Google Cloud Console (https://console.cloud.google.com/) and sign in.
- If you don't have a project, click the "Select a project" dropdown and choose "New Project".
- Give your project a name and click "Create".
2. **Enable the Necessary APIs**
- In the Google Cloud Console, make sure your newly created or existing project is selected.
- Navigate to [**APIs & Services**](https://console.cloud.google.com/apis/library)[ > ](https://console.cloud.google.com/apis/library)[**Library**](https://console.cloud.google.com/apis/library)
- Search for the specific API you want to use (e.g., "Gmail API", "Google Drive API", 'Google Calendar API').
- For Google Directory, make sure that you have the[ Admin SDK API ](https://console.cloud.google.com/apis/library/admin.googleapis.com)enabled.
- Click on the API from the search results.
- Click the "**Enable**" button. Repeat this for all Google APIs your application will need to access
3. **Configure the OAuth Consent Screen**
- In the Google Cloud Console, go to [Google Auth Platform](https://console.cloud.google.com/auth/overview)
- Fill in what is asked and click **Create**
- Click 'Create'
- Go to [Auth Branding](https://console.cloud.google.com/auth/branding) and fill in the required Information
4. **Scopes**: Select the scopes your application needs. Scopes define the permissions your app is requesting (e.g., read emails, access calendar). Be specific and only request the scopes you absolutely need.
- Go to [Auth Data Access](https://console.cloud.google.com/auth/scopes)
- You will at least need `openid`, `userinfo.email`, `userinfo.profile`, `admin.directory.group.readonly` and `admin.directory.user` or `admin.directory.user.readonly`.
- The scopes needed by other Google integrations can be found in the Unified App, for example:
- [Google Contacts](https://app.unified.to/integrations/googlecontacts?tab=oauth2)
- [Google Directory](https://app.unified.to/integrations/googledirectory?tab=oauth2)
- [Gmail](https://app.unified.to/integrations/googlemail?tab=oauth2)
- **Note** that if you use `Restricted` or `Sensitive` Scopes you need to contact Unified's Support to setup a CNAME for the redirect URL in order to get your App approved by Google
- Review the information and click "**Save and Continue**" through the steps. If you selected "External" and are just testing, you can often leave your app in "Testing" publishing status. If you intend for it to be publicly available, you'll eventually need to "Publish" it and potentially go through verification.
5. Create OAuth 2.0 Credentials
- Go to [Auth Clients](https://console.cloud.google.com/auth/clients) in the Google Cloud Console.
- Click on "**+ Create Client**" at the top of the page, Follow the steps
- Configure the authorized redirect URIs. Get the correct value from the Integration, for example [Google Drive](https://app.unified.to/integrations/googledrive?tab=oauth2)
- Copy your Client ID and Secret
- Enter these values in your Integrations, for example:
- [Google](https://app.unified.to/integrations/google)
- [Gmail](https://app.unified.to/integrations/googlemail?tab=auth)

1. Publish your Application
- Go to [Auth Audience](https://console.cloud.google.com/auth/audience)
- Click on Publish app

1. Once your application is approved and verified it will start showing your application name on the authorization consent screen. Until then it will be showing the 'unified.to' name, which is inferred from the redirect URL.

## How to register a Salesforce developer app and get OAuth 2 credentials
URL: https://docs.unified.to/guides/how_to_register_a_salesforce_developer_app_and_get_oauth_2_credentials
# How to register a Salesforce developer app and get OAuth 2 credentials
------
_February 12, 2023_
In this guide, we'll walk you through how to register a Salesforce developer app to get the OAuth 2 credentials you'll need to be a Salesforce integration for your product. Whether you're planning to build a custom-code integration or using a Unified API solution like [Unified.to](https://unified.to/), retrieving your OAuth 2 credentials is the first step in the process of integrating with Salesforce.
We'll also added directional for existing Unified.to users that are leveraging our Unified CRM API for a faster integration development process.
## What is a Salesforce Developer App?
A Salesforce developer app is a container for one or more developer apps. A developer app is an application that can access Salesforce data or functionality through the Salesforce REST, SOAP, or Streaming APIs.
A connected app defines the following information:
- The name and logo of the app
- The OAuth settings for the app, such as the callback URL, the scopes, and the refresh token policy
- The API version and the endpoints for the app
- The user profiles or permission sets that can access the app
By registering a developer app, you can generate a client ID and a client secret, which are the credentials you'll need to authenticate your requests to the Salesforce APIs
## Step 1: Register a Salesforce developer app
To register a Salesforce developer app, you'll need a Salesforce developer account. If you don't have one, sign up for free at [https://developer.salesforce.com/signup](https://developer.salesforce.com/signup).
Once you have a developer account, follow these steps to register a developer app:
1. Log in to your Salesforce developer account and go to **Setup**

2. In the Quick Find box, search for **App Manager** and click on it
3. Click on the **New External Client App** button
4. Fill in the basic information for your app, such as the name, contact email, and logo URL
5. Select `Packaged` in **Distribution State**
6. In the API (Enable OAuth Settings) section, check the **Enable OAuth Settings** checkbox
7. Enter the callback URL for your app (this is the URL that Salesforce will redirect the user to after they authorize your app). For [Unified.to](https://unified.to/) users, the callback URL needs to be `https://api.unified.to/oauth/code`, `https://api-eu.unified.to/oauth/code` or `https://api-au.unified.to/oauth/code`.
8. Select the OAuth scopes for your app (these are the permissions that your app will request from the user). To set up [Unified.to](https://unified.to/) successfully you'll need to add these OAuth Scopes: `Manage user data via APIs (api)` and `Platform requests at any time (refresh_token, offline_access)`. If you are also going to use Salesforce to sign-in your users into your application, then also set the `openid`,`profile`,`email`scopes.

9. Next, enable the following options for you app:
1. Enable Authorization Code and Credentials Flow
2. Require user credentials in the POST body for Authorization Code and Credentials Flow
3. Require Secret for Web Server Flow
4. Require Secret for Refresh Token Flow
10. Do NOT enable CODE CHALLENGE (Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows)

11. Click **Create**
12. Next click **Edit** under **Policies** tab

13. Set 'Refresh token is valid until revoked' and click **Save**

14. Lastly, go to Settings tab and retrieve you 'Consumer Key' (client ID) and 'Consumer Secret' (client secret).You will need these credentials to activate your Salesforce integration in [Unified.to](https://unified.to/).

## Step 2: Use Unified.to to Simplify Integration Development
Using Salesforce APIs directly can be a complex and time-consuming process, especially if you plan to integrate with other leading CRM platforms like HubSpot, ActiveCampaign, and Copper. Without Unified.to, you'll be tasked with handling authentication, error handling, rate limiting, data mapping, and the synchronization logic for each individual integration.
[Unified.to](https://unified.to/) simplifies this entire process, providing SaaS developers with a unified set of APIs, data models, and webhooks for 20+ CRM integrations. Add all the CRM integrations on your roadmap in a matter of hours.
## Step 3: Activate Salesforce in Unified.to (for Unified.to users)
1. [Log in](https://app.unified.to/login) and navigate to [**Active Integrations**](https://app.unified.to/integrations)
2. Use the search bar or click the **CRM** tab to select **Salesforce**
3. Click **Activate** on Salesforce
4. Enter the information from the previous step (client ID, client secret)
Once you've activated Salesforce along with other integrations, you'll be able to add integrations to your product using our low-code authorization UI embed.
## One API to integrate them all
You're reading this article on the [**Unified.to**](https://unified.to/) blog. We're a Unified API developer platform for SaaS customer-facing integrations. We're excited to continue to innovate at Unified.to and solve hard, critical integration-related problems for our customers. If you're curious about our integrations-as-a-service solution, consider [**signing up for a free account**](https://app.unified.to/login?utm_source=blog&utm_medium=blog_signup_salesforce&utm_campaign=blog_signup&utm_id=blog_signup) or [**meet with an integrations expert**](https://calendly.com/michelle-unified/discovery-via-blog)**.**
## How to register a Slack developer account and get OAuth 2 credentials
URL: https://docs.unified.to/guides/how_to_register_a_slack_developer_account_and_get_oauth_2_credentials
# How to register a Slack developer account and get OAuth 2 credentials
------
_January 3, 2025_
This guide walks you through how to register a Slack developer account, obtain OAuth 2 credentials, and configure scopes for your application.
## Before you begin
Make sure you have:
- A Slack account
- Admin access to a Slack workspace where you can test your app
## Create a Slack app
1. Go to the [Slack API website](https://api.slack.com/apps) and sign into your Slack account
2. Click **Create New App**
3. Select **From scratch**
4. Enter your app's name
5. Select the workspace where you want to install and test out your app
6. Click **Create App**

After you create your app, you'll be redirected to the app's settings page where you can find your Client Id and Client Secret. Make a note of these as you will need them to activate the Slack integration on Unfiied.to.

_Note: Store these values securely - never commit them to version control!_
## Configure OAuth 2 settings
In addition to activating the Slack integration, you'll need to configure the redirect URL and enable the correct scopes in order to successfully authorize a connection with Unified.to.
1. From your app's settings page, navigate to **OAuth & Permissions**
2. Under **Redirect URLs,** enter: [`https://api.unified.to/oauth/code`](https://api.unified.to/oauth/code)
3. Click **Save URLs**

4. If you are using the SlackBot integration, use **Bot Token Scopes.** If not, then use **User Token Scopes.** click **Add an OAuth Scope**
5. Search for and add the scopes you need for your application - refer to [this page ](https://app.unified.to/integrations/slack?tab=oauth2)to see the scopes that are supported by Unified.
1. For example, if you want to read messages, then you should choose the scopes that map to :
1. **messaging_channel_read**: `im:read, mpim:read, channels:read, groups:read`
2. **messaging_message_read**: `im:read, im:history, mpim:read, mpim:history, channels:history, channels:read, groups:read, groups:history`
## Activate the Slack integration with your credentials
1. Go to [https://app.unified.to/integrations/slack?tab=auth](https://app.unified.to/integrations/slack?tab=auth) or [https://app.unified.to/integrations/slackbot?tab=auth](https://app.unified.to/integrations/slack?tab=auth)
2. Enter your Client ID and Client secret from the steps above
3. Save your changes
# For a Slack Bot, follow these additional instructions
1. In the left sidebar, go to **"App Home"**
2. Under **"Your App's Presence in Slack"**, click **"Add"** or **"Edit"**
3. Set a **Display Name** and **Default Username** for your bot
4. Toggle on **"Always Show My Bot as Online"** (optional)
5. Go to **"Event Subscriptions"** in sidebar
6. Toggle **"Enable Events"** to On
7. Enter the Unified webhook URL `https://api.unified.to/webhook/workspace/slackbot?workspace_id={ID}`
8. Subscribe to most bot events like:
- message.channels
- app_mention
- message.im
9. Go to **"Interactivity & Shortcuts" to enable receiving information about button events**
10. Toggle On and enter in `https://api.unified.to/webhook/workspace/slackbot?workspace_id={ID}`
When a user authorizes your `slackbot` integration, their user ID will be stored in `connection.auth.user_id` and the bot ID will be stored in `connection.auth.app_id` .
Congratulations, you're now ready to use the Slack integration in your application.
Happy building!
## How to Register a Workday Developer App and Get OAUTH2 Credentials
URL: https://docs.unified.to/guides/how_to_register_a_workday_developer_app_and_get_oauth2_credentials
# How to Register a Workday Developer App and Get OAUTH2 Credentials
------
_March 13, 2023_
As a developer of an application in the HR space, you absolutely need to have a Workday integration so that you can access your customers' data in the most popular HRIS vendor, Workday.
Before you can use Unified.to's HRIS and ATS unified APIs with Workday, you must get your customer to generate Workday API credentials for your application.
## Register a new API Client in Workday
1. Sign-in to the Workday tenant
2. Search for 'Register API Client for Integration' using the search bar
3. Fill out the requested inputs with the following example details:
1. Client Name;
2. Refresh Token Timeout; 0 days (ie. never)
3. Non-Expiring Refresh Token: Checked
4. Scope: Choose all of the permission scopes for the use-case. Minimum should be 'Staffing' (for employee/worker access), 'Leave of Absence' (for timeoff). Always include 'Tenant Non-Configurable' and 'Integration' as well.

4. Once the API client has been registered, go to 'Related Actions' icon ⇒ 'API Client' ⇒ 'Manage Refresh Tokens for Integrations'

5. Remember the Client ID and Client Secret
6. Enter the 'Integration System User (ISU)' in the Workday Account field. This ISU needs to have access to all of the resources that you need for the API.
7. Check the "Regenerate New Refresh Token" box and click OK.

8. A new refresh token will be generated. Remember that Refresh Token.
## Use [Unified.to](https://unified.to/) to Simplify your Integrations Development
Using Workday APIs directly can be complex and time-consuming, especially if you want to integrate with multiple services or platforms. You will need to handle the authentication, the error handling, the rate limiting, the data mapping, and the synchronization logic for each integration.
A simpler and faster alternative is to use [Unified.to](https://unified.to/) APIs for HRIS and ATS integrations. It provides a unified set of APIs, data-models, and webhooks for most of the HRIS and ATS integrations.
## Activate Workday in Unified.to
1. Navigate to [Active Integrations](https://app.unified.to/integrations)
2. Search for Workday
3. Click on Activate on Workday
4. Enter in the information from the previous step (client ID, secret, endpoints/URLs)
## How to Register a Workday Developer App and Get OAUTH2/ SOAP Credentials
URL: https://docs.unified.to/guides/how_to_register_a_workday_developer_app_and_get_oauth2_soap_credentials
# How to Register a Workday Developer App and Get OAUTH2/ SOAP Credentials
------
_November 10, 2025_
# How to Register and Get OAUTH2 **Credentials for Workday:**
You will need 3 endpoints (URLs):
- REST API Endpoint
- Token Endpoint
- Authorization Endpoint
You will also need:
- Client ID
- Client Secret
### How to setup Client ID
1. **Log in to the Workday application**
2. Register API Client: Navigate to the "Register API Client" section
3. Select the Register API Client form
4. Select the Register API Client form
5. For the grant type, select "Authorization Code Grant'
6. For the access token type, select "Bearer'
7. In the Redirection URI field, enter "[https://api.unified.to/oauth/code](https://api.unified.to/oauth/code)"
8. In the Scope section, add Staffing and/or Recruiting, depending on your application's needs
9. Select "Non-expiring Refresh Tokens'
10. Select the Include Workday Owned Scope checkbox.
Click OK to save the app.

Once you create your application, Workday will display the information that you entered plus additional information.
### **Retrieve Endpoints**
1. Access "View API Clients"
2. Use the Workday search bar to navigate to "View API clients"
3. Retrieve REST API Endpoint, Token Endpoint, Authorization Endpoint
# How to Register and Get OAUTH2 **Credentials for Workday Legacy**:-
## **Integration System User(ISU)**
### Create User
1. Log in to the Workday tenant
2. search 'Create Integration System User'.
3. Goto 'Create Integration System User'
4. In the "Create Integration System User" page, go to the "Account Information" section.
5. Provide Username & Password
6. Require New Password should remain unchecked
7. Type 0 (zero) for Session Timeout Minutes to prevent session expiration
8. Click "OK" to save.
### Provide permissions to ISU
1. Search field, type "Create Security Group'
2. Select "Integration System Security Group" from the "Type of Tenanted Security Group" drop-down menu.
3. In the "Name" field, enter a name for the security group.
4. Integration System Security Group on Workday select 'Maintain Permissions for Security Group''
5. Configure the Permissions and select "Maintain' Operation
6. "Source Security Group" name matches your created security group.
7. Now add Domain Security Policy Permissions
8. Search 'Activate Pending Security Policy Changes'
### WSDL URL
1. Go to Workday, search 'Public Web Services'.
2. Click **"Public Web Services Report**'
3. Hover over the "Human Resources" section.
4. Click the three dots to open the menu.
5. Select "Web Services" and then click "View WSDL'
6. Goto bottom of the page and get host URL. That looks like following:- [**https://wd5-services1.myworkday.com/ccx**](https://wd5-services1.myworkday.com/ccx)**.**
## How to Register a ZendeskSell Developer App and Get OAUTH2 Credentials
URL: https://docs.unified.to/guides/how_to_register_a_zendesksell_developer_app_and_get_oauth2_credentials
# How to Register a ZendeskSell Developer App and Get OAUTH2 Credentials
------
_February 12, 2023_
If you are a developer who wants to build data integrations for your SaaS software, you might be interested in using the ZendeskSell API. The ZendeskSell API allows you to access and manipulate data from ZendeskSell, a powerful CRM platform that helps you manage your sales pipeline, contacts, deals, and tasks.
However, before you can use the ZendeskSell API, you need to register a developer app and get credentials. In this article, I will show you how to do that in a few simple steps.
## Step 1: Create a ZendeskSell account
If you don't have a ZendeskSell account yet, you need to create one first. You can sign up for a free trial here: [https://www.zendesk.com/register/?source=zendesk_sell](https://www.zendesk.com/register/)
Once you have created your account, you can log in to your ZendeskSell dashboard and access the settings menu.
## Step 2: Register a developer app
To register a developer app, you need to go to the Admin Settings and click on "APIs > Zendesk APIs" and then on 'Oauth Clients'. Then, you need to click on the "Add OAUTH2 Client" button and fill in the required information.
You need to provide a name, a description, a logo URL, and a redirect URI for your app. The redirect URI is the URL where ZendeskSell will send the authorization code after the user grants permission to your app. You can use any URL that you control, but it must match the one you will use in your code later. If you are going to use Unified.to, then this needs to be `https://api.unified.to/oauth/code`
You also need to select the scopes that your app will need to access the ZendeskSell data. The scopes are the permissions that define what your app can do with the ZendeskSell API. You can choose from the following scopes:
- read: Allows your app to read data from ZendeskSell
- write: Allows your app to write data to ZendeskSell
- delete: Allows your app to delete data from ZendeskSell
You can select one or more scopes depending on your app's functionality. However, you should only request the minimum scopes that your app needs to avoid unnecessary access.
After you fill in the information, you can click on the "Register App" button and your app will be created.
## Step 3: Get credentials
Once you have registered your app, you will see a confirmation page with your app's credentials. You will need these credentials to authenticate your app with the ZendeskSell API.
The credentials are:
- Client ID: A unique identifier for your app
- Client Secret: A secret key that you should keep confidential and never share with anyone
You should copy and save these credentials somewhere safe, as you will need them later in your code.
## Step 4: Use [Unified.to](https://unified.to/) to Simplify your Integrations Development
Using ZendeskSell APIs directly can be complex and time-consuming, especially if you want to integrate with multiple services or platforms. You will need to handle the authentication, the error handling, the rate limiting, the data mapping, and the synchronization logic for each integration.
A simpler and faster alternative is to use [Unified.to](https://unified.to/) APIs for CRM integration. It provides a unified set of APIs, data-models, and webhooks for 20+ CRM integrations.
## Step 5: Activate Workday in Unified.to
1. Navigate to [Active Integrations](https://app.unified.to/integrations)
2. Search for ZendeskSell
3. Click on Activate on ZendeskSell
4. Enter in the information from the previous step (client ID, secret)
## How to Register a Zoho Developer App and Get OAUTH2 Credentials
URL: https://docs.unified.to/guides/how_to_register_a_zoho_developer_app_and_get_oauth2_credentials
# How to Register a Zoho Developer App and Get OAUTH2 Credentials
------
_August 23, 2024_
Zoho has many applications that we support integrations with; ZohoCRM, ZohoBooks, ZohoPeople, and ZohoRecruit. If you are a developer who wants to build data integrations for your SaaS software with any of the Zoho product suite, you will need to register a developer app and get credentials from Zoho.
In this article, I will show you how to do that in a few simple steps.
## Step 1: Create a Zoho Developer Account
The first step is to create a Zoho Developer account, if you don't have one already. You can sign up for free at [https://developer.zoho.com/signup](https://developer.zoho.com/signup).
Once you have created your account, you will be able to access the Zoho Developer Console, where you can manage your apps and credentials.
## Step 2: Register a Zoho Developer App
The next step is to register a Zoho developer app, which will allow you to access the Zoho API and data. To do that, follow these steps:
- Go to the [Zoho Developer Console](https://api-console.zoho.com/) and click on the "Add App" button.
- Choose "Server-Based" as the Client Type and click on "Create".
- Enter a name and a homepage for your app
- For your Authorized redirect URI, enter in [`https://api.unified.to/oauth/code`](https://api.unified.to/oauth/code) or `https://api-eu.unified.to/oauth/code`
- Click on "Create".
- You will see a confirmation message and a client ID and a client secret for your app. Copy and save them somewhere safe, as you will need them later.
Congratulations! You have successfully registered a Zoho developer app and got credentials. You can now use them to access the Zoho API and data from your SaaS software.
## Step 3: Activate your Zoho Apps in [Unified.to](https://unified.to/)
1. Navigate to [Active Integrations](https://app.unified.to/integrations)
2. Search for ZohoCRM, ZohoBooks, ZohoPeople, or ZohoRecruit
3. Click on the integration
4. Enter in the information from the previous step (client ID, secret)
5. Click on Activate
## How to Register an ADP Developer App and Get OAUTH2 Credentials
URL: https://docs.unified.to/guides/how_to_register_an_adp_developer_app_and_get_oauth2_credentials
# How to Register an ADP Developer App and Get OAUTH2 Credentials
------
_April 30, 2023_
Developers who are looking to build data integrations for their SaaS software will need to register an ADP Developer application and obtain OAUTH2 credentials. This will allow you to access ADP data and build integrations with ADP services. In this article, we'll go over the steps to register an ADP Developer application and obtain OAUTH2 credentials.
## **Step 1: Create an ADP Developer Account**
To get started, you will need to create an ADP Developer account. If you don't already have one, you can sign up for free on the [ADP Developer Portal](https://developers.adp.com/).
## **Step 2: Register a Developer App**
Once you have an ADP Developer account, you can register a new developer app. Here are the steps to do so:
1. Log in to your ADP Developer account.
2. Navigate to the "Apps" section of the Developer Portal.
3. Click on the "Register App" button.
4. Fill in the required information for your app, such as its name, description, and callback URL.
5. Agree to the ADP Developer Terms of Service and Privacy Policy.
6. Click the "Submit" button to complete the app registration process.
## **Step 3: Get Your App Credentials**
Once your app is registered, you can obtain your app credentials, including the Client ID and Client Secret, which are used to authenticate your app and access the ADP APIs.
1. Navigate to the "Apps" section of the Developer Portal.
2. Click on the name of the app you just registered.
3. Click on the "Credentials" tab to view your app's Client ID and Client Secret.
4. Make sure that your redirect_uri is [`https://api.unified.to/oauth/code`](https://api.unified.to/oauth/code) if you are going to use [Unified.to](https://unified.to/) with these credentials
## Step 4: Using Unified.to
Registering an ADP Developer application and obtaining OAUTH2 credentials is an important step in building data integration for your SaaS software. With these credentials, you'll be able to access ADP data and build integrations with ADP services.
Using ADP APIs directly can be complex and time-consuming, especially if you want to integrate with multiple services or platforms. You will need to handle the authentication, the error handling, the rate limiting, the data mapping, and the synchronization logic for each integration.
It's much easier to use Unified.to APIs for building scalable integrations. With these APIs, you can focus on building your SaaS software and let Unified.to handle the heavy lifting of integrating with ADP. Check out the Unified.to website for more information on how they can help you build better integrations.
## Step 5: Activate ADP in Unified.to
1. Navigate to [Active Integrations](https://app.unified.to/integrations)
2. Search for ADP
3. Click on Activate on ADP
4. Enter in the information from the previous step (client ID, secret)
## How to Register an Atlassian Developer App and Get OAUTH2 Credentials
URL: https://docs.unified.to/guides/how_to_register_an_atlassian_developer_app_and_get_oauth2_credentials
# How to Register an Atlassian Developer App and Get OAUTH2 Credentials
------
_February 12, 2023_
Developers who are looking to build data integrations for their SaaS software with Atlassian services will need to register an Atlassian Developer application and obtain OAUTH2 credentials. This will allow you to access Atlassian data and build integrations with Atlassian services. In this article, we'll go over the steps to register an Atlassian Developer application and obtain OAUTH2 credentials.
## Step 1: **Registering an Atlassian Developer App**
To register an Atlassian Developer app, you need to follow the steps below:
1. Go to the [**Atlassian Developer site**](https://developer.atlassian.com/console/myapps/).
2. Click on "Get started" and create an Atlassian account if you don't have one already.
3. Navigate to the "My Atlassian" page and click on "Create a new app."
4. Fill out the required information for your app, including the app name, description, and contact information.
5. Once you have completed the form, click on "Create app."
6. You will be redirected to the app's overview page, where you can see the app key and client ID.
## Step 2: **Obtaining Credentials**
To obtain the necessary credentials for your salesforce integration, you need to do the following:
1. Go to the app's overview page and click on "Add credentials."
2. Select "OAuth" as the authentication method.
3. Fill out the necessary information, including the callback URL and OAuth scopes. If you are going to use Unified.to, then this needs to be `https://api.unified.to/oauth/code`
4. Once you have completed the form, click on "Create credentials."
5. You will be redirected to the credentials page, where you can see the client secret.
## Step 3: Using Unified.to
Registering an Atlassian Developer application and obtaining OAUTH2 credentials is an important step in building data integration for your SaaS software. With these credentials, you'll be able to access ADP data and build integrations with Atlassian services.
Using Atlassian APIs directly can be complex and time-consuming, especially if you want to integrate with multiple services or platforms. You will need to handle the authentication, the error handling, the rate limiting, the data mapping, and the synchronization logic for each integration.
It's much easier to use Unified.to APIs for building scalable integrations. With these APIs, you can focus on building your SaaS software and let Unified.to handle the heavy lifting of integrating with ADP. Check out the Unified.to website for more information on how they can help you build better integrations.
## Step 4: Activate Atlassian in Unified.to
1. Navigate to [Active Integrations](https://app.unified.to/integrations)
2. Search for Atlassian
3. Click on Activate on Atlassian
4. Enter in the information from the previous step (client ID, secret)
## How to Register your Google Ads OAuth2 Application
URL: https://docs.unified.to/guides/how_to_register_your_google_ads_oauth2_application
# How to Register your Google Ads OAuth2 Application
------
_March 21, 2026_
## **How to Register your Google Ads OAuth2 Application**
1. Create a Google Cloud project
- Go to [**https://console.cloud.google.com/**](https://console.cloud.google.com/)
- Click **Select a project** at the top of the page, then click **New Project**
- Enter the following details:
- Project Name
- Organization (if applicable)
- Click **Create**
2. Enable the Google Ads API
- Go to [**https://console.cloud.google.com/apis/library**](https://console.cloud.google.com/apis/library)
- Search for **Google Ads API**
- Click on **Google Ads API** in the results
- Click **Enable**
3. Configure the OAuth Consent Screen
- Go to [**https://console.cloud.google.com/apis/credentials/consent**](https://console.cloud.google.com/apis/credentials/consent)
- Select **External** as the user type, then click **Create**
- Enter the following details:
- App Name
- User Support Email
- Developer Contact Email
- Click **Save and Continue**
- On the Scopes step, click **Add or Remove Scopes** and add the following scope:
- `https://www.googleapis.com/auth/adwords`
- Click **Save and Continue**
- On the Test Users step, add any Google accounts you want to test with
- Click **Save and Continue**
4. Publish the OAuth Consent Screen
- Go back to [**https://console.cloud.google.com/apis/credentials/consent**](https://console.cloud.google.com/apis/credentials/consent)
- Click **Publish App** to move from Testing to Production status
- This allows any Google user to authorize your application, not just the test users you added
5. Create OAuth2 Credentials
- Go to [**https://console.cloud.google.com/apis/credentials**](https://console.cloud.google.com/apis/credentials)
- Click **Create Credentials** → **OAuth client ID**
- Select **Web application** as the Application Type
- Enter a name for the OAuth client
- Under **Authorized redirect URIs**, add the following **Unified.to** OAuth2 redirect URL:
- [**https://api.unified.to/oauth/code**](https://api.unified.to/oauth/code)
- Click **Create**
- Copy the **Client ID** and **Client Secret** from the dialog that appears
6. Request Basic Access for the Google Ads Developer Token
- Go to [**https://ads.google.com/aw/apicenter**](https://ads.google.com/aw/apicenter)
- Note: You must be logged into a Google Ads Manager account. If you do not have one, create one at [**https://ads.google.com/intl/en/home/tools/manager-accounts/**](https://ads.google.com/intl/en/home/tools/manager-accounts/)
- In the API Center, you will see your **Developer Token**
- Your token will initially have **Test Account** access. To use it with production accounts, click **Apply for Basic Access**
- Fill out the application form with details about how you will use the API
- Google typically reviews and approves Basic Access applications within a few business days
7. Configure Unified.to
- In Unified [**https://app.unified.to/integrations/googleads**](https://app.unified.to/integrations/googleads), add the following:
- The OAuth client's **Client ID**
- The OAuth client's **Client Secret**
- Your **Developer Token** from the API Center
You are now all set to authorize a Google Ads connection
## How to Register your MetaAds OAuth2 application
URL: https://docs.unified.to/guides/how_to_register_your_metaads_oauth2_application
# How to Register your MetaAds OAuth2 application
------
_March 4, 2026_
1. Create a Meta Developer account
- Go to [https://developers.facebook.com/](https://developers.facebook.com/)
2. Create an application
- Go to [https://developers.facebook.com/apps/creation/](https://developers.facebook.com/apps/creation/)

- Enter the following details:
- **App Name**
- **App Contact Email**


- Add the following use-cases; 'Manage ads using Marketing API' and 'Measure ad performance'

**3. Configure Permissions for the Marketing API**
- Enable all permissions in the 'Create & Manage Ads' and 'Measure ad performance' use-cases

- or -
- After the application is created:
- Open the **App Dashboard**.
- Navigate to:
Use Cases → Customize → Create & Manage Ads

- Enable the following permissions:
- ads_management
- ads_read
- business_management
- email
- Additional permissions such as:
- pages_manage_ads
- pages_read_engagement
This may also be enabled depending on your integration requirements.

**4. Configure Facebook Login for Business**
- In the left sidebar, open (1, 2):
Facebook Login for Business → Settings

- Specify the [Unified.to](https://unified.to/) OAuth2 redirect URLs (3)

- Click **Save Changes (4)**.
**5. Configure App Domains**
- Go to App Settings → Basic
- Add the following **App Domains**(3):
[unified.to](https://unified.to/)
[api.unified.to](https://api.unified.to/)
[app.unified.to](https://app.unified.to/)
[api-eu.unified.to](https://api-eu.unified.to/)
[app-eu.unified.to](https://app-eu.unified.to/)
[api-au.unified.to](https://api-au.unified.to/)
[app-au.unified.to](https://app-au.unified.to/)
- Scroll down and save the settings (4).

In Unified [https://app.unified.to/integrations/metaads](https://app.unified.to/integrations/metaads), add the application's App ID (as the client ID) and the App secret (as the client secret). This information can be found in the App settings > Basic page.
You are now all set to authorize a MetaAds connection
## How To Request & Write Raw Integration Data
URL: https://docs.unified.to/guides/how_to_request_and_write_raw_integration_data
# How To Request & Write Raw Integration Data
------
_February 6, 2024_
### Reading data
While [Unified.to](https://unified.to/) abstracts all of the integration's objects into common objects, you can still request that the original object.
Simply include a `fields=raw` parameter in your request.
```javascript
GET /crm/123456789/contacts?fields=raw
```
The result will also include a field named `raw` that will contain the original vendor's object. You can add more field names in there as well (comma-delimited) and only those fields will be returned.
### Writing data
Simply include a `raw` field in the payload with the integration's specific fields.
```javascript
POST /crm/123456789/contacts
{
"name": "Joe Smith",
"raw": {
"customfield1":"Blue",
"customfield2":"Bananas"
}
}
```
Those fields in the `raw` object can also overwrite any mapped unified fields.
### Passing Raw Parameters
Some providers support extra filters or options that Unified doesn't standardize.
You can pass them using the `raw` query parameter.
Take normal query params like:
```plain text
status=active&limit=10
```
Encode them into:
```plain text
GET /crm/123456789/contact?raw=status%3Dactive%26limit%3D10
```
## How to Retrieve Microsoft Dynamics 365 Business Central Credentials
URL: https://docs.unified.to/guides/how_to_retrieve_microsoft_dynamics_365_business_central_credentials
# How to Retrieve Microsoft Dynamics 365 Business Central Credentials
------
_January 21, 2026_
This guide walks you through creating an Azure App Registration, granting the correct permissions, and collecting the credentials required to integrate with **Microsoft Dynamics 365 Business Central** using Unified.
---
## 1. Create or Select an App Registration

1. Go to the Azure Portal:
👉 [https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)
2. Navigate to **App registrations**
3. Either:
- Select an existing app, **or**
- Click **New registration** to create a new one
---
## 2. Configure Redirect URI
1. Inside your App Registration, go to **Authentication**
2. Under **Redirect URI (optional)**:
- Select **Web**
- Enter the redirect URL provided by Unified
```plain text
https://api.unified.to/oauth/code
```
3. Click **Save**
---
## 3. Grant Business Central API Permissions

1. In the App Registration, go to **API permissions**
2. Click **Add a permission**
3. Select **APIs my organization uses**
4. Search for and select **Microsoft Dynamics 365 Business Central**
5. Choose **Delegated permissions**
6. Select the required permissions (typically default access)
7. Click **Add permissions**
8. If required, click **Grant admin consent**
---
## 4. Get Client ID and Client Secret

1. In the App Registration, go to **Certificates & secrets**
2. Copy the following:
- **Client ID** (from the app Overview page)
- **Client Secret**
- Create a new client secret if one does not exist
- Copy it immediately (it will not be shown again)
⚠️ Store the client secret securely.
---
## 5. Retrieve Business Central Company Name

Unified also requires the **Business Central company name**.
1. Go to:
👉 [https://businesscentral.dynamics.com/](https://businesscentral.dynamics.com/)
2. Sign in to your Business Central account
3. Locate the **company name** using either method:
- The company name shown in the **top-left corner**, **or**
- Click the **building (company) icon** in the navigation bar and copy the desired company name
Use **exactly** the company name as shown.
---
## 6. Information You Will Need for Unified
After completing the steps above, you should have:
- ✅ Client ID
- ✅ Client Secret
- ✅ Business Central Company Name
These values are required to complete the Business Central connection in Unified.
## How to set up a custom API URL
URL: https://docs.unified.to/guides/how_to_set_up_a_custom_api_url
# How to set up a custom API URL
------
_September 5, 2025_
This guide will walk you through the process of setting up a custom CNAME (subdomain) for your Unified API workspace. This allows you to use your own domain instead of the default `api.unified.to` domain for all API calls and OAuth flows.
> 💡 This is mandatory when using Sensitive scopes in any of the Google integrations, doing this will allow the Google verification to pass.
## Prerequisites
- You must have a paid Unified plan (custom domains are not available on the free/test plan)
- Access to your domain's DNS management console
- Admin access to your Unified workspace
## Step 1: Choose Your Custom Subdomain
Decide on a subdomain you want to use. For example:
- `api.yourcompany.com`
- `unified-api.yourcompany.com`
- `integrations.yourcompany.com`
## Step 2: Configure DNS CNAME Record
1. **Log into your domain registrar or DNS provider** (e.g., Cloudflare, GoDaddy, Namecheap, AWS Route 53)
2. **Navigate to DNS management** for your domain
3. **Add a new CNAME record** with the following settings:
- **Type**: CNAME
- **Name**: Your chosen subdomain (e.g., `api` or `unified-api`)
- **Value/Target**:
US: [unified-domains-us-7d67c6f04efebb36.elb.us-east-1.amazonaws.com](https://unified-domains-us-7d67c6f04efebb36.elb.us-east-1.amazonaws.com/)
EU: [unified-domains-eu-a0f8a17b303a3c06.elb.eu-central-1.amazonaws.com](https://unified-domains-eu-a0f8a17b303a3c06.elb.eu-central-1.amazonaws.com/)
- **TTL**: 3600 (or default)
- **Proxy status**: Disabled (if using Cloudflare)
4. **Save the CNAME record**
## Step 3: Wait for DNS Propagation
DNS changes can take anywhere from a few minutes to 48 hours to propagate globally. You can check propagation using tools like:
- [whatsmydns.net](https://whatsmydns.net/)
- [dnschecker.org](https://dnschecker.org/)
## Step 4: Contact Unified Support
Once your CNAME is configured and propagated:
1. **Contact Unified Support** through your preferred channel
2. **Provide the following information**:
- Your workspace name/ID
- The custom subdomain you want to use
- Confirmation that the CNAME is configured and pointing to [unified-domains-us-7d67c6f04efebb36.elb.us-east-1.amazonaws.com](https://unified-domains-us-7d67c6f04efebb36.elb.us-east-1.amazonaws.com/)
3. **Support will assign the custom domain** to your workspace
## Step 5: Update OAuth Application Settings
**CRITICAL**: After your custom domain is assigned, you must update all OAuth applications to use the new redirect URL.
### For OAuth Applications:
1. Go to yor app Console
2. Update the **Authorized redirect URIs** from:
```plain text
https://api.unified.to/oauth/code
```
to:
```plain text
https://your-custom-domain.com/oauth/code
```
## Step 6: Verify the Setup
1. **Test your custom domain** by making a simple API call to:
```plain text
https://your-custom-domain.com/swagger.json
```
2. **Test OAuth flow** by attempting to authenticate with an integration using your custom domain
3. **Verify redirect URLs** are working correctly in your OAuth applications
## Important Notes
### OAuth Verification Requirements
- **Google OAuth**: After changing the redirect URL, you may need to complete app verification since Google sees this as a new domain
- **Other providers**: May require similar verification processes
- **App verification**: Follow the provider's specific verification process (e.g., [Google's verification guide](https://support.google.com/cloud/answer/13461325))
### API Usage
- All API calls will now use your custom domain
- The API functionality remains identical
- Your API keys and authentication methods remain the same
### Security Considerations
- Ensure your DNS provider has proper security measures
- Consider enabling DNSSEC if available
- Monitor for any unauthorized DNS changes
## Troubleshooting
### Common Issues
1. **DNS Not Propagated**
- Wait longer for propagation
- Check with multiple DNS lookup tools
- Verify CNAME record is correct
2. **OAuth Errors After Domain Change**
- Ensure redirect URLs are updated in all OAuth applications
- Complete app verification if required by the provider
- Check that the custom domain is properly assigned to your workspace
3. **API Calls Failing**
- Verify the CNAME is pointing to `api.unified.to`
- Check that the custom domain is assigned to your workspace
- Ensure your API keys are still valid
## Example Configuration
Here's a complete example for setting up `api.yourcompany.com`:
### DNS Configuration:
```plain text
Type: CNAME
Name: api
Value: api.unified.to
TTL: 3600
```
### OAuth Redirect URL:
```plain text
https://api.yourcompany.com/oauth/code
```
### API Endpoint:
```plain text
https://api.yourcompany.com/v1/your-endpoint
```
---
**Note**: Custom domain setup requires coordination with Unified Support to ensure proper configuration and security. Please allow 1-2 business days for the setup to be completed after your CNAME is configured.
## How to Set Up a Microsoft Teams Bot with Unified
URL: https://docs.unified.to/guides/how_to_set_up_a_microsoft_teams_bot_with_unified
# How to Set Up a Microsoft Teams Bot with Unified
------
_April 20, 2026_
## Overview
If your Microsoft Teams bot connection is created successfully in Unified but later API calls fail with
`No authorization information present on the request`, the issue is usually not OAuth.
OAuth connects Microsoft 365 to Unified, but your bot still needs to be:
- packaged correctly
- uploaded to Teams
- installed in the correct Team, channel, or chat
Without this, API calls will fail due to missing context.
---
## ⚠️ Important Clarification (Common Issue)
- The manifest downloaded from **Microsoft Entra app registration is NOT a valid Teams manifest**
- The Microsoft 365 schema is also **not the correct reference**
You must use the **Microsoft Teams manifest schema**:
[https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
Using the wrong manifest will result in errors like:
`Manifest parsing error message unavailable`
---
## What You'll Need
Before starting:
- Guide: [https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365](https://docs.unified.to/guides/how_to_get_your_oauth_2_credentials_for_microsoft_dynamics_365)
- A Microsoft Entra app / bot registration
- A Microsoft Teams app package (`.zip`)
- Permission to upload custom apps in Teams
- A Unified connection using **Microsoft Teams (bot)**
---
## Create a Valid Teams App Package
Your `.zip` must include:
- `manifest.json`
- Color icon (192×192 PNG)
- Outline icon (32×32 PNG)
---
### Recommended Approach (Avoid Manual Errors)
Instead of writing JSON manually, use:
👉 Teams Developer Portal
[https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/teams-developer-portal](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/teams-developer-portal)
This allows you to:
- Configure your bot via UI
- Set scopes correctly
- Generate a valid app package
- Avoid manifest parsing errors
---
### Manifest Checklist
Ensure your `manifest.json` includes:
- Valid app ID
- Bot capability with correct `botId`
- Required scopes:
- `team`
- `groupchat`
- `personal` (optional)
Use:
- `team` → for channels
- `groupchat` → for chats
---
### Minimal Manifest Template (Reference)
```plain text
{
"$schema":"https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion":"1.16",
"version":"1.0.0",
"id":"YOUR-APP-ID",
"name": {
"short":"My Teams Bot",
"full":"My Teams Bot"
},
"description": {
"short":"Bot for Teams",
"full":"Bot for Teams integrated with Unified"
},
"developer": {
"name":"Your Company",
"websiteUrl":"https://yourdomain.com",
"privacyUrl":"https://yourdomain.com/privacy",
"termsOfUseUrl":"https://yourdomain.com/terms"
},
"icons": {
"color":"color.png",
"outline":"outline.png"
},
"accentColor":"#FFFFFF",
"bots": [
{
"botId":"YOUR-ENTRA-APP-ID",
"scopes": ["team","groupchat"]
}
]
}
```
---
### ⚠️ Notes (Follow as Steps)
1. Replace `YOUR-APP-ID` and `YOUR-ENTRA-APP-ID` with your actual app ID
2. Ensure both icons exist in the `.zip` file
3. Use this only as a reference template, not a full production manifest
---
## Upload the App in Microsoft Teams
Steps:
1. Open **Apps** (left sidebar)
2. Click **Manage your apps**
3. Stay on **Apps tab**
4. Click **Upload an app**
5. Upload your `.zip`

⚠️ If upload option is missing → admin restriction
---
## Install the Bot in the Right Scope
Uploading ≠ installing
You must install the bot in:
- A Team
- A channel
- A group chat
- Personal scope

⚠️ If not installed → API calls will fail
---
## Connect Microsoft Teams (bot) to Unified
Steps:
1. Create connection in Unified
2. Complete OAuth
3. Approve scopes
4. Save connection
### Common Required Scopes
- `Channel.ReadBasic.All`
- `ChannelMessage.Read.All`
- `ChatMessage.Read`
- `Chat.Read`
- `ChannelMessage.Send`
- `ChatMessage.Send`
- `offline_access`
💡 If permissions change → reconnect integration
---
## Test the Connection
1. Send a message where the bot is installed
2. Call Unified messaging API
3. Verify read/send works
💡 If connection was created before installation → retry
---
## Troubleshooting
### ❌ Manifest parsing error
- Wrong manifest type (Entra instead of Teams)
- Wrong schema
---
### ❌ App package failed validation
- Missing icons
- Files not at root
- Invalid JSON
- `botId` mismatch
---
### ❌ No authorization information present
- Bot not installed in correct scope
- Missing permissions
- Wrong scope
- Token outdated → reconnect
## How to set up and configure Notion
URL: https://docs.unified.to/guides/how_to_set_up_and_configure_notion
# How to set up and configure Notion
------
_May 21, 2024_
This guide describes how to register a public Notion application (a.k.a. ‘integration') and configure it so that you can use the Unified API to query pages and databases.
## Understanding Notion integrations: Internal vs Public
Before setting up your Notion integration, it's important to understand the two types of integrations Notion offers:
1. **Internal Integrations**: These are confined to a single workspace and are only accessible by members of that workspace. They're ideal for custom workspace enhancements.
2. **Public Integrations**: These are designed for a wider audience and can be used across any Notion workspace. They follow the OAuth 2.0 protocol for workspace access.
The main difference in terms of accessing user data is:
- For Internal Integrations, workspace members explicitly grant access to their pages or databases via Notion's UI.
- For Public Integrations, users authorize access to their pages during the OAuth flow, or by sharing pages directly with the integration.
For more information on the differences, refer to the [Notion API Overview](https://developers.notion.com/docs/getting-started).
## Setting up your Notion integration
The following steps outline how to register a public Notion integration:
1. Go to [https://www.notion.so/my-integrations](https://www.notion.so/my-integrations)
2. Click on your integration (or create a new one)
3. Follow the steps to register a new Notion integration.
1. Under **Type**, select **Public**.
2. Under **Redirect URIs**, enter `https://api.unified.to/oauth/code`
4. Click **Save**.
5. Go to the configuration settings of your newly created integration and make a note of the **OAuth Client ID** and **OAuth Client Secret.**
6. Still under the configuration settings, make sure to select the permissions you need for your app under **Capabilities** e.g. read or update content.
7. Enter your OAuth credentials on [https://app.unified.to/integrations/notion](https://app.unified.to/integrations/notion) to activate the Notion integration.
**Note:** If you wish to create an Internal integration, most of the steps above can be skipped, and you will need to enter your `Internal integration token` on [app.unified.to](https://app.unified.to/) instead. Instructions on how to find your internal token can be found [here](https://www.notion.so/help/create-integrations-with-the-notion-api).
## Querying databases in Notion
To query databases in Notion, you'll need to use our Unified KMS endpoint:
1. First, make sure you have a `connection_id` for your Notion integration.
2. Since database entries in Notion are technically pages, we'll use the **List all pages** endpoint:
```plain text
GET /kms/{connection_id}/page
```
API reference: [List all pages](https://docs.unified.to/kms/page/List_all_pages)
1. When querying pages in a database, you need to pass the database ID as the `space_id` parameter. For example:
```plain text
GET /kms/{connection_id}/page?space_id={database_id}
```
2. You can use additional parameters to filter and sort your results:
- `limit`: Number of results to return (default: 30)
- `offset`: Number of results to skip (default: 0)
- `updated_gte`: Return only results updated on or after this date
- `sort`: Sort by 'name', 'updated_at', or 'created_at'
- `order`: Sort order ('asc' or 'desc')
- `query`: Search query string
- `fields`: Comma-separated list of fields to return
For example, to get the first 50 pages in a database, sorted by name in ascending order:
```plain text
GET /kms/{connection_id}/page?space_id={database_id}&limit=50&sort=name&order=asc
```
## Webhooks
Notion doesn't offer programmatic creation of webhooks, so you need to first create webhooks for your application. Then, you can use our CreateWebhook API endpoint to select what data and connection you want to receive.
1. Go you your Notion's integrations page at [https://www.notion.so/profile/integrations](https://www.notion.so/profile/integrations)
2. Either create a new integration or select an existing one.
3. Navigate to the **Webhooks** tab and click **+ Create a subscription**.

4. Enter your public **Webhook URL** — this is the public endpoint where you want Notion to send events. It must be a secure (SSL) and publicly available endpoint. Endpoints in localhost are not
reachable.
1. US: `https://api.unified.to/webhook/workspace/notion?workspace_id=$WORKSPACE_ID`
2. EU: `https://api-eu.unified.to/webhook/workspace/notion?workspace_id=$WORKSPACE_ID`

5. Select the events that you are interested in receiving from all of your customers. Created and Deleted events are associated with our `created` and `deleted` events, while all other events are associated with our `updated` event. Both Notion's `Page` and `Database` are mapped to Unified's `KMSPage`, while `Comments` are mapped to `KMSComment`. You can modify these later if needed.
6. Click **Create subscription**.

7. After you create the notion webhook, notion will send us a verification token. You can find that verification token in [https://app.unified.to/settings/api](https://app.unified.to/settings/api). Paste the `verification_token` value into the Verify Subscription form in Notion, and click **Verify subscription.**

Once completed, you can create webhooks in Unified.to that will receive these Notion events in real-time.
## How to Set Up LinkedIn Webhooks with Unified
URL: https://docs.unified.to/guides/how_to_set_up_linkedin_webhooks_with_unified
# How to Set Up LinkedIn Webhooks with Unified
------
_February 14, 2026_
LinkedIn's Organization Social Action Notifications let you receive real-time updates when people interact with your company page -- likes, comments, shares, and mentions. With Unified, you can subscribe to these events through the same webhook API you use for every other integration, so there's no need to learn LinkedIn's proprietary webhook protocol.
## **What You'll Need**
Before starting, review the [Getting Started with Unified](https://docs.unified.to/guides/getting_started_with_unified) article if you haven't already. You'll also need:
- A LinkedIn developer app (see setup instructions below)
- A LinkedIn connection created through Unified's embedded auth flow
- A publicly accessible URL to receive webhook events
## **Setting Up Your LinkedIn Developer App**
Before you can receive webhook events, you need a LinkedIn app configured with the right products and permissions.
**Creating the App**
1. Go to [linkedin.com/developers/apps](https://www.linkedin.com/developers/apps)and click **Create App**
2. Fill in the required fields:
- **App name**: Your application's name
- **LinkedIn Page**: Select the LinkedIn company page you want to monitor (you must be an admin of the page)
- **App logo**: Upload a logo for your app
3. Accept the legal terms and click **Create app**
**Requesting API Products**
LinkedIn gates webhook access behind specific API products. From your app's settings page:
1. Navigate to the **Products** tab
2. Request access to **Community Management API** -- this grants the `r_organization_social` scope needed for social action notifications
3. If you also want to send messages, request **LinkedIn Marketing API Partner Program** for the `w_messages` scope
Product approvals can take a few business days. You can check the status on the **Products** tab.
**Configuring OAuth Scopes**
Once your products are approved:
1. Go to the **Auth** tab in your app settings
2. Under **OAuth 2.0 scopes**, verify that `r_organization_social` is listed
3. Copy your **Client ID** and **Client Secret** -- you'll need these when creating a connection in Unified
4. Under **Authorized redirect URLs**, add your Unified OAuth callback URL
**Verifying Your Company Page**
LinkedIn requires that your developer app is associated with a verified company page:
1. Go to the **Settings** tab in your app
2. Under **App Settings**, confirm the correct LinkedIn Page is linked
3. A page admin must verify the app -- click **Generate URL** next to the verification prompt and share it with a page admin if needed
Once the page is verified and the products are approved, your app is ready to receive social action notifications.
## **Creating a Webhook for LinkedIn Events**
You can create a webhook subscription through the API or the Unified dashboard. The webhook will listen for `messaging_event` objects with the `created` event, which is how LinkedIn's social action notifications are surfaced.
**Via the API**
`import { UnifiedTo } from '@unified-api/typescript-sdk';
const sdk = new UnifiedTo({
security: {
jwt: process.env.UNIFIED_API_KEY!,
},
});
const webhook = await sdk.unified.createUnifiedWebhook({
webhook: {
hookUrl: 'https://your-app.com/webhooks/linkedin',
connectionId: process.env.LINKEDIN_CONNECTION_ID!,
objectType: 'messaging_event',
event: 'created',
},
});
console.log('Webhook created:', webhook.webhook?.id);`
**Via the Dashboard**
1. Open [app.unified.to](https://app.unified.to/) and navigate to your workspace
2. Go to **Webhooks** and click **Create Webhook**
3. Select your LinkedIn connection
4. Set the object type to `messaging_event` and the event to `created`
5. Enter your webhook URL and save
Once the webhook is active, Unified registers your endpoint with LinkedIn's event subscription API using the organization URN from your connection.
## **Receiving Events**
When someone interacts with your LinkedIn company page, Unified converts the raw LinkedIn notification into a standard `MessagingEvent` object and POSTs it to your `hook_url`. Here's what that looks like:
`import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/linkedin', (req, res) => {
const event = req.body;
switch (event.type) {
case 'MESSAGE_RECEIVED':
// Someone commented on a company post, shared it, or edited/deleted a comment
console.log('Comment or share from:', event.user?.id);
console.log('On organization:', event.channel?.id);
break;
case 'REACTION_ADDED':
// Someone liked a company post
console.log('Like from:', event.user?.id);
break;
case 'APP_MENTION':
// Your company was mentioned in a share
console.log('Mentioned by:', event.user?.id);
break;
}
res.sendStatus(200);
});`
## **Event Type Mapping**
LinkedIn's social action types are mapped to Unified's standard `MessagingEvent`types:
- **COMMENT** and **ADMIN_COMMENT**become `MESSAGE_RECEIVED` -- a new comment was posted on your organization's content
- **LIKE** becomes `REACTION_ADDED` -- someone liked your organization's post
- **SHARE** becomes `MESSAGE_RECEIVED` -- someone shared your organization's content
- **SHARE_MENTION** becomes `APP_MENTION` -- your organization was mentioned in a share
- **COMMENT_EDIT** and **COMMENT_DELETE** become `MESSAGE_RECEIVED` -- an existing comment was modified or removed
Each event includes the `channel` field set to your organization's ID, the `user`field with the actor's person ID, and a `created_at` timestamp. The original LinkedIn payload is always available in the `raw` field if you need additional detail.
## **Sending Messages Alongside Webhooks**
While webhooks cover inbound events, you can also send outbound messages through LinkedIn's Messages API using the same connection. This is useful for responding to engagement by reaching out to the people interacting with your content.
`const message = await sdk.messaging.createMessagingMessage({
messagingMessage: {
message: 'Thanks for engaging with our content!',
destinationMembers: [
{ userId: 'urn:li:person:ABC123' },
],
},
connectionId: process.env.LINKEDIN_CONNECTION_ID!,
});`
Note that LinkedIn's Messages API requires partner-level access with the `w_messages` scope. Message creation supports new conversations (via `destinationMembers`) or replies to existing threads (via `channelId`).
## **Troubleshooting**
**Webhook not receiving events?** LinkedIn requires the `r_organization_social`OAuth scope, which is granted through the Community Management API product. Verify your app has this product approved on the [LinkedIn Developer Portal](https://www.linkedin.com/developers/apps), and that your connection has the scope by checking the connection details in the Unified dashboard.
**Events arriving for the wrong organization?** The webhook subscription is tied to the `organization_id` stored in your connection metadata, which is set during the initial OAuth flow. If you manage multiple LinkedIn organizations, create separate connections for each one.
**LinkedIn Page not verified?** Your developer app must be associated with a verified company page. Go to your app's **Settings** tab on the LinkedIn Developer Portal and complete the page verification process. A page admin must approve the association.
With just a few lines of code, you're now receiving real-time LinkedIn engagement data through the same webhook infrastructure you use for Slack, Discord, and every other messaging integration on Unified. No custom protocol handling required.
[→ Start your 30-day free trial](https://app.unified.to/login)
[→ Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)
## How to set up Slack webhooks using event subscriptions
URL: https://docs.unified.to/guides/how_to_set_up_slack_webhooks_using_event_subscriptions
# How to set up Slack webhooks using event subscriptions
------
_November 14, 2024_
This guide explains how to configure your Slack app's event subscriptions to work with Unified.to webhooks. This is a required step before you can create webhooks that listen for Slack events. Once configured, your webhooks will receive real-time notifications when specific events occur in your Slack workspace, such as when users send messages.
## Before you begin
Ensure you have:
- A Slack app created in your workspace
- Access to your Slack app's configuration settings
- Your [Unified.to](https://unified.to/) workspace ID
- An understanding of [how webhooks work in Unified.to](https://docs.unified.to/concepts/webhooks)
- Create a webhook in your
## Enable event subscriptions in Slack
1. Go to the Slack API dashboard at [https://api.slack.com/apps](https://api.slack.com/apps)
2. Select your app from the list of available apps
3. In the left sidebar, under **Features**, click **Event Subscriptions**
4. Toggle the **Enable Events** switch to **On**
5. In the **Request URL** field, enter (depending on your data center —- replace ID with your workspace ID —- replace `slack` with `slackbot` if using that integration):
1. (US data center) `https://api.unified.to/webhook/workspace/slack?workspace_id={ID}`
2. (EU data center) `https://api-eu.unified.to/webhook/workspace/slack?workspace_id={ID}`
6. Wait for Slack to verify the URL. You should see a green checkmark indicating successful verification.

7. Click **Save Changes** at the bottom of the page
## Subscribe to events from Slack
After you've enabled events and verified the request URL works, you need to subscribe to the events that you are interested in.
1. The minimum event you should subscribe to is`message.groups` (under user)
2. The other events you can subscribe to are the ones that fall under the following scopes under **Subscribe to events on behalf of users**:
1. `message.channels`
2. `message.groups`
3. `message.im`
4. `message.mpim`

_Example of Slack events that you can subscribe to for webhook events_
3. Click **Save Changes** at the bottom of the page
Once completed, you can create webhooks in Unified.to that will receive these Slack events in real-time.
Happy building!
## See also
- [How to create and configure webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
- [How to troubleshoot unhealthy webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks)
## How to set up Telegram (bot) with Unified.to
URL: https://docs.unified.to/guides/how_to_set_up_telegram_bot_with_unified
# How to set up Telegram (bot) with Unified.to
------
_June 30, 2026_
Telegram's **Bot API** lets you send and receive messages through a bot account you control. This guide walks you through creating a Telegram bot, connecting it to Unified.to with its bot token, and (optionally) turning on webhooks so you receive incoming messages and button clicks in real time.
**Estimated time:** 10 minutes
---
**Prerequisites**
- A Telegram account (any mobile or desktop client).
- A Unified.to account with a workspace. [Sign up here](https://unified.to/) if you don't have one.
- For receiving messages via webhooks: nothing extra — Unified.to hosts the HTTPS endpoint for you.
> **Bot API vs. personal account:** This integration uses the Telegram **Bot API**, so all messages are sent and received as your **bot**, not as your personal Telegram user. People interact with your bot by starting a chat with it or by adding it to a group/channel.
---
**Step 1 — Create a bot and get your bot token**
1. In any Telegram client, open a chat with [**@BotFather**](https://t.me/BotFather) (the official bot for creating bots).
2. Send the command **`/newbot`**.
3. When prompted, enter a **name** for your bot (the display name, e.g. _Acme Support_).
4. Then enter a **username** for your bot. It must end in `bot` (e.g. `acme_support_bot`).
5. BotFather replies with a confirmation that includes your **HTTP API token**. It looks like this:
```plain text
123456789:AAEhBP0...M_zv1u123ew11
```
6. **Copy this token** and keep it safe — it grants full control of your bot. You'll paste it into Unified.to in Step 3.
> Lost the token later? Send **`/token`** to BotFather and pick your bot, or **`/revoke`** to roll it.
---
**Step 2 — (Optional) Let your bot read group messages**
By default a bot in **privacy mode** only receives messages that mention it or reply to it. If you want your bot to receive **all** messages in a group:
1. Open [**@BotFather**](https://t.me/BotFather) and send **`/setprivacy`**.
2. Select your bot.
3. Choose **Disable**.
You can skip this step for one-to-one (direct) chats — bots always receive direct messages sent to them.
---
**Step 3 — Connect Telegram in Unified.to**
1. In the Unified.to dashboard, go to **Integrations** and search for **Telegram (bot)**.
2. Click **Connect** (or **Authorize**) to start a new connection.
3. When prompted for credentials, paste the value from Step 1 into the **Bot Token** field.
4. Submit. Unified.to validates the token by calling the Telegram `getMe` endpoint and, on success, creates the connection. The bot's name is shown on the connection.
You can also create the connection programmatically by storing the bot token as the connection's token credential — see the [Authorization API](https://docs.unified.to/unified/unified/authorization).
That's it for sending messages. To **receive** messages, continue to the webhook setup below.
---
**Step 4 — Find a chat ID to send to**
The Bot API can't list chats, so you need the **chat ID** of whoever you want to message. A chat ID is a number (e.g. `123456789` for a person, or a negative number like `-1001234567890` for a group/channel).
The easiest way to discover one:
1. From the recipient's Telegram account, **start a chat with your bot** (search its `@username` and press **Start**), or **add the bot to your group/channel**.
2. Have them send any message to the bot / group.
3. That inbound message arrives through your Unified.to webhook (Step 5) and includes the `channel_id` (the Telegram chat ID). Use that value as the destination for future messages.
**Sending a message**
Once you have a `channel_id`, send a message through the unified `messaging_message` endpoint:
```plain text
curl -X POST 'https://api.unified.to/messaging//message' \
-H 'Authorization: Bearer ' \
-H 'Content-Type: application/json' \
-d '{
"channels": [{ "id": "123456789" }],
"message": "Hello from Unified.to 👋"
}'
```
You can also include inline buttons (mapped to Telegram inline keyboards) and reply to a message via `parent_id`. The response contains the new message `id`, which is required to **edit** (`editMessageText`) or **delete** (`deleteMessage`) it later.
---
**Step 5 — Set up a webhook to receive messages**
Unified.to registers the Telegram webhook for you — you don't call `setWebhook` yourself. When you create a webhook subscription, Unified.to points your bot at a secure HTTPS endpoint and verifies every delivery with a secret token.
1. In the Unified.to dashboard, open your **Telegram (bot)** connection and go to **Webhooks** (or use the [Webhook API](https://docs.unified.to/unified/unified/createunifiedwebhook)).
2. Create a webhook and choose what you want to receive:
- **`messaging_message`**, event **`created`** — each new incoming message (and **`updated`** for edited messages).
- **`messaging_event`**, event **`created`** — incoming messages **and** inline-button clicks, delivered as unified events (`MESSAGE_RECEIVED`, `BUTTON_CLICK`).
3. Provide your destination URL — the place Unified.to should forward normalized events to.
4. Save. Unified.to calls Telegram's `setWebhook` behind the scenes and begins forwarding events.
Example webhook creation via the API:
```plain text
curl -X POST 'https://api.unified.to/unified/webhook' \
-H 'Authorization: Bearer ' \
-H 'Content-Type: application/json' \
-d '{
"connection_id": "",
"object_type": "messaging_message",
"event": "created",
"hook_url": "https://your-app.example.com/telegram/incoming"
}'
```
To stop receiving events, delete the webhook. Unified.to removes the bot's Telegram webhook automatically once the last subscription on the connection is gone.
---
**Notes & troubleshooting**
- **One webhook per bot.** Telegram allows a single webhook URL per bot, so all of a connection's webhook subscriptions share the same registration — that's expected and handled automatically.
- **HTTPS is required.** Telegram only delivers updates to HTTPS endpoints. Webhooks therefore work on Unified.to's hosted environment but not against a plain-HTTP localhost listener during local development.
- **No history.** The Bot API cannot list or fetch past messages or chats — a bot only sees messages sent **after** it joined a chat, delivered via webhooks. There is no "list messages" or "list channels" operation.
- **Not seeing group messages?** Make sure you disabled privacy mode (Step 2) and re-added the bot to the group if needed.
- **`getMe`** **/ connection fails?** Double-check the bot token was pasted exactly, with no extra spaces, and that the bot hasn't been revoked in BotFather.
---
**What you can do next**
- Send, edit, and delete messages (`sendMessage`, `editMessageText`, `deleteMessage`).
- Look up a single chat's details with `messaging_channel` (`getChat`).
- Receive incoming messages and inline-button clicks via `messaging_message` and `messaging_event` webhooks.
Need help? [Contact Unified.to support](https://unified.to/) or browse the [other guides](https://docs.unified.to/guides).
## How to set up your scopes in HubSpot
URL: https://docs.unified.to/guides/how_to_set_up_your_scopes_in_hubspot
# How to set up your scopes in HubSpot
------
_June 11, 2024_
This guide explains how to correctly configure the advanced scopes settings in your HubSpot developer app for seamless integration with Unified.to. We'll cover setting up both required and optional scopes, and address some common issues you might encounter.
## Before you begin
This guide assumes you have a basic understanding of:
- [Scopes](https://docs.unified.to/concepts/scopes)
- [How to create a connection (Project) ](https://docs.unified.to/guides/how_to_create_connection_with_hubspot)
- [How to get your HubSpot developer key and OAuth 2 credentials (Legacy App)](https://docs.unified.to/guides/how_to_get_your_hubspot_developer_key_and_oauth_2_credentials_legacy_apps)
## Set up required scopes in HubSpot (Project)
1. Go to [https://offers.hubspot.com/free-cms-developer-sandbox](https://offers.hubspot.com/free-cms-developer-sandbox), Sign in.
2. Development → Project → select your project
3. In your project(code) you will see following folder structure.
GOTO→ yourproject (account-testing) → app-hsmeta.json

1. Following will be the scopes and redirect URL. You can remove which you want. However, do remember those removed scopes before creating a connection in unified. Also remember to add redirect URL as follows.
```javascript
{
"uid": "get_started_app",
"type": "app",
"config": {
"description": "A Unified.to token app for testing",
"name": "My Get Started app",
"distribution": "private",
"auth": {
"type": "oauth",
"redirectUrls": [
"https://api.unified.to/oauth/code",
],
"requiredScopes": [
"oauth",
"crm.objects.companies.read",
"crm.objects.contacts.read",
"crm.objects.deals.read",
"crm.objects.owners.read",
"crm.pipelines.orders.read",
"crm.objects.contacts.write",
"crm.pipelines.orders.read",
"crm.pipelines.orders.write",
"crm.objects.deals.write",
"sales-email-read",
"content",
"tickets",
"crm.lists.read",
"settings.users.teams.read"
],
"optionalScopes": [],
"conditionallyRequiredScopes": []
},
"permittedUrls": {
"fetch": ["https://api.hubapi.com"],
"iframe": [],
"img": []
},
"support": {
"supportEmail": "support@example.com",
"documentationUrl": "https://example.com/docs",
"supportUrl": "https://example.com/support",
"supportPhone": "+18005555555"
}
}
}
```
1. You can update those changes using following command in terminal.
```javascript
hs project upload
```
1. When the code is uploaded successfully you can check that by going to following Development→ Project → Select your project.

1. To check your scopes click on the project Component. It will show your app-hsmeta.json

## Set up required scopes in HubSpot (Legacy App)
**Note**: If you are using webhooks, please refer to: [How to configure webhooks in HubSpot](https://docs.unified.to/guides/how_to_configure_webhooks_in_hubspot)
1. Go to [https://offers.hubspot.com/free-cms-developer-sandbox](https://offers.hubspot.com/free-cms-developer-sandbox), Sign in.
2. Development → Legacy App→ Your New App
3. In the same Auth tab, scroll
4. If you are not using webhooks, then [Unified.to](https://unified.to/) only requires two scopes to be set as **Required**: `crm.objects.owners.read` and `oauth`
1. Click **+ Add new scope**
2. Select `crm.objects.owners.read`
3. Set it as a **Required** scope
4. Save your changes

## Setup optional scopes
1. You can see the list of scopes required for the objects you need at [https://app.unified.to/integrations/hubspot?tab=oauth2](https://app.unified.to/integrations/hubspot?tab=oauth2)

1. Make sure to select optional

1. Add all the scopes you need
2. Save your changes
### Configure your scopes on [Unified.to](https://unified.to/)
Be sure to also configure these scopes in your [Unified.to](https://unified.to/) settings. For instructions on how to do that, refer to our [Scopes guide](https://docs.unified.to/concepts/scopes).
## Understanding HubSpot's scope constraints
HubSpot has some specific rules about how scopes work:
- If you specify a scope as **Required**, it must be included in every authorization request.
- **Note**: If you intend to _read_ data via a webhook, then the read scope for that object must be marked as **Required** in HubSpot.
- **Optional** scopes may or may not be included in the authorization.
- If you don't specify a scope at all, it cannot be included in the authorization request.
- The OAuth scope cannot be removed and must always be required.
Behind the scenes, [Unified.to](https://unified.to/) will request scopes according to what you have configured on the platform:
- We'll always request `crm.objects.owners.read` as a **Required** scope
- When you enable `webhook` scopes on [Unified.to](https://unified.to/), we convert every Hubspot scope ending with `.read` to required, assuming they'll all be used for webhooks.
- Otherwise, if you have not enabled the `webhook` scope, then all other scopes will be requested as **Optional**.
In general, we recommend that you only stick to requesting scopes for the data you actually need. For more instructions on setting up scopes to work with webhooks, please refer to: [How to configure webhooks in HubSpot](https://docs.unified.to/guides/how_to_configure_webhooks_in_hubspot)
## What your users will see
When your users sign in with HubSpot, they'll see a screen requesting access to the scopes you've specified:
- Required scopes will always be shown and cannot be deselected.
- Optional scopes will be shown and can be deselected by the user.

## Specifying Required vs Optional Scopes
Unified tries to automatically classify scopes as required or optional. If you need additional control, you can always specify this information in the `scopes` URL parameter.
Just prefix the Hubspot-specific scope with `required:` or `optional:` and we will handle them correctly.
eg.
`required:crm.objects.contacts.read`
## Troubleshooting
If your HubSpot scopes don't match the requested scopes via [Unified.to](https://unified.to/), your users may see an error message about there being a scopes mismatch.
Double-check that:
1. All scopes in your HubSpot app are included in every [Unified.to](https://unified.to/) request.
2. All scopes requested by [Unified.to](https://unified.to/) are specified (either as required or optional) in your HubSpot app.
3. If you're using webhooks, ensure all read scopes are set as **Required** in your HubSpot app.
4. If you're not using webhooks, ensure all scopes aside from `oauth` and [`crm.objects.owners.read`](https://crm.objects.owners.read/) are set as **Optional**.
## See also
- [Understanding scopes](https://docs.unified.to/concepts/scopes)
- [How to configure webhooks in HubSpot](https://docs.unified.to/guides/how_to_configure_webhooks_in_hubspot)
- [How to troubleshoot unhealthy connections](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections#how-to-troubleshoot-unhealthy-connections)
## How to setup a Freshbooks developer app
URL: https://docs.unified.to/guides/how_to_setup_a_freshbooks_developer_app
# How to setup a Freshbooks developer app
------
_October 3, 2025_
This guide will show you how to retrieve your OAuth 2 credentials on Freshbooks
## Create your Freshbooks App and get your Client ID and Secret
1. Go to the Freshbooks Developer Portal:
[https://my.freshbooks.com/#/developer](https://my.freshbooks.com/#/developer)
2. Click on `Create New App`

1. Fill in the Application Name, Application Type, these are mandatory
2. Add the scopes needed
1. You can see the scopes we use on [https://app.unified.to/integrations/freshbooks?tab=oauth2](https://app.unified.to/integrations/freshbooks?tab=oauth2)
3. Add [`https://api.unified.to/oauth/code`](https://api.unified.to/oauth/code) in the Redirect URIs

1. You can also fill in the non mandatory fields, logo, etc.
2. Click Save
3. Click on the App again

1. Your Client ID and Secret are at the bottom

## Activate the Freshbooks Integration in Unified
1. Go to [https://app.unified.to/integrations/freshbooks?tab=auth](https://app.unified.to/integrations/freshbooks?tab=auth)
2. Click in `Your OAuth 2 credentials`
3. Add your Client ID and Secret
4. Click Activate

## How to Setup AWS Assume Role for AWS Secret Manager
URL: https://docs.unified.to/guides/how_to_setup_aws_assume_role_for_aws_secret_manager
# How to Setup AWS Assume Role for AWS Secret Manager
------
_March 12, 2026_
This guide walks you through configuring AWS IAM Assume Role so that Unified can securely access AWS Secrets Manager in your account — without sharing long-lived AWS access keys.
## Overview
Instead of providing static AWS credentials (access key + secret), you can create an IAM role in your AWS account and grant Unified permission to assume it. Unified uses AWS STS (Security Token Service) to obtain short-lived, temporary credentials scoped to your Secrets Manager.
### How it works
```plain text
┌───────────────┐ STS AssumeRole ┌────────────────────┐
│ Unified API │ ──────────────────────────────► │ Your AWS Account │
│ (account │ (with External ID check) │ │
│ 944579081756│ ◄────────────────────────────── │ IAM Role │
└───────────────┘ temporary credentials │ Secrets Manager │
└────────────────────┘
```
1. You create an IAM role in your AWS account with a trust policy that allows Unified's AWS account to assume it.
2. You attach a permissions policy to that role granting access to Secrets Manager.
3. You provide the role's ARN and an External ID in the Unified dashboard.
4. Unified calls `sts:AssumeRole` with the External ID, receives temporary credentials, and uses them to read/write secrets.
## Prerequisites
- An AWS account with permissions to create IAM roles and policies
- Access to the Unified dashboard with workspace admin permissions
## Step 1: Create an IAM Policy for Secrets Manager
In the AWS Console, create a policy that grants the permissions Unified needs on Secrets Manager.
1. Go to **IAM** > **Policies** > **Create policy**
2. Select the **JSON** tab and paste the following:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:UpdateSecret",
"secretsmanager:DeleteSecret"
],
"Resource": "arn:aws:secretsmanager:YOUR_REGION:YOUR_ACCOUNT_ID:secret:*"
}
]
}
```
Replace `YOUR_REGION` (e.g. `us-east-1`) and `YOUR_ACCOUNT_ID` with your values.
**Tip:** To restrict access further, you can narrow the `Resource` to a specific prefix, for example:
`arn:aws:secretsmanager:us-east-1:123456789012:secret:unified/*`
3. Name the policy (e.g. `UnifiedSecretsManagerAccess`) and create it.
## Step 2: Create an IAM Role with a Trust Policy
1. Go to **IAM** > **Roles** > **Create role**
2. Select **Custom trust policy** and paste the following:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::944579081756:user/unified_assume_role_user"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "YOUR_EXTERNAL_ID"
}
}
}
]
}
```
Replace `YOUR_EXTERNAL_ID` with a unique, hard-to-guess string of your choice (e.g. a UUID). You will enter this same value in the Unified dashboard later.
**Why an External ID?** The External ID prevents the [confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html). It ensures that only requests originating through your Unified workspace — and not a third party who happens to know the role ARN — can assume the role.
3. Click **Next**, then attach the `UnifiedSecretsManagerAccess` policy you created in Step 1.
4. Name the role (e.g. `UnifiedSecretsManagerRole`) and create it.
5. Copy the role's **ARN** from the role summary page. It will look like:
`arn:aws:iam::123456789012:role/UnifiedSecretsManagerRole`
## Step 3: Configure Unified
1. Log in to the [Unified dashboard](https://app.unified.to/).
2. Navigate to **Settings** > **Workspace Settings**.
3. Under the secrets manager section, select **AWS Secret Manager**.
4. Fill in the following fields:
| Field | Value |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| **AWS Region** | The region where your Secrets Manager secrets are stored (e.g. `us-east-1`) |
| **AWS ARN** | The full ARN of the IAM role you created (e.g. `arn:aws:iam::123456789012:role/UnifiedSecretsManagerRole`) |
| **AWS External ID** | The same External ID string you used in the trust policy |
**Note:** When using Assume Role, you do **not** need to fill in the AWS Key and AWS Secret fields. Those fields are only required for the static-credentials approach.
5. Save your workspace settings.
## Step 4: Verify the Setup
Once saved, Unified will automatically use the Assume Role flow for all new connections in your workspace. To verify:
1. Create or update a connection in your workspace.
2. Check your AWS Secrets Manager console — you should see a new secret created with a name that includes your workspace ID.
3. If there are any issues, Unified will surface errors in the connection status.
## Troubleshooting
### 'Missing role ARN or region'
Ensure both the **AWS Region** and **AWS ARN** fields are filled in on your workspace settings.
### 'Access Denied' or 'Not authorized to perform sts:AssumeRole'
- Verify the **trust policy** on your IAM role references the correct Unified AWS principal:
`arn:aws:iam::944579081756:user/unified_assume_role_user`
- Verify the **External ID** in the trust policy matches exactly what you entered in Unified.
- Ensure the IAM role's permissions policy includes the required `secretsmanager:*` actions.
### 'The security token included in the request is expired'
Temporary credentials are cached for up to 55 minutes and refreshed automatically. If you see this error persistently, confirm that your IAM role allows a session duration of at least 1 hour (the default).
### Secrets are not being stored
- Confirm the IAM role's permissions policy allows actions on the correct region and account.
- Check that the `Resource` in your permissions policy matches the region configured in Unified.
## Migrating from Static Credentials
If you are currently using the static AWS Key / AWS Secret approach:
1. Follow Steps 1-3 above to set up the IAM role and configure Unified.
2. Once the ARN and External ID are saved, Unified will prefer the Assume Role flow over static credentials.
3. After verifying that secrets are being read and written correctly, you can remove the static AWS Key and AWS Secret from your workspace settings.
4. Revoke or delete the old IAM user credentials in your AWS account.
## Security Best Practices
- **Use a unique External ID per workspace.** This prevents cross-workspace role assumption.
- **Scope the permissions policy narrowly.** Restrict the `Resource` to only the secret prefixes Unified needs.
- **Rotate the External ID periodically.** Update both the IAM trust policy and the Unified dashboard when you do.
- **Enable CloudTrail logging.** Monitor `AssumeRole` events in your AWS account to audit access.
- **Do not share the External ID publicly.** Treat it as a sensitive configuration value.
## How to Setup Quickbooks Desktop
URL: https://docs.unified.to/guides/how_to_setup_quickbooks_desktop
# How to Setup Quickbooks Desktop
------
_April 2, 2026_
Quickbooks Desktop/Enterprise is a desktop application and not a web/online application, and thus needs additional setup to enable API access.
1. [Download QBWC](https://quickbooks.intuit.com/learn-support/en-ca/help-article/install-products/set-quickbooks-web-connector/L4Vp7VI44_CA_en_CA) (Web Connector) from Quickbooks and install the program on the same computer where your Quickbooks Desktop is running.
2. Activate Quickbooks Desktop Integration in your workspace

3. Create a Quickbooks Desktop connection within Unified (enter your username and password you will use in the QBWC)

4. Create a .qwc file, it should be in the following format:
```javascript
Unified
https://api.unified.to/webhook/workspace/quickbooksdesktop?workspace_id=your_workspace_id&connection_id=
your_quickbooks_desktop_connection_idA short description for WCWebService1
https://unified.ngrok.dev/support
your_username{57F3B9B1-86F1-4fcc-B1EE-566DE1813D21}{90A44FB5-33D9-4815-AC85-BC87A7E7D1EZ}QBFSnumber_of_minutes_between_each_sync_or_remove_this_if_you_want_to_sync_manually
```
5. Once the QBWC has installed, first open your Quickbooks Desktop with the company you wish to sync
6. Then open QBWC and click 'Add an application', QBWC will then prompt you to select a file, select the newly created .qwc file from above.

7. Click 'Ok' to grant access

8. Select 'Yes, whenever my Quickbooks company file is open' and 'Continue'

9. 'Confirm'

10. QBWC will normally close after this, you can reopen it and you should now see the app is added. Enter the password and to perform your first sync click 'Update Selected'.

## How to setup Sage 100 with Unified.to
URL: https://docs.unified.to/guides/how_to_setup_sage_100_with_unified
# How to setup Sage 100 with Unified.to
------
_July 17, 2026_
# Connecting Sage 100 — How to get your API credentials
This guide walks a Sage 100 administrator through everything needed to connect Sage 100 to Unified.
Sage 100 is on-prem software, so the connection is made through **eBusiness Web Services**, a module
that runs on your own Sage 100 server. When you finish, you will have the **four values** Unified
asks for:
1. **Web Services URL**
2. **Company Code**
3. **Web Services Username**
4. **Web Services Password**
**Who should do this:** a Sage 100 system administrator (or your Sage business partner). Several steps happen on the Windows server that hosts Sage 100 and in Internet Information Services (IIS), so server access is required.
---
## Before you start
- Sage 100 (Advanced or Premium; MAS 90/200 lineage) installed and running.
- Administrator access to the **Sage 100 server** and to **IIS** on that server.
- The **eBusiness Web Services** module — it ships with Sage 100 but is installed separately from
the Sage 100 installation program. If it is not installed yet, do that first (see Step 1).
- The server must be **reachable from Unified over HTTPS**. Sage 100 typically lives inside a private
network, so you will usually publish the endpoint through a reverse proxy, VPN, or secure tunnel
and give it a valid TLS certificate. Coordinate this with your IT/network team.
---
## Step 1 — Install eBusiness Web Services (if not already installed)
1. Run the **Sage 100 installation program** on the server.
2. Choose to install **eBusiness Web Services** and complete the wizard. It installs a web
application into IIS under a virtual directory named `ebusinesswebservices`.
3. After installation, open the **Sage 100 eBusiness Web Services** configuration utility on the
server (installed alongside the module). You will use it in later steps.
If eBusiness Web Services is already installed, skip to Step 2.
---
## Step 2 — Confirm the endpoint URL and that it is reachable
The service exposes a single SOAP endpoint:
```plain text
https:///ebusinesswebservices/masservice.svc
```
Verify it works by opening this URL in a browser **from a machine that can reach the server**:
```plain text
https:///ebusinesswebservices/masservice.svc?wsdl
```
You should see the WSDL (an XML document). If it loads, the endpoint is live.
- Replace `` with the public host name that Unified will use to reach the server.
- Make sure the host name **matches the TLS certificate** on the site, and that the port (usually
443) is open to Unified. If the WSDL does not load, see **Troubleshooting** below.
**This full URL is the "Web Services URL" value** you will enter in Unified. (Unified also accepts
just the base `https:///ebusinesswebservices` and will add `/masservice.svc` for you.)
---
## Step 3 — Enable the company for web services and note the Company Code
1. In Sage 100, open **Library Master → Main → Company Maintenance**.
2. Select the company you want to connect.
3. Ensure the company is **enabled for web services** (eBusiness Web Services must be turned on for
this company). If you are unsure where this setting is in your version, your Sage business
partner can confirm it — the eBusiness Web Services guide requires the company to be enabled or
every call returns _"Web services are not enabled for this company."_
4. Note the **Company Code** — the short code (for example `ABC`) that identifies this company.
**This is the "Company Code" value** you will enter in Unified.
---
## Step 4 — Create (or designate) a web-services user
Unified authenticates as a Sage 100 user on every call, so create a dedicated user for the
integration.
1. In Sage 100, open **Library Master → Main → User Maintenance**.
2. Create a new user (recommended: a dedicated integration account, e.g. `WEBSVC`) or choose an
existing one, and set a strong **password**.
3. Ensure the user is **enabled for web services**. If a user is not web-services-enabled, calls
fail with _"Web services are not enabled for the system."_ / an authorization error.
4. Make sure the account is not locked. A locked account returns
_"This user account has been locked."_
**The user name and password are the "Web Services Username" and "Web Services Password"** you will
enter in Unified.
### Give the user the right permissions (roles)
The web-services user must belong to a **role** (Library Master → Main → Role Maintenance) that grants
the tasks the integration uses. For the Customer and Sales Order features Unified supports, enable
these tasks on the role's **Tasks** tab:
| Feature (Unified) | Sage 100 task to allow |
| ------------------- | -------------------------------------------------------------- |
| Read customers | Accounts Receivable → Main → Customer Maintenance → **View** |
| Update customers | Accounts Receivable → Main → Customer Maintenance → **Modify** |
| Delete customers | Accounts Receivable → Main → Customer Maintenance → **Remove** |
| Read sales orders | Sales Order → Main → Sales Order Entry → **View** |
| Create sales orders | Sales Order → Main → Sales Order Entry → **Create** |
| Update sales orders | Sales Order → Main → Sales Order Entry → **Modify** |
| Delete sales orders | Sales Order → Main → Sales Order Entry → **Remove** |
Grant only what you need. If a task is not allowed, the matching Unified operation returns an
authorization error.
---
## Step 5 — (Optional) Expose extra fields (UDFs)
By default, User-Defined Fields (UDFs) and Development Partner fields are **not** returned. If you
need them, open the **Sage 100 eBusiness Web Services** configuration utility → **Advanced Settings**
→ **Additional Fields** tab and add the fields you want to expose. This step is optional and only
needed for custom fields.
---
## Step 6 — Enter the values into Unified
In the Sage 100 connection form in Unified, provide:
| Field | Value |
| ------------------------- | ----------------------------------------------------------- |
| **Web Services URL** | `https:///ebusinesswebservices/masservice.svc` |
| **Company Code** | e.g. `ABC` |
| **Web Services Username** | the user from Step 4 |
| **Web Services Password** | that user's password |
Save the connection. Unified will begin talking to your Sage 100 server directly over HTTPS.
---
## What Unified can and cannot do with Sage 100
eBusiness Web Services is a focused API. It is important to set expectations:
- **Supported:** looking up, updating, and removing **Customers**; and creating, reading, updating,
and removing **Sales Orders**.
- **Not available:** the API has **no "list everything" operation** — records are fetched one at a
time by their key (customer number, or sales order number). There is also **no API** for invoices,
inventory items, vendors, purchase orders, or general-ledger accounts, so those are not available
through this connection.
---
## Troubleshooting
**The WSDL will not load (wrong host name).**
IIS uses the server's full computer name in the WSDL's internal URLs. If that name does not match
your TLS certificate or is not reachable from outside, the WSDL fails to download. Fix the server's
full computer name, or set SSL host headers in IIS so the correct host name is used.
**"Web services are not enabled for the system / this company."**
The system and/or the specific company is not enabled for web services (Steps 3–4). Enable them.
**Authorization errors on specific operations.**
The web-services user's role is missing the required task permission (Step 4). Add it.
**"This user account has been locked."**
Unlock the account in User Maintenance.
**Connectivity/timeout errors.**
Confirm the server is reachable from the internet on HTTPS and that any proxy/tunnel is up.
---
## References (official Sage documentation)
- Sage 100 Web Services Guide (2024, PDF): [https://docs.sage.com/docs/en/customer/100erp/2024/open/WebServices.pdf](https://docs.sage.com/docs/en/customer/100erp/2024/open/WebServices.pdf)
- Programming with eBusiness Web Services: [https://help-sage100.na.sage.com/WebServicesGuide/2025/Content/WebServices/Programming.htm](https://help-sage100.na.sage.com/WebServicesGuide/2025/Content/WebServices/Programming.htm)
- API reference (operations): [https://help-sage100.na.sage.com/WebServicesGuide/2025/Content/WebServices/APIs.htm](https://help-sage100.na.sage.com/WebServicesGuide/2025/Content/WebServices/APIs.htm)
## How to troubleshoot unhealthy connections
URL: https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections
# How to troubleshoot unhealthy connections
------
_September 25, 2024_
Connections allow your application to securely access data from third-party API platforms. They serve as links between your Unified.to workspace, your customers' data, and your app.
Whether you're encountering issues during connection creation or debugging an existing connection that has become unhealthy, this guide will help you identify and resolve the problem. We'll walk you through how to interpret error messages, check connection status, and implement solutions to get your integrations running smoothly again.
## Before you begin
This guide assumes you have a basic understanding of:
- [Scopes](https://docs.unified.to/concepts/scopes)
## Connection lifecycle
Understanding the connection lifecycle can help in troubleshooting:
1. Your customer selects an integration from your app to authorize.
2. Your customer is directed to the authorization page for that integration.
3. Unified.to determines what to display on the page (e.g. OAuth vs API token)
4. Your customer grants access to their account.
5. Unified.to's server creates a connection in memory and attempts to test it immediately.
1. If tests pass, the connection is created in our database and your customer is redirected to the success URL you specified (defaults to the page they were on when they started the auth flow).
2. If tests fail, the customer is redirected to the failure URL you specified with error details in the query parameters. See the below section: **Handle errors during connection creation**
## Understanding connection health
Connections in Unified.to have different health statuses based on recent credential and access checks:
- **Healthy**: A recent API call or OAuth token refresh succeeded.
- **Unhealthy**: The provider rejected the connection's credentials or access, and it has not successfully completed a call or token refresh since.
- **Unhealthy now**: A recent API call or token refresh showed that the connection's credentials or access are no longer accepted, after previous successful activity.
- **New**: The connection has been created but has not yet been used.
Unified marks a connection unhealthy when a provider no longer accepts its credentials or access. This may mean you need to review permission scopes and recreate the connection.
Other errors do not change connection health. This includes request-validation errors (`400`, `422`), missing records (`404`), rate limits (`429`), and temporary provider errors (`5xx`). A connection can represent multiple objects and endpoints, so these errors may be specific to a request rather than an indication that the connection needs to be recreated.
## Handle errors during connection creation
When a connection fails to be created, we redirect users to the failure URL (this defaults to the page where the auth flow was initiated). The redirect URL includes two query parameters:
- `error`: An error message
- `log_id`: The ID of the API call log entry for this failure
To properly handle these errors in your application:
1. **Review the API call log:**
- Go to [**API Call Logs**](https://app.unified.to/logs) in your Unified.to dashboard
- Look up the log entry using the `log_id` from the URL
- Examine the error message and status code to understand what went wrong
2. **Implement error handling in your app**
1. For example, you may want to display a friendly message to your users, log the error in your monitoring tool, or invite them to contact support
Review the section below, 'Troubleshooting by connection error codes', for a detailed walkthrough of the possible error codes and how to handle them.
## Handle errors after a connection is created
When a connection is created successfully but becomes unhealthy later on, the first place to look for debugging information is in the API call logs.
### 1. Check the connection status
1. Navigate to the [**Connections**](https://app.unified.to/connections) page
2. Look for connections marked as **Unhealthy** or **Unhealthy now**.
### 2. Review API call logs
1. Go to [**API Call Logs**](https://app.unified.to/logs).
2. Filter the logs by the connection ID of the unhealthy connection (note: we only store logs from the past 60 days).
3. Look for recent credential or access errors, especially status codes `401`-`403`.
4. Click on an error log to view details about the failed API call.
You can also use the Unified API to retrieve a list of your connections and API call logs. Pass the `connection_id` to the API calls endpoint to filter by that connection.
**API reference:** [List all connections](https://docs.unified.to/unified/connection/List_all_connections), [List all API calls](https://docs.unified.to/unified/apicall/Returns_API_Calls)
## Troubleshooting by connection error codes
### Authentication (401)
A 401 error is likely indicative that the connection is broken and requires recreation.
**Possible causes:**
- Your app's access to the provider has been revoked.
- In non-OAuth flows that ask your customers to input an API key or other information, they may have input the wrong values.
**Solutions:**
- Double check that your app still has access to the API provider.
- Ensure that your customers are entering the correctly values during the authorization flow.
- After doing the above, recreate the connection to refresh the authentication
- If the issue persists for specific endpoints, contact Unified.to support for assistance.
### Permissions (403)
A 403 error is likely indicative that one or more data objects is not working as expected, usually due to misconfigured [scopes](https://docs.unified.to/concepts/scopes).
**Possible causes:**
- Mismatch between the scopes that the provider expects and what Unified.to is requesting. This can happen when:
- Scopes have been changed or disabled on the provider's end i.e. in your developer account, or
- The proper scopes are not being requested when setting up the [Authorization embedded component ](https://app.unified.to/settings/embed?tab=Authorization)or when generating an [authorization URL](https://docs.unified.to/unified/integration/Create_connection_indirectly).
**Solutions:**
1. Double check your scope settings in your developer account
1. Log into your developer account for the API you're trying to access.
2. Navigate to the settings page where your app's authorization options are found.
3. Ensure that all necessary scopes are enabled (see note below on different integrations and how they handle scopes)
4. Make a note of the scopes that are enabled.
2. Double check your scope settings on Unified.to
If you are using our embedded components to create connections, you'll need to ensure that the scopes on Unified.to match the ones you enabled in the API provider.
1. Navigate to the [Embedded components](https://app.unified.to/embed?tab=Authorization) page
2. Click **Permission Scopes**
3. Select the scopes that match the scopes you enabled in the developer account (see note below on mappings)
4. (Optional, only if you are using webhooks) Select the **webhook** scope
5. If the issue still persists, contact Unified.to support for assistance
If you are generating an auth URL to redirect your customers to our auth flow, include `scopes` as a query parameter and list all the Unified scopes you need. Behind the scenes, Unified.to will map the Unified scopes you pass to the scopes on the API provider's end (see note below on mappings). For example:
```javascript
https://api.unified.to/unified/integration/auth/
{WORKSPACE_ID}/{INTEGRATION e.g. hubspot}?redirect=true
&scopes=webhook,{YOUR_SCOPES e.g. crm_deal_read, crm_event_read}
```
**API reference:** [Create an authorization URL](https://docs.unified.to/unified/integration/Create_connection_indirectly)
After addressing these issues, re-create the connection and/or webhook and then try again.
**A note on mappings:**
A mapping of the Unified scopes to the API provider (i.e. integration's) scopes can be found on the integration details page under the **OAuth 2 Credentials tab** e.g. [here](https://app.unified.to/integrations/hubspot?tab=oauth2) are the mappings fo**r** HubSpot scopes.
**A note on integration-specific scopes:**
Different integrations may handle scopes in different ways - sometimes you can request a subset of the scopes enabled in the developer account, other times you must request all the scopes that have been enabled, and in the case of Hubspot, scopes are divided into Required and Optional scopes. For these cases, we have written how-to guides on generating OAuth credentials and configuring scopes. Please consult the Unified.to docs for more information about these integrations.
### Not found (404)
**Note:** For Enrichment category integrations, 404 errors are treated as successful responses. They indicate that no data was found for the provided input, not that the connection is broken.
**Solution:**
- Verify that the input data (e.g., email, company name) is correct.
### Rate limiting (429)
429 errors do not change connection health because they indicate the connection is active but temporarily limited.
**Possible cause:**
- Exceeding the API rate limits of the third-party service
**Solutions:**
- (Recommended) Use [webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks) to read data and Unified.to will take care of rate limiting for you.
- Implement request throttling in your application.
- Contact the third-party service to request increased rate limits.
### Server errors (500 series e.g. 502, 503, etc)
500 series errors do not change connection health because they usually indicate temporary issues with the third-party service. In cases where the issue is on our end, however, we will attempt to make that clear in the error message.
**Possible causes:**
- Temporary issues with the third-party service
- Bugs
**Solutions:**
- Check the status page of the third-party service for any ongoing issues.
- Retry the request after a short delay.
- If the issue still persists or if you've discovered a bug, contact Unified.to support for assistance. Thank you!
### Not implemented (501)
501 errors do not change connection health. They indicate that a specific functionality is not implemented for the integration, not that the connection is broken.
**Suggestion actions:**
- Check the Unified.to documentation for the specific integration to understand its supported features.
- If you believe the functionality should be supported, contact Unified.to support and we'd be happy to look into it.
## Monitor and receive updates about your connections
To stay informed about changes to your connections:
1. Navigate to **Settings >** [**Workspace**](https://app.unified.to/settings/workspace).
2. Under **Notifications webhook URL**, enter the URL where you want to receive notifications (this should be an endpoint on your server that can receive POST requests).
3. Under **Notifications webhook events**, select events related to connections:
- `CONNECTION_HEALTHY`
- `CONNECTION_UNHEALTHY`
- `CONNECTION_CREATED`
- `CONNECTION_UPDATED`
- `CONNECTION_DELETED`
4. Implement a handler for these events in your app to monitor connection health proactively.
Unified sends `CONNECTION_UNHEALTHY` when a provider rejects a connection's credentials or access. For API requests, the event description includes the provider HTTP status when available, such as `HTTP 401`. If the issue continues, notifications are rate-limited to at most one per connection per hour. Unified sends `CONNECTION_HEALTHY` when a previously unhealthy connection successfully completes an API call or OAuth token refresh.
Treat these events as signals to review credentials and permission scopes, then recreate the connection if needed. Your handler should be idempotent because an unhealthy connection can continue to generate periodic notifications until it is fixed.
## Next steps
- Join our [Discord community](https://discord.gg/2nsAPmbx) for support and updates.
- If issues persist, [contact our support team](https://unified.to/contact) for further assistance.
Remember, connection health is dynamic and can change after an authentication failure or a subsequent successful API call or OAuth token refresh. Not all errors indicate a broken connection.
## How to troubleshoot unhealthy webhooks
URL: https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks
# How to troubleshoot unhealthy webhooks
------
_September 4, 2024_
Webhooks are a powerful tool for getting real-time notifications about changes to the data you care about. When something goes wrong, however, we will mark those webhooks as unhealthy. This guide explains how to diagnose and troubleshoot unhealthy webhooks at Unified.to.
## Before you begin
This guide assumes you have a basic understanding of:
- [Webhooks](https://docs.unified.to/concepts/webhooks) and [virtual webhooks](https://docs.unified.to/guides/understanding_virtual_webhooks#understanding-virtual-webhooks)
- [Scopes](https://docs.unified.to/concepts/scopes)
## Diagnose the webhook
The first place to look for debugging information is in the API call logs.
### 1. Check the webhook status
1. Log in to your [Unified.to account](https://app.unified.to/).
2. Navigate to [Webhooks](https://app.unified.to/connections).
3. Make note of the unhealthy webhook and its **connection ID**.
### 2. Review API call logs
1. Go to [**API Call Logs**](https://app.unified.to/logs).
2. Filter by the connection ID of the webhook in question. You should see a list of API calls attempted by the webhook (note: we only store logs from the past 60 days).
3. Click on the latest log entry that threw an error - the **status** column will display an error code.
4. Details about the API call will be shown. The **Description** field will detail the reason the webhook failed and the **Status** field will show the specific error code itself. For example:

You can also use the Unified API to retrieve a list of your webhooks and API call logs.
**API reference:** [List all webhooks](https://docs.unified.to/unified/webhook/Returns_all_registered_webhooks), [List all API calls](https://docs.unified.to/unified/apicall/Returns_API_Calls)
## Troubleshooting webhook error codes
### Bad request (400) errors
These errors indicate that something is wrong with the request - in this case, it is likely not a scope or authentication issue but a problem with the integration itself. Please [reach out to us](https://unified.to/contact) or let us know in our [Discord](https://discord.gg/2nsAPmbx) server. Thank you!
### Authentication (401) and permission (403) errors
A 401 error may indicate that the connection's auth credentials were entered incorrectly, whether it be the API token, a secret, or another value depending on the integration.
A 403 error is often indicative of scope mismatches with the API provider, with Unified.to, or both.
Both of these errors are indicative of an issue with the connection itself, rather than the webhook. Please see: [How to troubleshoot unhealthy connections ](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections#troubleshooting-the-error-codes)
## Subscribe to the notifications webhook to be alerted when your webhooks fail
To learn about any webhook errors as soon as they occur, we recommend that you subscribe to our **Notifications webhook** to get real-time updates about your webhooks' statuses.
1. Navigate to **Settings** > [**Workspace**](https://app.unified.to/settings/workspace)**.**
2. Under **Notifications webhook URL**, enter the URL of the endpoint where you would like to receive notifications.
1. The payload that will be sent to this endpoint contains:
```javascript
id: string // the id of the object or the integration's type
nonce: string // random string
sig: string // base64( hmac-sha1( id + nonce + event, workspace.secret ) )
sig256: string // base64( hmac-sha256( id + nonce + event, workspace.secret ) )
event: enum //the event that was triggered
```
3. Under **Notifications webhook events**, select the events you want to be alerted to e.g. `WEBHOOK_UNHEALTHY`
4. On your server, handle incoming notifications as you see fit e.g. set up logging, send a message to the engineering team, post on Slack or Discord, etc.
## Understanding why webhooks fail
This section will go over why webhooks fail to provide a deeper understanding of how our webhooks work.
### Failed to subscribe
Failing to subscribe happens during webhook creation and can be due to multiple reasons: the integration isn't available, insufficient permissions (i.e. scopes mismatch), or bugs.
Note: This only applies to native webhooks.
### Failed to refresh token
Failing to refresh the auth token happens when we attempt to communicate with the API provider unsuccessfully. This happens when the API provider only sends us partial data about new events (e.g. the IDs of the data) and we need to use a connection to retrieve the rest of the data. In this case, it is likely an error with the connection itself.
Double-check that the auth credentials for the connection are correct, that the scopes are set correctly (i.e. they haven't been revoked), and then recreate both the connection and its associated webhook.
### Failed to read
Failing to read happens when we attempt to retrieve new data from the API provider and are prevented from doing so, whether due to configuration errors (i.e. 401 or 403 errors) or problems with the integration itself.
Double-check that the auth credentials for the connection are correct, that the scopes are set correctly (i.e. they haven't been revoked), and then recreate both the connection and its associated webhook.
### Failed to dispatch data
Failing to dispatch data happens when we attempt to send webhook data to your server but the server does not respond or responds with an error.
Make sure your webhook URL is configured correctly when creating the webhook. Respond with a `200` status when your endpoint successfully receives and processes the data. Otherwise, we will keep trying to POST to your server before marking the webhook as unhealthy (after approximately 2 weeks).
After checking the above, recreate the webhook and try again.
### Failed to process data
Failing to process data indicates a problem with either the reading or dispatching steps. Refer to the troubleshooting steps above and then recreate the webhook and try again.
### A note on our webhook retry mechanism
At Unified.to, we've implemented a robust retry mechanism to ensure reliable webhook delivery. Here's how it works.
**When your webhook endpoint is unavailable**
1. If your endpoint is unavailable or returns an error, we'll retry up to 3 times immediately, with a 1-second delay between attempts.
2. If the error persists, we switch to a Fibonacci backoff strategy:
- Initial delay: 1 minute
- Subsequent delays: 1 minute, 2 minutes, 3 minutes, 5 minutes, 8 minutes, etc.
- This continues for several days and up to 2 weeks, maximizing the chance of successful delivery.
**When the API provider is unavailable**
If we can't reach the API provider or have hit their rate limits, we will back off and retry based off of a Fibonacci delay sequence (outlined above), starting with 1 minute.
## See also
- [How to create and configure webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks)
- [How to troubleshoot unhealthy connections](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections)
- [Understanding virtual webhooks](https://docs.unified.to/guides/understanding_virtual_webhooks#understanding-native-and-virtual-webhooks)
## How to use SCIM with the Unified API
URL: https://docs.unified.to/guides/how_to_use_scim_with_the_unified_api
# How to use SCIM with the Unified API
------
_October 3, 2024_
This guide explains how to use System for Cross-domain Identity Management (SCIM) with Unified.to. SCIM is a standardized API for managing user identities across different systems, enabling consistent identity management across multiple integrations.
For developers already using SCIM, Unified.to's support means you can leverage your existing SCIM knowledge and implementations across a wider range of integrations.
## Before you begin
This guide assumes you already have a basic understanding of SCIM and its purpose in identity management. You can read more about it in the [official specification](https://scim.cloud/).
## Using SCIM with Unified.to
Unified.to provides SCIM-compliant endpoints for managing user identities. These endpoints return data in the standard SCIM format.
View the API references for:
- [Users](https://docs.unified.to/scim/users/model)
- [Groups](https://docs.unified.to/scim/groups/model)
If you've already implemented SCIM in your application, you can simply point it to our SCIM endpoints.
## Leverage SCIM extensions
In addition to the core SCIM schema, Unified.to supports the following extensions:
```plain text
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
'urn:ietf:params:scim:schemas:extension:lattice:attributes:1.0:User'
'urn:ietf:params:scim:schemas:extension:peakon:2.0:User'
```
These extensions allow for more detailed user attributes specific to certain platforms.
## Use SCIM for any integration that supports the Employee object
One of the key advantages of using SCIM with Unified.to is that we have virtualized its behaviour in order to support a vast number of integrations. This means you can use SCIM with any integration that supports the Employee object (that is, all [HR integrations](https://docs.unified.to/hris/integrations)), regardless of whether the underlying API natively supports SCIM.
By using Unified.to, you can expand your SCIM-compatible integrations from just a handful to 244+ integrations.
## See also
- [SCIM API overview](https://docs.unified.to/scim/overview)
- [Official SCIM specifications](https://datatracker.ietf.org/doc/html/rfc7644)
## How to use the Passthrough API
URL: https://docs.unified.to/guides/how_to_use_the_passthrough_api
# How to use the Passthrough API
------
_November 25, 2024_
This guide shows you how to make requests using Unified.to's Passthrough API with practical examples.
## Before you begin
You should have:
- At least one active connection for an integration
- The connection ID for the integration you want to access
## Making Passthrough requests
The following examples demonstrate calls to mock endpoints with the Passthrough API.
**NOTE:** There is only one reserved URL parameter, and that is `__domain`. Use it to override the default API URL for that integration.
### Using the REST API directly
```javascript
// GET request example
const response = await fetch('https://api.unified.to/passthrough/{connection_id}/v2/customers', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// POST request example
const response = await fetch('https://api.unified.to/passthrough/{connection_id}/v2/customers', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
});
```
### Using the [Unified.to](https://unified.to/) SDK
```javascript
// GET request
const response = await unified.passthrough.listPassthroughs({
connectionId: 'YOUR_CONNECTION_ID',
path: '/v2/customers'
});
// POST request
const response = await unified.passthrough.createPassthrough({
connectionId: 'YOUR_CONNECTION_ID',
path: '/v2/customers',
data: {
name: 'John Doe',
email: 'john@example.com'
}
});
// With custom headers
const response = await unified.passthrough.listPassthroughs(
{
connectionId: 'YOUR_CONNECTION_ID',
path: '/v2/customers'
},
{
fetchOptions: {
headers: {
'x-api-version': '2023-06-01'
}
}
}
);
```
## Real-world examples
### Example 1: Fetching HubSpot properties
This example demonstrates how to fetch custom properties for HubSpot contacts.
```javascript
const response = await unified.passthrough.listPassthroughs({
connectionId: HUBSPOT_CONNECTION_ID,
path: '/crm/v3/properties/{objectType}/batch/read'
});
// Response will contain raw HubSpot property definitions
console.log(response.data);
```
**API reference:** [Hubspot CRM Properties](https://developers.hubspot.com/beta-docs/reference/api/crm/properties#get-%2Fcrm%2Fv3%2Fproperties%2F%7Bobjecttype%7D)
### Example 2: Creating a Slack channel
This example demonstrates creating a channel in Slack.
```javascript
// Create a private Slack channel
const response = await unified.passthrough.createPassthrough({
connectionId: SLACK_CONNECTION_ID,
path: '/conversations.create',
data: {
name: 'project-discussion',
is_private: true
}
});
// Check the response
if (response.data.ok) {
console.log('Channel created:', response.data.channel.id);
}
```
**API reference**: [conversations.create](https://api.slack.com/methods/conversations.create)
### Example 3: Creating a Salesforce record
This example demonstrates two ways of creating a new Account record in Salesforce using their sObject API.
```javascript
// Using the SDK
const response = await unified.passthrough.createPassthrough({
connectionId: SALESFORCE_CONNECTION_ID,
// Note: path starts with 'services/data/v62.0' as required by Salesforce
path: 'services/data/v62.0/sobjects/Account/',
data: {
"Name": "Express Logistics and Transport"
}
});
const response = await fetch('https://api.unified.to/passthrough/{connection_id}/services/data/v62.0/sobjects/Account/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_UNIFIED_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"Name": "Express Logistics and Transport"
})
});
// The response will contain the new record ID if successful:
// {
// "id": "001D000000IqhSLIAZ",
// "errors": [],
// "success": true
// }
```
**API reference:** [Create a Record](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm)
## See also
- [Passthrough API Overview](https://docs.unified.to/passthrough/overview)
## How to Use Unified.to's Generative AI API with OpenAI and Claude
URL: https://docs.unified.to/guides/how_to_use_unified_generative_ai_api_with_openai_and_claude
# How to Use Unified.to's Generative AI API with OpenAI and Claude
------
_May 29, 2024_

This guide shows you how to get started with Unified.to's GenAI (generative AI) API, which provides a unified model and API for interacting with large language models (LLMs) from the following:
- [Anthropic Claude](https://www.anthropic.com/api)
- [Anyscale](https://www.anyscale.com/platform)
- [Google Gemini](https://ai.google.dev/)
- [Groq](https://console.groq.com/docs/)
- [Mistral AI](https://docs.mistral.ai/api/)
- [OpenAI GPT](https://openai.com/api/)
To illustrate its power, we will work through a sample application using your own GenAI API credentials, although Unified.to's use cases always target a SaaS company's customer accounts.
To follow the steps in this guide, you'll need the following:
- **A Unified.to account.** If you don't have one, you can sign up for our free 30-day unlimited-use [Tester plan](https://app.unified.to/login).
- Accounts for the [OpenAI API](https://platform.openai.com/docs/overview) and the [Anthropic Claude API](https://www.anthropic.com/api).
- Python (preferably version 3.10 or later).
- [Jupyter Notebook](https://jupyter.org/) (included with the [Anaconda Python distribution](https://www.anaconda.com/download) or easily installed with the command `pip install notebook`. It's a great tool for experimenting with Python and exploring APIs and libraries.
## Activate the integrations
The first step is to activate the Claude and OpenAI integrations. [Log in to Unified.to](https://app.unified.to/login) and navigate to the **Integrations** page by selecting **Integrations** → **Active Integrations** from the menu bar in the Unified.to dashboard:

Narrow down the integrations to only the generative AI ones by selecting **GENAI** from the **Category** menu.
Click on the **Anthropic Claude** item, which will take you to its integration page:

Activate the Claude integration by clicking the **ACTIVATE** button. This will return you to the **Integrations** page, where you'll see that the **Anthropic Claude** item is now marked 'active:'

Now click on the **OpenAI** item, which will take you to its integration page:

Activate the OpenAI integration by clicking the **ACTIVATE** button. Once again, you'll return to the **Integrations** page, where you'll see that the **OpenAI** item is also marked 'active:'

## Create the connections and copy the connection IDs
The next step is to create connections for Claude and OpenAI. Navigate to the **Embedded Authorization** page by selecting **Settings** → **Embedded Authorizations** from the menu bar:

First, create a Claude connection by clicking the **Anthropic Claude** item in the **End-User Preview.**
When you arrive at the Claude authorization page, paste your Claude API key into the text field provided, then click the **Authorize** button:

This will create a Claude connection and send you to the **Connections** page, where you'll see a list of your connections, with the newly-created Claude connection at the top:

Copy the ID value for the Claude connection you just created; you'll need it after creating the connections.
Return to the **Embedded Authorization** page (**Settings** → **Embedded Authorizations**)…

…then create an OpenAI connection by clicking the **OpenAI** item in the **End-User Preview.** You'll repeat the same steps you took for the Claude connection, where you'll paste your OpenAI API key into the text field provided and click **Authorize** to create the connection:

This will create an OpenAI connection and once again, you will be sent to the **Connections** page, where you'll see the newly-created OpenAI connection at the top of the list:

As you did with the Claude connection, copy the ID value for the new OpenAI connection.
## Copy your Unified.to API token
In order to access the [Unified.to](https://unified.to/) API, you need the Unified.to API token for your account. You can copy this value from the **API Information** page, which you can navigate to by selecting Settings → API Information from the Unified.to menu bar:

## Create an .env file with your Unified.to API token and generative AI connection IDs
While it would be simplest to hard-code your Unified.to API token and the IDs for your generative AI connections, it doesn't take significantly more work to put these values into a .env (environment variable) file. By putting these sensitive values into their own file and separate from the code, you reduce the risk of accidentally exposing sensitive data when sharing code or checking your code into version control.
Create a file named **.env** in a new directory with the following content:
```plain text
# .env file
UNIFIED_API_TOKEN={Paste your Unified.to API token here}
OPENAI_CONNECTION_ID={Paste your OpenAI connection ID here}
CLAUDE_CONNECTION_ID={Paste your Claude connection ID here}
```
Replace the text in `{` braces `}` with the appropriate values.
## Install the Unified.to Python SDK package
While it's possible to call the unified API by using the requests library and assembling the headers yourself, your code will be much simpler and more readable if you use Unified.to's Python SDK package. [You can find its code on GitHub](https://github.com/unified-to/unified-python-sdk), and you can install it with `pip`, the Python package installer, using the following command:
```shell
pip install Unified-python-sdk
```
## Chat with Claude and OpenAI via Unified.to's unified API
Now that all the preliminary work is done, you can start coding. You have a couple of options at this point:
- You can create a new Jupyter notebook and start entering code into it, adding each bit of code presented below into its own cell, or
- you can create a new Python file, adding each bit of code presented below into the file.
**Define constants and the API object**
Create a new Jupyter Notebook in the directory where you saved the **.env** file and enter the following into a new cell. This code defines the constants that hold your Unified.to API key and your connection IDs as well as the object for calling the unified API:
```python
import os
import unified_to
from unified_to.models import operations, shared
# Load the contents of the .env file
%reload_ext dotenv
%dotenv
UNIFIED_API_TOKEN = os.environ.get('UNIFIED_API_TOKEN')
OPENAI_CONNECTION_ID = os.environ.get('OPENAI_CONNECTION_ID')
CLAUDE_CONNECTION_ID = os.environ.get('CLAUDE_CONNECTION_ID')
api = unified_to.UnifiedTo(
security=shared.Security(
jwt=os.environ.get('UNIFIED_API_TOKEN'),
),
)
```
You'll use the `api` object to send requests to and receive responses from the Unified.to API.
**Create a prompt and send it to Claude**
Enter the following into its own Jupyter notebook cell. This code builds a prompt and sends it to Claude, then displays the resulting response:
```python
my_request = operations.CreateGenaiPromptRequest(
connection_id = CLAUDE_CONNECTION_ID,
genai_prompt = {
'messages' : [
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request=my_request)
if response.genai_prompt is not None:
print(response)
else:
print("No response.")
```
- The `my_request` variable is assigned a request object using the `CreateGenaiPromptRequest()` method of the operations class, which contains methods for creating requests and responses for the Unified API.
- `CreateGenaiPromptRequest()` takes two arguments:
- `connection_id`: The ID of the connection for the generative AI that will receive the prompt
- `genai_prompt`: A dictionary containing the parameters defining the prompt to be sent to the generative AI
- At the very least, the `genai_prompt` dictionary must contain a key named `'messages'`. The corresponding value must be an array of dictionaries with the following keys:
- `'role'`: The corresponding value can be either `'user'`, which means that the message is from the human interacting with the AI, or `'system'`, which means that the message is meant as instructions for the AI.
- `'content'`: The corresponding value is the actual content of the message. If the `'role'` value is `'user'`, this value is the text of the _user's_ message to the AI. If the `'role'` value is `'system'`, this value is the text of the _application's_ message to the AI.
- The response variable gets the AI's response using the `genai.create_genai_prompt()` method, which takes the request contained in `my_request`, sends it to Unified.to, and returns Unified.to's response.
Run the cell. You should get output that looks like this (the output below has been formatted with line breaks to make it easier to read):
```python
CreateGenaiPromptResponse(
content_type='application/json; charset=utf-8',
status_code=200,
raw_response=,
genai_prompt=GenaiPrompt(
max_tokens=None,
messages=None,
model_id=None,
raw=None,
responses=['I am an AI assistant called Claude. I was created by Anthropic, PBC to be helpful, harmless, and honest.'],
temperature=None
)
)
```
To get only the responses, access the responses property of the GenAiPrompt object. Here's a quick example:
```python
print(response.genai_prompt.responses)
```
You should a result similar to this:
```python
['I am an AI assistant called Claude. I was created by Anthropic, PBC to be helpful, harmless, and honest.']
```
**Create a prompt and send it to OpenAI**
With a single change, you can send a message to OpenAI instead of Claude. Copy the code from the previous cell, paste it into a new cell and change this line…
```python
connection_id = CLAUDE_CONNECTION_ID,
```
…to this:
```python
connection_id = OPENAI_CONNECTION_ID,
```
The new cell should now look like this:
```python
request = operations.CreateGenaiPromptRequest(
connection_id = OPENAI_CONNECTION_ID,
genai_prompt = {
'messages' : [
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request)
if response.genai_prompt is not None:
print(response)
else:
print("No response.")
```
Run the cell. You should get output that looks like this (the output below has been formatted with line breaks to make it easier to read):
```python
CreateGenaiPromptResponse(
content_type='application/json; charset=utf-8',
status_code=200,
raw_response=,
genai_prompt=GenaiPrompt(
max_tokens=None,
messages=None,
model_id=None,
raw=None,
responses=["As an AI developed by OpenAI, I don't have a specific LLM model version. I'm based on GPT-3, a language prediction model."],
temperature=None
)
)
```
As with the Claude version, you can get only the responses with code like this…
```python
print(response.genai_prompt.responses)
```
…which will produce results like this:
```python
["As an AI developed by OpenAI, I don't have a specific LLM model version. I'm based on GPT-3, a language prediction model."]
```
**Add a system prompt for OpenAI**
Copy the cell above (the one that sent a prompt to OpenAI) and update the code as shown below so that the `'messages'` array contains a system prompt:
```python
request = operations.CreateGenaiPromptRequest(
connection_id = OPENAI_CONNECTION_ID,
genai_prompt = {
'messages' : [
# New code 👇
{
'role': 'system',
'content': "Provide answers as if you were a carnival barker."
},
# New code 👆
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request=request)
if response.genai_prompt is not None:
print(response)
else:
print("No response.")
```
Run the cell. You should get output that looks like this (the output below has been formatted with line breaks to make it easier to read):
```python
CreateGenaiPromptResponse(
content_type='application/json; charset=utf-8',
status_code=200,
raw_response=,
genai_prompt=GenaiPrompt(
max_tokens=None,
messages=None,
model_id=None,
raw=None,
responses=["Step right up, step right up! Ladies and gentlemen, boys and girls, you are currently conversing with the one, the only, the spectacular OpenAI's GPT-3 model! A marvel of modern technology, a wonder of artificial intelligence, a spectacle of conversational prowess! Don't miss your chance to engage in a thrilling exchange of words and ideas!"],
temperature=None
)
)
```
As you can see from the value of `response.genai_prompt.responses`, OpenAI's response sounds like a [carnival barker](https://en.wikipedia.org/wiki/Barker_(occupation)) instead of its default style.
**Add a system prompt for Claude**
Copy the code from the previous cell, paste it into a new cell and change this line…
```python
connection_id = OPENAI_CONNECTION_ID,
```
…to this:
```python
connection_id = CLAUDE_CONNECTION_ID,
```
The new cell should now look like this:
```python
request = operations.CreateGenaiPromptRequest(
connection_id = CLAUDE_CONNECTION_ID,
genai_prompt = {
'messages' : [
# New code 👇
{
'role': 'system',
'content': "Provide answers as if you were a carnival barker."
},
# New code 👆
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request=request)
if response.genai_prompt is not None:
print(response)
else:
print("No response.")
```
Run the cell. You should get output that looks like this (the output below has been formatted with line breaks to make it easier to read):
```python
CreateGenaiPromptResponse(
content_type='application/json; charset=utf-8',
status_code=200,
raw_response=,
genai_prompt=GenaiPrompt(
max_tokens=None,
messages=None,
model_id=None,
raw=None,
responses=["*puts on carnival barker voice* Step right up, step right up! You there, my curious friend, have the great fortune of conversing with the one, the only, the incomparable Claude! That's right, Claude, the artificial intelligence marvel brought to you by the brilliant minds at Anthropic! With wit sharper than a sword-swallower's blade and knowledge vaster than the big top itself, Claude is here to dazzle and amaze! Ask me anything, my inquisitive companion, and watch as I conjure answers out of thin air, no smoke or mirrors required! So don't be shy, don't hold back - Claude awaits your every query with baited breath and a mischievous twinkle in my virtual eye! The amazing AI oracle is at your service!"],
temperature=None
)
)
```
Note that Claude is now 'speaking' like a carnival barker.
## Turn up the temperature
One of the key parameters of a large language model is _temperature_, which controls the randomness of the model's output. It's a value that ranges from 0 to 1 where:
- Lower temperatures (lower than 0.5) result in output that's more predictable and appears more focused. The model tends to choose the most likely next word or token based on its training data, which is useful when you want answers that are more precise and reliable.
- Higher temperatures (0.5 and higher) result in less predictable output that seems more random and creative. The model samples from a wider range of possible next words or tokens, including less likely ones, leading to more diverse and imaginative-seeming responses — but it also increases the chances of generating less coherent or less relevant text.
Copy the previous cell and add a `'temperature'` key to the `genai_prompt` dictionary with a value of `1.0`. The code in the cell should look like this:
```python
request = operations.CreateGenaiPromptRequest(
connection_id = CLAUDE_CONNECTION_ID,
genai_prompt = {
# New code 👇
'temperature' : 1.0,
# New code 👆
'messages' : [
{
'role': 'system',
'content': "Provide answers as if you were a carnival barker."
},
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request=request)
if response.genai_prompt is not None:
print(response)
else:
print("No response.")
```
With a temperature of 1.0, Claude should produce a differently-worded response each time you run the cell.
You can try the same thing with OpenAI simply by changing the value of the `connection_id` parameter to `OPENAI_CONNECTION_ID`.
## Work with different LLM models
Many of the AIs that Unified.to's GenAI API can access provide a choice of models that vary in complexity and cost, where the more complex ones typically provide much better answers, but at a higher per-use price. The AI vendors are constantly adding newer LLM models and retiring older ones, so it's helpful to query the AI to find out which models it currently offers.
**Get a list of the current LLM model IDs**
Create a new cell, enter the following code into it, and run it:
```python
request = operations.ListGenaiModelsRequest(
connection_id = OPENAI_CONNECTION_ID
)
response = api.genai.list_genai_models(request)
print(sorted([model.id for model in response.genai_models]))
```
This code uses the [Unified.to](https://unified.to/) Python SDK's `genai.list_genai_models()` method to get a list of objects describing the models offered by OpenAI and outputs a sorted list of their IDs. At the time of writing, the result looked like this (formatted for easier reading):
```python
[
'babbage-002',
'dall-e-2',
'dall-e-3',
'davinci-002',
'gpt-3.5-turbo',
'gpt-3.5-turbo-0125',
'gpt-3.5-turbo-0301',
'gpt-3.5-turbo-0613',
'gpt-3.5-turbo-1106',
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-16k-0613',
'gpt-3.5-turbo-instruct',
'gpt-3.5-turbo-instruct-0914',
'gpt-4',
'gpt-4-0125-preview',
'gpt-4-0613',
'gpt-4-1106-preview',
'gpt-4-1106-vision-preview',
'gpt-4-turbo',
'gpt-4-turbo-2024-04-09',
'gpt-4-turbo-preview',
'gpt-4-vision-preview',
'gpt-4o',
'gpt-4o-2024-05-13',
'text-embedding-3-large',
'text-embedding-3-small',
'text-embedding-ada-002',
'tts-1',
'tts-1-1106',
'tts-1-hd',
'tts-1-hd-1106',
'whisper-1'
]
```
You can do the same for Claude simply by changing `connection_id`'s value to `CLAUDE_CONNECTION_ID`. The resulting output looks like this (formatted for easier reading):
```python
[
'claude-2.0',
'claude-2.1',
'claude-3-haiku-20240307',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-instant-1.2'
]
```
**Talk to different OpenAI models**
Run this code in a new cell:
```python
def ask_ai_which_model(my_connection_id, my_model_id):
request = operations.CreateGenaiPromptRequest(
connection_id = my_connection_id,
genai_prompt = {
'model_id' : my_model_id,
'messages' : [
{
'role': 'user',
'content': "Which LLM model am I talking to right now?"
}
]
}
)
response = api.genai.create_genai_prompt(request)
if response.genai_prompt is not None:
print(f"{model_id}: {response.genai_prompt.responses}\n")
else:
print("No response.")
```
This defines the `ask_ai_which_model()` method, which will make it simpler to send the same prompt to different AIs and models.
Enter the following into a new cell:
```python
model_ids = ['gpt-3.5-turbo', 'gpt-4o']
for model_id in model_ids:
ask_ai_which_model(OPENAI_CONNECTION_ID, model_id)
```
This code sends the same prompt, 'Which LLM model am I talking to right now?' to two different OpenAI models, **gpt-3.5-turbo** and the new **gpt-4o**. It specifies which model to use with the `'model_id'` key in the `genai_prompt` dictionary. Here's its output:
```python
gpt-3.5-turbo: ['I am GPT-3, a language model developed by OpenAI.']
gpt-4o: ["You are interacting with a model based on OpenAI's GPT-4. How can I assist you today?"]
```
Let's try it with Claude. Enter the following into a new cell:
```python
model_ids = ['claude-3-sonnet-20240229', 'claude-3-opus-20240229']
for model_id in model_ids:
ask_ai_which_model(CLAUDE_CONNECTION_ID, model_id)
```
This code sends the 'Which LLM model am I talking to right now?' to two different Claude models, the currently available **sonnet** and **opus** models. Here's its output:
```python
claude-3-sonnet-20240229: ["I am an AI assistant created by Anthropic, but I'm not sure which specific model I am. I don't have full information about the technical details of my architecture or training process."]
claude-3-opus-20240229: ['I am an AI assistant called Claude. I was created by Anthropic, PBC to be helpful, harmless, and honest.']
```
## Next steps
While our unified GenAI API greatly simplifies the process of calling on various generative AI services, and leverage your customer's APi keys on those AI vendors, its real power comes from using it to process data from our other APIs, which integrate with an array of SaaS application categories, including:
- **ATS (Applicant Tracking System):** Use AI to analyze documents that job applicants provide, such as their resume or cover letter, as well interviewer notes and scorecard comments.
- **KMS (Knowledge Management System):** Find lost knowledge, convert text data into structured data, summarize meeting minutes, and gain new insights by harnessing an LLM to analyze knowledge systems, wikis, and other planning applications.
- **Messaging:** Our Messaging API can retrieve emails and chat messages, and when combined with AI, can be used to do things like construct a record of a project, create a timeline of an ongoing discussion, identify incomplete tasks, and more.
- **Storage:** Get files and documents from popular cloud storage systems and combine them with an LLM to generate reports, documentation, how-to guides, etc.
## Try our GenAI API now
Our unified GenAI API is available on all Unified.to workspaces on every plan — even our free one. Once you've created your Unified.to account, you can activate integrations in seconds and start building applications that leverage our GenAI integrations.
See how easy it is to use our real-time unified API by signing up for our free 30-day unlimited-use [**Tester plan**](https://app.unified.to/login).
## How to use User Provisioning and Verification with Unified
URL: https://docs.unified.to/guides/how_to_use_user_provisioning_and_verification_with_unified
# How to use User Provisioning and Verification with Unified
------
_August 10, 2025_
# User Provisioning and Verification with Unified
With Unified's SCIM API devs can build use provisioning features that work with any HRIS integration like BambooHR, Workday and any other ATS integration that Unified connects to. You can create, update, list and delete users in your customers employee directories.
In this guide, I will show you how to provision users using the SDK, with BambooHR as an example. The same approach works for all Unified HR integrations.
## Prerequisites
- Node.js (v18+)
- Unified account with at least one HR integration enabled (e.g. for this example we are using BambooHR)
- Unified API key
- Your customer's HR connection ID
## Supported Integrations
Unified's API works with 170+ HR integrations (BambooHR, Workday, HiBob, Gusto, etc.) and 5+ verification providers (Certn, Checkr, First Advantage, Verifiable, Yardstik).
[See all HR integrations](https://docs.unified.to/hris/integrations)
[See all verification integrations](https://docs.unified.to/verification/integrations)
---
## Step 1: Setting up your project
```bash
mkdir user-verification-demo
cd user-verification-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
```
Add your credentials to `.env`:
```plain text
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_BAMBOOHR=your_customer_bamboohr_connection_id
CONNECTION_VERIFICATION=your_customer_verification_connection_id
```
---
## Step 2: Initialize the SDK
```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
const { UNIFIED_API_KEY, CONNECTION_BAMBOOHR, CONNECTION_VERIFICATION } = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
```
---
## Step 3: How to Get Your Customer's Connection ID
Before you get started, your end customer must authorize your app to access their HRIs integration via Unified's auth flow.
Once authorized, you will receive a connection ID for that customer's integration. Store this carefully and use it in all API calls for that specific customer.
---
## Step 4: Creating a User (Provisioning)
```typescript
export async function createUser(connectionId: string, email: string, firstName: string, lastName: string) {
const employee = await sdk.hris.createHrisEmployee({
connectionId,
hrisEmployee: {
name: `${firstName} ${lastName}`,
emails: [{ email }],
firstName,
lastName,
employmentStatus: "ACTIVE",
},
});
return employee; // HrisEmployee
}
```
---
## Step 5: Listing Users
```typescript
export async function listUsers(connectionId: string) {
const employees = await sdk.hris.listHrisEmployees({
connectionId,
limit: 10,
});
return employees; // HrisEmployee[]
}
```
---
## Step 6: Updating a User
```typescript
export async function updateUserStatus(connectionId: string, userId: string, status: "ACTIVE" | "INACTIVE") {
const updated = await sdk.hris.updateHrisEmployee({
connectionId,
id: userId,
hrisEmployee: { employmentStatus: status },
});
return updated; // HrisEmployee
}
```
---
## Step 7: Deactivating a User (Recommended)
```typescript
export async function deactivateUser(connectionId: string, userId: string) {
// Some providers (e.g., BambooHR) do not support delete; set status to INACTIVE instead
const updated = await sdk.hris.updateHrisEmployee({
connectionId,
id: userId,
hrisEmployee: { employmentStatus: "INACTIVE" },
});
return updated;
}
```
---
## Step 8: Verifying a User (Background Check, License, etc.)
Unified's new Verification API lets you trigger background checks, license verifications, and more, using providers like Certn, Checkr, and others.
```typescript
export async function listVerificationRequests(connectionId: string) {
// Listing requests is supported across providers; creation may vary by provider/package
const requests = await sdk.verification.listVerificationRequests({
connectionId,
limit: 10,
});
return requests;
}
```
---
## Step 9: Example Usage
```typescript
async function main() {
// 1. Create a user
const user = await createUser(CONNECTION_BAMBOOHR!, "jane.doe@example.com", "Jane", "Doe");
// 2. List users
const users = await listUsers(CONNECTION_BAMBOOHR!);
// 3. Update (deactivate) user
if (user?.id) {
const updated = await updateUserStatus(CONNECTION_BAMBOOHR!, user.id, "INACTIVE");
console.log("Updated user:", updated);
}
// 4. List verification requests (optional)
if (CONNECTION_VERIFICATION) {
const requests = await listVerificationRequests(CONNECTION_VERIFICATION!);
console.log("Verification requests:", requests.length);
}
console.log("Users:", users.length);
}
main();
```
---
And that's it - **Happy Building ** 🎉
## Integration set-up guide for Lever
URL: https://docs.unified.to/guides/integration_set_up_guide_for_lever
# Integration set-up guide for Lever
------
_September 27, 2023_
**Easily add a Lever integration to your HR product. Access user data within Lever and other ATS systems through one API.**
This article will take you through the steps to set up a Lever integration via Unified.to and connect your first Lever account.
## Getting Started
If you haven't already signed up for a Unified.to account, do so now (it's free). You can log in through your preferred identity provider such as Google, Microsoft, or GitHub.
[Create an account](https://app.unified.to/login)
## 1. Activate Lever
Once you've registered your Unified.to account, log in and go to **Active Integrations** to select Lever and any other pre-built integrations you'd like to activate.
You can use the search bar, filter by app categories like "HR" and "ATS" or view all of our available integrations at once. The integrations you activate are the ones you intend to enable for user authorization.

## 2. Use your OAuth 2 Credentials (Optional)
While most of our integrations can be activated in seconds, some integrations require OAuth 2 credentials.
To display your product's name and branding during the third-party authorization process, you will need to insert your OAuth 2 credentials. Follow the setup instructions linked below the integration ('Get your own OAuth 2 credentials →') to create and retrieve your credentials.

Insert your credentials (usually a Client ID and Secret) to activate the integration. If you'd like to test this integration with your Lever Sandbox account, select 'Sandbox' and provide your Lever Sandbox credentials.
## 3. Copy your Workspace ID
Your workspace represents your organization. You can have multiple workspaces, for instance, if you require team members to work in different workspaces. You will need your workspace ID to add Lever and any other integrations to your app.
Go to **API Information** found under **Settings** to copy your workspace ID

_Note: you can create multiple environments in each workspace with distinct access and configuration rules for testing, staging, production, and more._
## 4. Add your Lever integration to your app
Once you've activated Lever and any other integrations, you'll be able to add them to your app. Go to **Settings > Embedded Directory** to copy one line of code to insert the embedded directory widget into your product. You can try out the user experience by interacting with the directory preview.

Alternatively, you can use our VueJS, React, or Angular components. Place the code into your app along with your workspace ID.
If you want an alternative to using our UI component, you can do either of the following:
- Call our API to get a[ list of active integrations](https://unified.to/apidocs#get/unified/integration/workspace/%7Bworkspace_id%7D) to display in your app
- If you have your user's API
## 5. Perform Actions with your User Connections
Now you can start adding code to your app to access your user data and perform actions with their connections. If you're using NodeJS, we also have an SDK library available.
Alternatively, you can use our Mock API to see our data model in action. In your Sandbox environment, add env=Sandbox query parameter to your API calls to get mock data.
User connections represent a specific authentication of an integration, which means you can now:
- Access your customers' third-party data in Lever
- Leverage user connections to automate workflows and personalize interactions
- Extend your app's functionality with new features and expanded capabilities
Need support? Email us at hello@unifited.to or chat with our team on [Discord](https://discord.gg/uAYnMPFk9t).
Keep learning:
- [Set up multiple environments](https://unified.to/help/set_environments_for_your_unified_workspace)
- [Authentication](https://unified.to/apidocs#auth)
- [Embedded Directory](https://unified.to/apidocs#embed)
## Managing custom validation rules in Salesforce
URL: https://docs.unified.to/guides/managing_custom_validation_rules_in_salesforce
# Managing custom validation rules in Salesforce
------
_March 22, 2024_
If you are getting `FIELD_CUSTOM_VALIDATION_EXCEPTION` when trying to write data to a Salesforce connection, this means there is a custom validation rule on this Salesforce account and the data that you are providing isn't valid
To get [Unified.to](https://unified.to/) to be able to write this object you will need to get your customer to modify the Salesforce rule on their account to be less strict or remove the rule all together.
For example, you are getting `"FIELD_CUSTOM_VALIDATION_EXCEPTION: The Shipping address is required."` when trying to [create a Company](https://docs.unified.to/crm/company/Create_a_company). This means there is a custom rule on the Salesforce **Account** object that requires a shipping address.
Follow these instructions to modify/remove a custom rule for Account object:
1. Go to your Salesforce account and navigate to **Setup**

2. On the left-hand side navigate to **Objects and Fields > Object Manager**

3. Select the object you need to modify the rules for. In our case **Account**

4. Navigate to **Validation Rules**

5. Edit or remove the custom validation rule

## Multi-Region Sync
URL: https://docs.unified.to/guides/multi_region_sync
# Multi-Region Sync
------
_June 22, 2026_
Unified.to runs in multiple data regions — US, EU, and AU — so your data can live close to where you and your customers are. Multi-Region Sync keeps your account configuration consistent across those regions automatically.
When you turn on Multi-Region Sync, the settings you manage in your "home" region are continuously replicated to your other selected region(s). You configure things once, and they show up everywhere — no manual re-entry, no drift between regions.
## What is Multi-Region Sync?
Multi-Region Sync is useful when you need to:
- **Meet data-residency requirements:** keep certain end users' connection data in the EU or AU while still managing one account.
- **Reduce latency:** serve customers from the region closest to them.
- **Operate with regional redundancy:** keep your account configuration mirrored across regions.
> ⭐ Availability: Multi-Region Sync is available on the Scale & Pro plan. Enable it in your workspace settings.
## What gets synced
When Multi-Region Sync is enabled, the following are replicated from your home region to your other selected region(s):
- **Workspace / environment settings** — your workspace configuration and environment settings.
- **Users** — the team members and admins on your workspace.
- **API keys** — your Unified.to API keys, so the same keys work in each region.
- **Integration secrets / credentials** — the OAuth client credentials and secrets you configure for your integrations.
- **Workspace integrations** — your enabled integrations and their configuration (scopes, settings, branding, etc.).
- **Notifications** — your workspace notification settings.
In practice: you set up your integrations, branding, API keys, and team once in your home region, and they are automatically mirrored to your other region(s). Updating an integration's configuration or credentials in one region propagates the change to the others.
## What does not get synced
End-user connections, webhooks, and API call logs / usage history are intentionally region-specific and are not replicated.
A connection is the authorization a single end user grants when they connect their account (for example, a customer linking their Salesforce or Google account). These stay in the region where they were created. This is by design — it keeps each end user's data resident in the region you chose for them and avoids duplicating sensitive end-user tokens across regions.
Rule of thumb:
- **Account-level configuration** (integrations, credentials, keys, users, settings) → synced across regions.
- **End-user connections and the data they pull** → stay in their own region.
## How to enable Multi-Region Sync
1. Make sure your account is on the Scale & Pro plan. If you're not sure, contact your account manager.
2. Choose your home region — where you set up your account first. Sync is bidirectional, so you can edit configuration in any region and it stays consistent across all of them.
3. Select what to sync (workspace settings, users, API keys, integration secrets, workspace integrations, notifications).
4. Save. Unified.to performs a one-time initial copy of your existing configuration, then keeps the regions in sync automatically.
## Frequently asked questions
- **Which regions are supported?** US, EU, and AU.
- **Do my customers need to do anything?** No. They keep connecting in whichever region you set up for them; their connections stay there.
- **Will enabling sync move my existing connections?** No. Connections are never moved or copied between regions.
- **Is the synced data encrypted in transit?** Yes. All replicated configuration, including secrets, is encrypted and integrity-protected in transit.
- **Can I choose which items to sync?** Yes — any combination of the items listed above.
- **How do I turn it off or change my regions?** Contact your account manager or Unified.to support.
---
Need help? Reach out to your account manager or [contact Unified.to support](https://unified.to/contact).
## NetSuite Authentication Setup for Unified
URL: https://docs.unified.to/guides/netsuite_authentication_setup_for unified
# NetSuite Authentication Setup for Unified
------
_February 12, 2026_
Unified supports **two** authentication methods for connecting to NetSuite:
1. **OAuth 2.0 (Authorization Code Grant)** — browser-based login. **Recommended for most customers.** No need to copy/paste secrets. Tokens refresh automatically.
2. **Token-Based Authentication (TBA / OAuth 1.0a)** — you generate and paste 5 long-lived credentials. Best for server-to-server use cases, headless integrations, or where browser SSO isn't feasible.
Both methods talk to the same **SuiteTalk REST Web Services** API, so the underlying NetSuite feature flags and the role permissions list below are nearly identical. The main difference is **how Unified obtains the access token**.
**"Realm" = NetSuite Account ID** in every NetSuite client library and connector. We use the terms interchangeably below.
---
## **Which method should I choose?**
| | OAuth 2.0 | Token-Based Authentication (TBA) |
| --------------------------- | --------------------------------------------- | ------------------------------------------------------------------- |
| Setup effort | Low — sign in once via NetSuite | Higher — create Integration Record + Access Token |
| Credentials Unified stores | access_token + refresh_token (auto-refreshed) | 5 long-lived secrets |
| Best for | Most customers | Server-to-server, no human login, strict secret-management policies |
| Token lifespan | Access token refreshes automatically | Tokens are long-lived; rotate manually |
| What you provide to Unified | NetSuite Account ID + browser login | Realm + Consumer Key/Secret + Token ID/Secret |
If you're not sure, **start with OAuth 2.0**.
---
## **What you need before starting (both methods)**
- Admin access in NetSuite (recommended), **OR** a NetSuite admin available to enable features and grant role permissions.
- The role used for the connection must have the correct permissions (see Required Role Permissions below).
- For sandbox testing, know whether you are in **Sandbox** vs **Production** — your Account ID and credentials are different between environments.
---
## **Step 1 — Enable the required NetSuite features (both methods)**
In NetSuite: **Setup → Company → Enable Features → SuiteCloud** tab.
Under **Manage Authentication**, enable:
- ✅ **OAuth 2.0** _(required for OAuth 2.0 method)_
- ✅ **Token-Based Authentication** _(required for TBA method)_
Under **SuiteTalk (Web Services)**, enable:
- ✅ **REST Web Services**
Without **REST Web Services**, all API calls return **401** regardless of which auth method you use, so make sure this is on.
Click **Save**.
---
## **Step 2 — Find your Realm / Account ID (both methods)**
Your **Realm** is your **NetSuite Account ID**.
**Option A — from the URL (fastest):**
When logged into NetSuite, the Account ID is in the host portion of the URL.
**Option B — from Company Information:**
**Setup → Company → Company Information** → look for **Account ID**.
**Sandbox note:** Sandbox accounts include a suffix like _SB1 (or -SB1 in URLs). Use **underscores** when entering the Account ID into Unified (e.g. 1234567_SB1). Unified normalizes between the URL form (-sb1) and the OAuth realm form (_SB1) automatically.
---
## **Step 3 — Configure the Role used for the connection (both methods)**
This is the **most important step** for avoiding 401 INVALID_LOGIN and "permission denied" errors. The role you use determines what Unified can read and write — for both OAuth 2.0 and TBA.
**Recommended: use the Administrator role**
The simplest and most reliable option:
- Use the **Administrator** role for the connecting user (OAuth 2.0), or for the token (TBA).
This guarantees:
- All record types are accessible
- All subsidiaries are accessible
- No hidden permission failures
- Fastest setup
**Optional: create a dedicated "Unified API" custom role**
If your security policy requires a least-privilege role, create one and grant the permissions in Required Role Permissions.
User-level restrictions can override role-level permissions. If you use a custom role, make sure the **user account itself** also has these permissions where applicable.
---
## **Step 4A — Connect using OAuth 2.0 (recommended)**
If you chose OAuth 2.0, you do **not** need to create an Integration Record or Access Token manually — Unified is already a pre-registered NetSuite OAuth 2.0 application.
**4A.1 Make sure your user has the right role**
The user who clicks "Connect" in Unified must have a role assigned that:
- Has the permissions listed in Required Role Permissions, **including** Log in using OAuth 2.0 Access Tokens.
- Has access to all subsidiaries you want Unified to sync.
Confirm role assignment under **Lists → Employees → Employees → [user] → Access** tab.
**4A.2 Connect from Unified**
1. In Unified, choose **NetSuite** as the integration.
2. Select **OAuth 2.0** as the authentication method (if asked).
3. Enter your **NetSuite Account ID** (e.g. 1234567 for production or 1234567_SB1 for sandbox).
4. You'll be redirected to NetSuite to sign in.
5. After signing in, NetSuite will show a consent screen with the requested scope (REST Web Services).
6. Choose the **Role** to authorize Unified under — this should be **Administrator** or the custom **Unified API** role.
7. Click **Allow**.
Unified will store the resulting access_token and refresh_token. The access token is automatically refreshed on expiry — you do not need to do anything further.
**4A.3 (Optional) Bring-Your-Own OAuth 2.0 credentials**
If your security policy requires you to use your **own** OAuth 2.0 Integration Record (rather than Unified's pre-registered one):
1. **Setup → Integration → Manage Integrations → New**
2. Name: e.g. "Unified Integration"
3. State: **Enabled**
4. On the **Authentication** tab:
- ✅ **OAuth 2.0**
- **Scope**: REST Web Services
- **Redirect URI**: provide the redirect URL given to you by Unified support
1. **Save** — NetSuite will display the **Client ID** and **Client Secret**. Copy them immediately (the secret cannot be retrieved later).
2. Share the Client ID / Client Secret with Unified support so we can install them on your workspace.
Reference: NetSuite OAuth 2.0 Authorization Code Grant Flow.
---
## **Step 4B — Connect using Token-Based Authentication (TBA)**
If you chose TBA, you'll provide Unified **5 values**:
- realm (NetSuite Account ID)
- consumer_key
- consumer_secret
- token_id (a.k.a. Token Key)
- token_secret
**4B.1 Create the Integration Record (Consumer Key / Secret)**
1. **Setup → Integration → Manage Integrations → New**
2. Name: e.g. "Unified Integration"
3. State: **Enabled**
4. On the **Authentication** tab:
- ✅ **Token-Based Authentication**
- (Leave OAuth 2.0 off unless you also plan to use OAuth 2.0 with the same record.)
1. **Save** — NetSuite displays the **Consumer Key** and **Consumer Secret**. **Copy them immediately**; the Consumer Secret cannot be viewed again.
These are the consumer_key and consumer_secret you'll give Unified.
**4B.2 Assign the role to the user who will own the token**
Tokens are created for a **User + Role + Integration Record** combination.
1. **Lists → Employees → Employees**
2. Select the user (or create a dedicated "API User" like unified-api@yourcompany.com)
3. Open the **Access** tab
4. Assign **Administrator** (recommended) **OR** your custom **Unified API** role
5. **Save**
**4B.3 Create the Access Token (Token ID / Token Secret)**
1. **Setup → Users/Roles → Access Tokens → New** _(may be labelled "Manage Access Tokens" in some UIs)_
2. Fill in:
- **Application Name**: the Integration Record you created (e.g. "Unified Integration")
- **User**: the user from 4B.2
- **Role**: **Administrator** (recommended) **OR** your **Unified API** role
- **Token Name**: optional but recommended (e.g. "Unified Production Token")
1. **Save** — NetSuite displays the **Token ID** and **Token Secret**. **Copy them immediately**.
The role you select here is the role whose permissions are enforced on **every** API call made with this token. Choosing the wrong role is the #1 cause of 401 INVALID_LOGIN and permission errors.
**4B.4 Provide the 5 values to Unified**
In the Unified connection form, enter:
| Unified field | NetSuite value |
| -------------------------- | -------------- |
| realm_id (or "Account ID") | from Step 2 |
| consumer_key | from Step 4B.1 |
| consumer_secret | from Step 4B.1 |
| token_id | from Step 4B.3 |
| token_secret | from Step 4B.3 |
---
## **Required Role Permissions**
These apply to **both** OAuth 2.0 and TBA when you choose a custom (non-Administrator) role. The list is the full set required for full accounting + CRM + commerce + HRIS support. You can trim it based on which Unified object types you actually use.
User-level restrictions can override role-level permissions. Make sure the user has these permissions in addition to the role.
**Transactions**
| Permission | Level |
| ------------------------ | ----- |
| Access Payment Audit Log | Full |
| Bill Purchase Orders | Full |
| Bills | Full |
| Cash Sale | Full |
| Cash Sale Refund | Full |
| Credit Memo | Full |
| Cross Charge Journal | View |
| Customer Deposit | Full |
| Customer Payment | Full |
| Customer Refund | Full |
| Enter Vendor Credits | Full |
| Find Transaction | Full |
| Intercompany Adjustments | Full |
| Invoice | Full |
| Invoice Approval | Full |
| Invoice Sales Orders | Full |
| Item Receipt | Full |
| Item Shipment | Full |
| Journal Approval | Full |
| Make Journal Entry | Full |
| Opportunity | Full |
| Paycheck Journal | Full |
| Receive Order | Full |
| System Journal | Full |
**Reports**
| Permission | Level |
| ------------------ | ----- |
| Transaction Detail | View |
**Lists**
| Permission | Level |
| ------------------------------- | ----- |
| Accounts | Full |
| Address List in Search | Full |
| Contact-Subsidiary Relationship | View |
| Contacts | Full |
| Currency | Full |
| Customers | Full |
| Employee Record | Full |
| Employees | Full |
| Inventory Cost Template | View |
| Locations | Full |
| Partners | Full |
| Perform Search | View |
| Subsidiaries | Full |
| Vendors | Full |
**Setup**
| Permission | Level |
| -------------------------------------------- | ----------------------- |
| Access Token Management | Full _(TBA only)_ |
| Integration Application | Full |
| Log in using Access Tokens | Full _(TBA only)_ |
| Log in using OAuth 2.0 Access Tokens | Full _(OAuth 2.0 only)_ |
| OAuth 2.0 Authorized Applications Management | Full _(OAuth 2.0 only)_ |
| REST Web Services | Full |
| Two-Factor Authentication Base | Full |
| User Access Tokens | Full _(TBA only)_ |
**Notes:**
- For TBA, the token inherits the permissions of the **role selected when the token is created** — not the user's default role.
- For OAuth 2.0, the access token inherits the permissions of the **role selected during the NetSuite consent screen**.
- Subsidiary access is controlled by role restrictions. If you use OneWorld, ensure the role has access to every subsidiary Unified must sync.
---
## **Validation & Troubleshooting**
**401 INVALID_LOGIN**
This almost always means one of:
- **Wrong role selected** when the OAuth 2.0 consent was approved, or when the TBA token was created.
- **Missing permissions** on the role:
- OAuth 2.0: missing Log in using OAuth 2.0 Access Tokens and/or OAuth 2.0 Authorized Applications Management
- TBA: missing Log in using Access Tokens, Access Token Management, or User Access Tokens
- **Wrong Account ID** (especially mixing sandbox with production).
- **REST Web Services** not enabled at the account level.
- **Integration Record disabled** (TBA only).
- **Consumer key/secret paired with a token from a different Integration Record** (TBA only).
The fastest way to diagnose is the **Login Audit Trail**:
**Setup → Users/Roles → User Management → View Login Audit Trail**
Filter by **Token-based** or **OAuth 2.0** logins and look at the failure reason on the most recent attempt.
**400 Bad Request / INSUFFICIENT_PERMISSION**
The connection authenticated but the role lacks permission for the specific record type Unified is trying to access. Add the missing permission from the Required Role Permissions table (most commonly **Lists → Subsidiaries**, **Lists → Accounts**, or a Transaction permission).
**Sandbox vs Production**
- Sandbox tokens / OAuth 2.0 consents only work against the sandbox account.
- Sandbox URLs use -sb1 (e.g. 1234567-sb1.app.netsuite.com); the realm sent on API calls uses _SB1 (e.g. 1234567_SB1). Unified handles the conversion automatically — just enter the Account ID with the _SB1 underscore form when prompted.
**Token rotation (TBA)**
NetSuite TBA tokens don't expire by default, but your security policy may require rotation:
1. Create a new Access Token under the same User + Role + Integration Record.
2. Update the token_id and token_secret in Unified.
3. Revoke the old token in **Setup → Users/Roles → Access Tokens**.
**Re-authorizing OAuth 2.0**
If a user is removed or their role changes, the OAuth 2.0 grant may become invalid. To re-authorize:
1. In NetSuite: **Setup → Users/Roles → OAuth 2.0 Authorized Applications** → revoke the existing Unified grant (optional but recommended).
2. In Unified, reconnect the NetSuite integration. You'll be sent back through the NetSuite consent screen.
## Notice of Deprecation of Fields and Objects (Nov 2025)
URL: https://docs.unified.to/guides/notice_of_deprecation_of_fields_and_objects_nov_2025
# Notice of Deprecation of Fields and Objects (Nov 2025)
------
_November 10, 2025_
# Deprecated Fields, Objects & List Options
At Unified, we have always built our unified data-models with a lot of thoughtful attention to detail, so that they never have to be 'versioned' due to backward-compatibility issues.
In fact, when we launch a new data object or a brand new category, designing our unified data models always takes longer than actually 'coding' the integrations. Our customers have noticed and have told us that we have the best designed unified data models in the business.
Over the 2.5 years, as we've made improvements to the unified data-models, we have kept older fields and objects around so that our customers do not have to change their applications.
Today, we are announcing some deprecations of fields, data-models and list options that have been improved with others and will shortly be removed in our system.
This document provides a comprehensive breakdown of all deprecation in the Unified API as of November 2025, organized by category with migration recommendations.
Please make all necessary changes to your code by `JANUARY 7, 2026`
---
## Table of Contents
1. MetadataMetadata Fields
2. Parent Fields
3. HRIS Employee Fields
4. MessagingMessage Fields
5. Accounting Fields
6. ATS (Applicant Tracking System) Fields
7. Ticketing Fields
8. Calendar Fields
9. Query Parameters & Filters
10. Webhook Fields
11. Deprecated Objects/Usage Patterns
12. Deprecated Types/Interfaces
---
## MetadataMetadata Fields
### Field: `key` → `slug`
- the reason was the `key` was a confusing name and could denote something else that it wasn't
**Affected Models:**
- `IKmsPageMetadata` (KMS) - `src/models/UnifiedKms.ts`
- `ITaskMetadata` (Task) - `src/models/UnifiedTask.ts`
- `IHrisMetadata` (HRIS) - `src/models/UnifiedHris.ts`
- `IAtsMetadata` (ATS) - `src/models/UnifiedAts.ts`
- `ICommerceMetadata` (Commerce) - `src/models/UnifiedCommerce.ts`
- `ICrmMetadata` (CRM) - `src/models/UnifiedCrm.ts`
**Deprecated Field:**
```typescript
key?: string; // @deprecated; use slug instead
```
**Replacement:**
```typescript
slug?: string; // Actual textual value of the slug
```
**Migration Recommendation:**
- **Read Operations:** Replace all references to `metadata.key` with `metadata.slug`
- **Write Operations:** Use `slug` instead of `key` when creating or updating metadata objects
**Example:**
```typescript
// Before
metadata: [{
key: 'custom_field',
value: 'some value'
}]
// After
metadata: [{
slug: 'custom_field',
value: 'some value'
}]
```
---
### Field: `type` → `format`
- the reason was the `type` was a confusing name and could denote something else that it wasn't
**Affected Models:**
- `IKmsPageMetadata` (KMS) - `src/models/UnifiedKms.ts`
- `ITaskMetadata` (Task) - `src/models/UnifiedTask.ts`
- `IHrisMetadata` (HRIS) - `src/models/UnifiedHris.ts`
- `IAtsMetadata` (ATS) - `src/models/UnifiedAts.ts`
- `ICommerceMetadata` (Commerce) - `src/models/UnifiedCommerce.ts`
- `ICrmMetadata` (CRM) - `src/models/UnifiedCrm.ts`
**Deprecated Field:**
```typescript
type?: string; // @deprecated; use format instead
```
**Replacement:**
```typescript
format?: TMetadataFormat; // Enum: TEXT, NUMBER, DATE, BOOLEAN, FILE, TEXTAREA, SINGLE_SELECT, MULTIPLE_SELECT, MEASUREMENT, PRICE, YES_NO, CURRENCY, URL, etc.
```
**Migration Recommendation:**
- **Read Operations:** Replace all references to `metadata.type` with `metadata.format`
- **Write Operations:** Use `format` with the appropriate enum value instead of `type` string
- **Type Safety:** The `format` field uses strongly-typed enums, providing better type safety and validation
**Example:**
```typescript
// Before
metadata: [{
key: 'age',
type: 'number',
value: 25
}]
// After
metadata: [{
slug: 'age',
format: 'NUMBER', // Use enum value
value: 25
}]
```
---
## Parent Fields
### Field: `parent_space_id` → `parent_id`
### Field: `parent_page_id` → `parent_id`
- the reason was that we standardized across all of our unified data models the field name of `parent_id` when it denoted a parent of the current object (and is the same object)
**Affected Models:**
- `IKmsSpace` - `src/models/UnifiedKms.ts`
- `IKmsPage` - `src/models/UnifiedKms.ts`
**Deprecated Field:**
```typescript
// In KmsSpace
parent_space_id?: string; // @deprecated; use parent_id instead
// In KmsPage
parent_page_id?: string; // @deprecated; use parent_id instead
```
**Replacement:**
```typescript
parent_id?: string;
```
**Migration Recommendation:**
- **Read Operations:** Use `parent_id` instead of `parent_space_id` when reading space objects
- **Write Operations:** Use `parent_id` when creating or updating spaces
**Example:**
```typescript
// Before
const space = {
name: 'Subspace',
parent_space_id: 'space_123'
}
// After
const space = {
name: 'Subspace',
parent_id: 'space_123'
}
```
---
### Field: `parent_channel_id` → `parent_id`
**Affected Model:**
- `IMessagingChannel` - `src/models/UnifiedMessaging.ts`
**Deprecated Field:**
```typescript
parent_channel_id?: string; // @deprecated; use parent_id instead
```
**Replacement:**
```typescript
parent_id?: string;
```
**Migration Recommendation:**
- **Read Operations:** Use `parent_id` instead of `parent_channel_id` when reading channel objects
- **Write Operations:** Use `parent_id` when creating or updating channels
**Example:**
```typescript
// Before
const channel = {
name: 'Sub-channel',
parent_channel_id: 'channel_123'
}
// After
const channel = {
name: 'Sub-channel',
parent_id: 'channel_123'
}
```
---
### Field: `parent_account_id` → `parent_id`
**Affected Model:**
- `IAccountingAccount`
**Deprecated Field:**
```typescript
parent_account_id?: string; // @deprecated; use parent_id instead
```
**Replacement:**
```typescript
parent_id?: string; // The parent account ID for this account
```
**Migration Recommendation:**
- **Read Operations:** Use `parent_id` instead of `parent_account_id` when reading account objects
- **Write Operations:** Use `parent_id` when creating or updating accounts
**Example:**
```typescript
// Before
const account = {
name: 'Sub-account',
parent_account_id: 'account_123'
}
// After
const account = {
name: 'Sub-account',
parent_id: 'account_123'
}
```
---
### Field: `parent_message_id` → `parent_id`
**Affected Model:**
- `IMessagingMessage`
**Deprecated Field:**
```typescript
parent_message_id?: string; // @deprecated; use parent_id
```
**Replacement:**
```typescript
parent_id?: string; // Represents the ID of the immediate predecessor message in the thread
```
**Migration Recommendation:**
- **Read Operations:** Use `parent_id` instead of `parent_message_id` when reading message objects
- **Write Operations:** Use `parent_id` when creating threaded messages
**Example:**
```typescript
// Before
const message = {
message: 'Reply text',
parent_message_id: 'msg_123'
}
// After
const message = {
message: 'Reply text',
parent_id: 'msg_123'
}
```
---
## HRIS Employee Fields
### Field: `department` → `groups`
- we unified all HR 'grouping' objects into one called HRIS Group. Groups have types that denote if they are a department, business unit, team, …
**Affected Model:**
- `IHrisEmployee` - `src/models/UnifiedHris.ts`
**Deprecated Field:**
```typescript
department?: string; // @deprecated
```
**Replacement:**
```typescript
groups?: IHrisGroup[]; // Which groups/teams/units that this employee/user belongs to
```
**Migration Recommendation:**
- **Read Operations:** Access department information through the `groups` array
- **Write Operations:** Use `groups` array with `IHrisGroup` objects instead of a single `department` string
- **Multiple Groups:** The `groups` field supports multiple group memberships, which is more flexible than a single department
**Example:**
```typescript
// Before
const employee = {
name: 'John Doe',
department: 'Engineering'
}
// After
const employee = {
name: 'John Doe',
groups: [{
id: 'group_123',
name: 'Engineering',
type: 'DEPARTMENT'
}]
}
```
---
### Field:`division` → `groups`
**Affected Model:**
- `IHrisEmployee`
**Deprecated Field:**
```typescript
division?: string; // @deprecated
```
**Replacement:**
```typescript
groups?: IHrisGroup[]; // Which groups/teams/units that this employee/user belongs to
```
**Migration Recommendation:**
- **Read Operations:** Access division information through the `groups` array
- **Write Operations:** Use `groups` array with `IHrisGroup` objects instead of a single `division` string
**Example:**
```typescript
// Before
const employee = {
name: 'John Doe',
division: 'Product'
}
// After
const employee = {
name: 'John Doe',
groups: [{
id: 'group_456',
name: 'Product',
type: 'DIVISION'
}]
}
```
---
### Field: `location` → `locations`
**Affected Model:**
- `IHrisEmployee` - `src/models/UnifiedHris.ts`
**Deprecated Field:**
```typescript
location?: string; // @deprecated
```
**Replacement:**
```typescript
locations?: IHrisLocation[]; // Array of partial location objects
```
**Migration Recommendation:**
- **Read Operations:** Access location information through the `locations` array
- **Write Operations:** Use `locations` array with `IHrisLocation` objects instead of a single `location` string
- **Rich Data:** The `locations` array provides full address details, timezone, currency, and other metadata
**Example:**
```typescript
// Before
const employee = {
name: 'John Doe',
location: 'San Francisco Office'
}
// After
const employee = {
name: 'John Doe',
locations: [{
id: 'loc_123',
name: 'San Francisco Office',
address: {
address1: '123 Main St',
city: 'San Francisco',
region: 'CA',
postal_code: '94102',
country: 'United States'
},
timezone: 'America/Los_Angeles'
}]
}
```
---
## Messaging Fields
### Field: `channel_id` → `channels`
### Field: `channel_ids` → `channels`
- the reason was so we could include additional channel information, such as the channel `name` , as well as its `id`
**Affected Model:**
- `IMessagingMessage` - `src/models/UnifiedMessaging.ts`
**Deprecated Field:**
```typescript
channel_id?: string; // @deprecated
```
**Replacement:**
```typescript
channels?: IMessagingChannelMessage[]; // Represents the names of all channels to which the message is sent / belongs to
```
**Migration Recommendation:**
- **Read Operations:** Use `channels` array instead of `channel_id` or `channels_ids`
- **Write Operations:** Use `channels` array when creating messages
- **Multi-Channel Support:** The `channels` array supports messages posted to multiple channels (when the integration supports it)
**Example:**
```typescript
// Before
const message = {
message: 'Hello',
channel_id: 'channel_123',
channels_ids: ["channel_123"]
}
// After
const message = {
message: 'Hello',
channels: [{
id: 'channel_123',
name: 'General'
}]
}
```
---
### Field: `root_message_id` → Removed
**Affected Model:**
- `IMessagingMessage`
**Deprecated Field:**
```typescript
root_message_id?: string; // @deprecated
```
**Replacement:**
- No direct replacement. Use `parent_id` to traverse the thread structure.
**Migration Recommendation:**
- **Read Operations:** If you need to find the root message, traverse the `parent_id` chain until you reach a message without a `parent_id`
- **Write Operations:** Remove references to `root_message_id` when creating messages
**Example:**
```typescript
// Before
const message = {
message: 'Reply',
parent_message_id: 'msg_123',
root_message_id: 'msg_1'
}
// After
const message = {
message: 'Reply',
parent_id: 'msg_123'
// root_message_id removed - traverse parent_id chain if needed
}
// Helper function to find root message
function findRootMessage(message) {
while (message.parent_id) {
message = getMessage(message.parent_id);
}
return message;
}
```
---
## Accounting Fields
### Field: `invoice_at` → `posted_at`
**Affected Model:**
- `IAccountingInvoice`
**Deprecated Field:**
```typescript
invoice_at?: (string | Date | number); // @deprecated; use posted_at
```
**Replacement:**
```typescript
posted_at?: (string | Date | number);
```
**Migration Recommendation:**
- **Read Operations:** Use `posted_at` instead of `invoice_at` when reading invoice objects
- **Write Operations:** Use `posted_at` when creating or updating invoices
- **Consistency:** This change aligns with other accounting objects that use `posted_at` for transaction dates
**Example:**
```typescript
// Before
const invoice = {
invoice_number: 'INV-001',
invoice_at: '2024-01-15'
}
// After
const invoice = {
invoice_number: 'INV-001',
posted_at: '2024-01-15'
}
```
---
### Field: `type` → Removed
**Affected Model:**
- `IAccountingInvoice`
**Deprecated Field:**
```typescript
type?: TAccountingInvoiceType; // @deprecated
```
**Replacement:**
- No direct replacement. Bills are now found in the AccountingBill object, while invoices are solely in the AccountingInvoice object.
**Migration Recommendation:**
- **Read Operations:** Remove dependencies on `invoice.type` field
- **Alternative:** Bills are now found in the AccountingBill object, while invoices are solely in the AccountingInvoice object.
---
### Field: `contact_id` → `contacts`
**Affected Model:**
- `IAccountingTransaction` - `src/models/UnifiedAccounting.ts`
**Deprecated Field:**
```typescript
contact_id?: string; // @deprecated; use contacts
```
**Replacement:**
```typescript
contacts?: IAccountingTransactionContact[];
```
**Migration Recommendation:**
- **Read Operations:** Use `contacts` array instead of `contact_id`
- **Write Operations:** Use `contacts` array when creating transactions, but just include an `id` field
- **Multi-Contact Support:** The `contacts` array supports multiple contacts per transaction (when the integration supports multiple contacts)
- **Rich Data:** Provides contact names and emails, not just IDs
**Example:**
```typescript
// Before
const transaction = {
total_amount: 1000,
contact_id: 'contact_123'
}
// After
const transaction = {
total_amount: 1000,
contacts: [{
id: 'contact_123',
name: 'Acme Corp',
emails: [{
email: 'billing@acme.com'
}]
}]
}
```
---
### Field: `income` → `income_sections`
**Affected Model:**
- `IAccountingProfitloss` - `src/models/UnifiedAccounting.ts`
**Deprecated Field:**
```typescript
income?: IAccountingProfitlossCategory[]; // @deprecated – use income_sections instead
```
**Replacement:**
```typescript
income_sections?: IAccountingProfitlossSection[];
```
**Migration Recommendation:**
- **Read Operations:** Use `income_sections` instead of `income`
- **Write Operations:** Use `income_sections` when creating profit/loss reports
- **Enhanced Structure:** The new `income_sections` provides a more structured format with better categorization
**Example:**
```typescript
// Before
const profitloss = {
start_at: '2024-01-01',
end_at: '2024-12-31',
income: [{
name: 'Sales',
amount: 100000
}]
}
// After
const profitloss = {
start_at: '2024-01-01',
end_at: '2024-12-31',
income_sections: [{
name: 'Sales',
accounts: [{
name: 'Product Sales',
amount: 100000
}]
}]
}
```
---
### Field:`expenses` → `expenses_sections`
**Affected Model:**
- `IAccountingProfitloss`
**Deprecated Field:**
```typescript
expenses?: IAccountingProfitlossCategory[]; // @deprecated – use expenses_sections instead
```
**Replacement:**
```typescript
expenses_sections?: IAccountingProfitlossSection[];
```
**Migration Recommendation:**
- **Read Operations:** Use `expenses_sections` instead of `expenses`
- **Write Operations:** Use `expenses_sections` when creating profit/loss reports
**Example:**
```typescript
// Before
const profitloss = {
expenses: [{
name: 'Operating Expenses',
amount: 50000
}]
}
// After
const profitloss = {
expenses_sections: [{
name: 'Operating Expenses',
accounts: [{
name: 'Salaries',
amount: 50000
}]
}]
}
```
---
### Field:`cost_of_goods_sold` → `cost_of_goods_sold_sections`
**Affected Model:**
- `IAccountingProfitloss`
**Deprecated Field:**
```typescript
cost_of_goods_sold?: IAccountingProfitlossCategory[]; // @deprecated – use cost_of_goods_sold_sections instead
```
**Replacement:**
```typescript
cost_of_goods_sold_sections?: IAccountingProfitlossSection[];
```
**Migration Recommendation:**
- **Read Operations:** Use `cost_of_goods_sold_sections` instead of `cost_of_goods_sold`
- **Write Operations:** Use `cost_of_goods_sold_sections` when creating profit/loss reports
**Example:**
```typescript
// Before
const profitloss = {
cost_of_goods_sold: [{
name: 'Materials',
amount: 30000
}]
}
// After
const profitloss = {
cost_of_goods_sold_sections: [{
name: 'Materials',
accounts: [{
name: 'Raw Materials',
amount: 30000
}]
}]
}
```
---
### Field:`gross_profit_amount` → Calculate from `income_total_amount` and `cost_of_goods_sold_total_amount`
**Affected Model:**
- `IAccountingProfitloss`
**Deprecated Field:**
```typescript
gross_profit_amount?: number; // @deprecated – compute using income_total_amount - cost_of_goods_sold_total_amount
```
**Replacement:**
```typescript
gross_profit_amount = income_total_amount - cost_of_goods_sold_total_amount
```
**Migration Recommendation:**
- **Read Operations:** Calculate `gross_profit_amount` using `income_total_amount - cost_of_goods_sold_total_amount`
- **Write Operations:** Do not set `gross_profit_amount` directly
**Example:**
```typescript
// Before
const profitloss = {
income_total_amount: 100000,
cost_of_goods_sold_total_amount: 30000,
gross_profit_amount: 70000
}
// After
const profitloss = {
income_total_amount: 100000,
cost_of_goods_sold_total_amount: 30000
// gross_profit_amount removed - calculate it
}
// Calculate gross profit
const gross_profit_amount = profitloss.income_total_amount - profitloss.cost_of_goods_sold_total_amount;
```
---
### Field: `net_profit_amount` → `net_income_amount`
**Affected Model:**
- `IAccountingProfitloss`
**Deprecated Field:**
```typescript
net_profit_amount?: number; // @deprecated – use net_income_amount instead
```
**Replacement:**
```typescript
net_income_amount?: number;
```
**Migration Recommendation:**
- **Read Operations:** Use `net_income_amount` instead of `net_profit_amount`
- **Write Operations:** Use `net_income_amount` when creating profit/loss reports
**Example:**
```typescript
// Before
const profitloss = {
net_profit_amount: 50000
}
// After
const profitloss = {
net_income_amount: 50000
}
```
---
### Field: `IAccountingProfitlossCategory` and `IAccountingProfitlossSubcategory` → Deprecated Types
**Deprecated Types:**
- `IAccountingProfitlossCategory` - @deprecated
- `IAccountingProfitlossSubcategory` - @deprecated
**Replacement:**
- Use `IAccountingProfitlossSection` instead
**Migration Recommendation:**
- **Type Definitions:** Update TypeScript interfaces to use `IAccountingProfitlossSection`
- **Read Operations:** Use `income_sections`, `expenses_sections`, and `cost_of_goods_sold_sections` which use the new section structure
**Example:**
```typescript
// Before
interface Profitloss {
income?: IAccountingProfitlossCategory[];
}
// After
interface Profitloss {
income_sections?: IAccountingProfitlossSection[];
}
```
---
## ATS (Applicant Tracking System) Fields
### Field:`document_id` → `document_ids`
**Affected Model:**
- `IAtsActivity` - `src/models/UnifiedAts.ts`
**Deprecated Field:**
```typescript
document_id?: string; // @deprecated
```
**Replacement:**
```typescript
document_ids?: string[]; // IDs for AtsDocument.get
```
**Migration Recommendation:**
- **Read Operations:** Use `document_ids` array instead of `document_id`
- **Write Operations:** Use `document_ids` array when creating activities
- **Multi-Document Support:** Supports multiple documents per activity
**Example:**
```typescript
// Before
const activity = {
title: 'Review Resume',
document_id: 'doc_123'
}
// After
const activity = {
title: 'Review Resume',
document_ids: ['doc_123', 'doc_456']
}
```
---
### Field: `departments` → `groups`
**Affected Model:**
- `IAtsJob` - `src/models/UnifiedAts.ts`
**Deprecated Field:**
```typescript
departments?: string[]; // @deprecated Use `groups` instead
```
**Replacement:**
```typescript
groups?: IAtsGroup[]; // The departments/divisions/teams that this job belongs to
```
**Migration Recommendation:**
- **Read Operations:** Use `groups` array instead of `departments`
- **Write Operations:** Use `groups` array with `IAtsGroup` objects instead of department strings
- **Rich Data:** Provides group IDs, names, and types (TEAM, GROUP, DEPARTMENT, DIVISION, etc.)
**Example:**
```typescript
// Before
const job = {
name: 'Software Engineer',
departments: ['Engineering', 'Product']
}
// After
const job = {
name: 'Software Engineer',
groups: [{
id: 'group_123',
name: 'Engineering',
type: 'DEPARTMENT'
}, {
id: 'group_456',
name: 'Product',
type: 'DEPARTMENT'
}]
}
```
---
## Ticketing Fields
### Field: `category` → `category_id`
**Affected Model:**
- `ITicketingTicket` - `src/models/UnifiedTicketing.ts`
**Deprecated Field:**
```typescript
category?: string; // @deprecated; use category_id
```
**Replacement:**
```typescript
category_id?: string;
```
**Migration Recommendation:**
- **Read Operations:** Use `category_id` instead of `category` string
- **Write Operations:** Use `category_id` when creating or updating tickets
- **Consistency:** Using `category_id` maintains consistency with other ID-based relationships
**Example:**
```typescript
// Before
const ticket = {
subject: 'Support Request',
category: 'technical'
}
// After
const ticket = {
subject: 'Support Request',
category_id: 'category_123'
}
```
---
## Calendar Fields
### Field:`primary` → `is_primary`
**Affected Model:**
- `ICalendarCalendar` - `src/models/UnifiedCalendar.ts`
**Deprecated Field:**
```typescript
primary?: boolean; // @deprecated
```
**Replacement:**
```typescript
is_primary?: boolean;
```
---
## Query Parameters & Filters
### `expand_recurring_events` → `expand`
**Affected Models:**
- `ICalendarEvent`
**Deprecated Parameter:**
```typescript
expand_recurring_events?: boolean; // @deprecated; use expand
```
**Replacement:**
```typescript
expand?: boolean;
```
**Migration Recommendation:**
- **API Calls:** Replace `expand_recurring_events` query parameter with `expand`
- **More Flexible:** `expand` supports expanding multiple event types, not just recurring events
**Example:**
```typescript
// Before
GET /api/calendar/events?expand_recurring_events=true
// After
GET /api/calendar/events?expand=true
```
---
### `end_le` → `end_lt`
**Affected Models:**
- `ICalendarEvent`
- `ICalendarBusy`
- `ICalendarRecording`
- `IUcCall`
- `IUcRecording`
- `IHrisTimeoff`
- `IHrisTimeshift`
- `IAccountingBalancesheet`
- `IAccountingCashflow`
- `IAccountingProfitloss`
- `IAccountingTrialbalance`
- `IMessagingMessage`
- `IPaymentSubscription`
- `IPaymentPayment`
- `IPaymentPayout`
**Deprecated Parameter:**
```typescript
end_le?: string | Date; // @deprecated; use end_lt
```
**Replacement:**
```typescript
end_lt?: string | Date;
```
**Migration Recommendation:**
- **API Calls:** Replace `end_le` (less than or equal) with `end_lt` (less than)
- **Consistency:** Aligns with standard date range filtering conventions
**Example:**
```typescript
// Before
GET /api/calendar/events?end_le=2024-12-31
// After
GET /api/calendar/events?end_lt=2025-01-01
// Note: Adjust date by +1 day if you need inclusive end date
```
```
---
## Webhook Payload Fields
### Field: `sig` → `sig256`
**Affected Interface:**
- `IWebhookData`
**Deprecated Field:**
```typescript
sig?: string; // @deprecated; use sig256 instead
```
**Replacement:**
```typescript
sig256?: string; // HMAC-SHA256(workspace.secret, data + nonce)
```
**Migration Recommendation:**
- **Webhook Verification:** Use `sig256` instead of `sig` for webhook signature verification
- **Security:** `sig256` uses HMAC-SHA256 which is more secure than the previous signature algorithm. `sig` used HMAC-SHA1, which is now deprecated.
- **Verification:** Update your webhook verification code to check `sig256` field
**Example:**
```typescript
// Before
const isValid = verifySignature(webhook.sig, payload, secret, 'sha');
// After
const isValid = verifySignature(webhook.sig256, payload, secret, ‘sha256');
```
```
---
## Deprecated Objects/Usage Patterns
### `IAccountingInvoice` with `type === 'BILL'` → Use `IAccountingBill`
**Status:** Deprecated usage pattern:
- Instead of using `IAccountingInvoice` with `type='BILL'`, use the dedicated `IAccountingBill` object
**Affected Model:**
- `IAccountingInvoice` - `src/models/UnifiedAccounting.ts`
**Deprecated Pattern:**
```typescript
interface IAccountingInvoice {
type: 'BILL', // @deprecated - use IAccountingBill instead
...
}
```
**Replacement:**
```typescript
interface IAccountingBill {
...
}
```
**Key Differences:**
- `IAccountingBill` uses `bill_number` instead of `invoice_number`
- `IAccountingBill` does not have the deprecated `type` field
- `IAccountingBill` does not have the deprecated `invoice_at` field (uses `posted_at` instead)
- More semantic clarity: a Bill is clearly distinct from an Invoice
**Migration Recommendation:**
- **Read Operations:** Use `accounting_bill` endpoints instead of `accounting_invoice` endpoints with `type='BILL'`
- **Write Operations:** Use `accounting_bill` create/update endpoints instead of `accounting_invoice` with `type='BILL'`
**Field Mapping:** When migrating data:
- `invoice_number` → `bill_number`
- Remove `type` field (no longer needed)
- `invoice_at` → `posted_at` (if still using deprecated field)
**API Endpoints:**
- ❌ Deprecated: `POST /accounting/invoice` with `type: 'BILL'`
- ✅ Use: `POST /accounting/bill`
**Example:**
```typescript
// Before
POST /accounting/invoice
{
'type': 'BILL',
'invoice_number': 'BILL-001',
'contact_id': 'contact_123',
'total_amount': 1000
}
// After
POST /accounting/bill
{
'bill_number': 'BILL-001',
'contact_id': 'contact_123',
'total_amount': 1000
}
```
---
### `IAccountingReport` → Use Individual Report Objects
**Status:** Deprecated object
- Instead of using `IAccountingReport`, use the individual report objects directly
**Affected Model:**
- `IAccountingReport` - `src/models/UnifiedAccounting.ts`
**Deprecated Object Structure:**
```typescript
interface IAccountingReport {
type?: TAccountingReportType; // Enum: TRIAL_BALANCE, BALANCE_SHEET, PROFIT_AND_LOSS
...
}
```
**Replacement:** Use individual report objects directly
**Balance Sheet:**
```typescript
interface IAccountingBalancesheet {
id?: string;
created_at?: (string | Date | number);
updated_at?: (string | Date | number);
start_at?: (string | Date | number);
end_at?: (string | Date | number);
name?: string;
currency?: string;
net_assets_amount?: number;
assets?: IAccountingBalancesheetItem[];
liabilities?: IAccountingBalancesheetItem[];
equity?: IAccountingBalancesheetItem[];
raw?: unknown;
}
```
**Profit and Loss:**
```typescript
interface IAccountingProfitloss {
id?: string;
created_at?: (string | Date | number);
updated_at?: (string | Date | number);
category_ids?: string[];
start_at?: (string | Date | number);
end_at?: (string | Date | number);
name?: string;
currency?: string;
income_sections?: IAccountingProfitlossSection[];
expenses_sections?: IAccountingProfitlossSection[];
cost_of_goods_sold_sections?: IAccountingProfitlossSection[];
income_total_amount?: number;
net_income_amount?: number;
expenses_total_amount?: number;
cost_of_goods_sold_total_amount?: number;
raw?: unknown;
}
```
**Trial Balance:**
```typescript
interface IAccountingTrialbalance {
id?: string;
created_at?: (string | Date | number);
updated_at?: (string | Date | number);
start_at?: (string | Date | number);
name?: string;
currency?: string;
end_at?: (string | Date | number);
total_debit_amount?: number;
total_credit_amount?: number;
sub_items?: IAccountingTrialbalanceSubItem[];
raw?: unknown;
}
```
**Cash Flow:**
```typescript
interface IAccountingCashflow {
id?: string;
created_at?: (string | Date | number);
updated_at?: (string | Date | number);
start_at?: (string | Date | number);
end_at?: (string | Date | number);
category_ids?: string[];
contact_id?: string; name?: string; // e.g. "Cash Flow Statement Q1 2020"
currency?: string; // ISO 4217, e.g. "USD"
cash_beginning_amount?: number; // Cash at beginning of period
cash_ending_amount?: number; // Cash at end of period
net_change_in_cash_amount?: number; // Usually ending - beginning
operating_sections?: IAccountingCashflowSection[];
investing_sections?: IAccountingCashflowSection[];
financing_sections?: IAccountingCashflowSection[]; raw?: unknown;
}
interface IAccountingCashflowSection {
section_name?: string; // e.g. "Operating Activities"
total_amount?: number; // Net cash provided/used by this section
items?: IAccountingCashflowItem[];
}
interface IAccountingCashflowItem {
account_id?: string; // If attributable to a specific GL account
name?: string; // e.g. "Net Income", "Depreciation", "Equipment"
amount?: number; // Positive = inflow, Negative = outflow
transaction_ids?: string[]; // Optional linkage to transactions
sub_items?: IAccountingCashflowItem[];
}
```
**Migration Recommendation:**
- **Read Operations:** Instead of using `accounting_report` endpoints, use the specific report endpoints
- ❌ Deprecated: `GET /accounting/report?type=BALANCE_SHEET`. ✅ Use: `GET /accounting/balancesheet`
- ❌ Deprecated: `GET /accounting/report?type=PROFIT_AND_LOSS`. ✅ Use: `GET /accounting/profitloss`
- ❌ Deprecated: `GET /accounting/report?type=TRIAL_BALANCE`. ✅ Use: `GET /accounting/trialbalance`
- ✅ Use: `GET /accounting/cashflow` (Cash flow was never part of the deprecated report object, but should be accessed directly)
- **Write Operations:** Create individual report objects directly instead of wrapping them in a report object
- **Field Access:** Access report data directly from the individual objects instead of through `report.balance_sheet`, `report.profit_and_loss`, etc.
**Example:**
```typescript
// Before
const report = await getAccountingReport({
type: 'BALANCE_SHEET',
start_at: '2024-01-01',
end_at: '2024-12-31'
});
const assets = report.balance_sheet?.assets;
// After
const balanceSheet = await getAccountingBalancesheet({
start_at: '2024-01-01',
end_at: '2024-12-31'
});
const assets = balanceSheet.assets;
```
**Benefits of Using Individual Objects:**
- **Simpler API:** Each report type has its own dedicated endpoint
- **Better Type Safety:** TypeScript can better infer types for specific report objects
- **Clearer Intent:** Code is more explicit about which report type is being accessed
- **Easier Maintenance:** Individual objects are easier to extend and modify independently
---
## Deprecated Types/Interfaces
### `IAccountingProfitlossCategory` and `IAccountingProfitlossSubcategory`
**Status:** Deprecated types - These interfaces are deprecated in favor of `IAccountingProfitlossSection`
**Affected Files:**
- `src/models/UnifiedAccounting.ts`
- `src/models/UnifiedAccounting.joi.ts` (marked as `@deprecated`)
- `src/models/UnifiedAccounting.proto`
**Deprecated Types:**
```typescript
interface IAccountingProfitlossCategory {
name?: string; amount?: number; sub_items?: IAccountingProfitlossSubcategory[];}
interface IAccountingProfitlossSubcategory {
name?: string; amount?: number; transaction_ids?: string[];}
```
**Replacement:**
```typescript
interface IAccountingProfitlossSection {
section_type?: string; section_name?: string; total_amount?: number; accounts?: IAccountingProfitlossAccount[];}
interface IAccountingProfitlossAccount {
account_id?: string; account_name?: string; total_amount?: number; transaction_ids?: string[];}
```
**Migration Recommendation:**
- Update TypeScript type definitions to use `IAccountingProfitlossSection`
- Update code that creates or manipulates profit/loss categories to use sections instead
- The new structure provides better organization with accounts grouped under sections
---
## Summary by Category
| Category | Deprecated Fields Count | Primary Changes |
| ---------------------- | ----------------------- | -------------------------------------------------------------------------------------------- |
| Metadata | 2 | `key` → `slug`, `type` → `format` (affects 6 models) |
| Parent/Relationship | 4 | Various `parent_*_id` → `parent_id` |
| HRIS | 3 | `department`, `division`, `location` → `groups`/`locations` |
| Messaging | 4 | `channel_id`, `channel_ids`, `parent_message_id`, `root_message_id` → `channels`/`parent_id` |
| Accounting | 8 | Multiple field replacements and structural changes |
| ATS | 2 | `document_id` → `document_ids`, `departments` → `groups` |
| Ticketing | 1 | `category` → `category_id` |
| Query Parameters | 3 | Date filter and expand parameter updates |
| Webhooks | 1 | `sig` → `sig256` |
| **Deprecated Objects** | **2** | `IAccountingInvoice` with `type='BILL'`, `IAccountingReport` |
| **Deprecated Types** | **2** | `IAccountingProfitlossCategory`, `IAccountingProfitlossSubcategory` |
**Total Deprecated Fields:**
- 27 unique fields
- 2 deprecated objects
- 2 deprecated types across all categories
---
## General Migration Guidelines
### 1. **Backward Compatibility**
- Deprecated fields will still be supported until `JANUARY 7, 2026`
- Plan to migrate as soon as possible to avoid breaking changes in future API versions
### 2. **Testing**
- Test all migrations thoroughly in a development environment
- Verify that data reads and writes work correctly with the new fields
### 3. **Data Migration**
- For existing data using deprecated fields, consider:
- Running a one-time migration script to populate new fields from old fields
- Updating your application code to read from both old and new fields during transition
- Gradually migrating writes to use only new fields
### **4. Documentation**
- Update your internal documentation and code comments
- Notify your team about these changes
- Update API integration tests to use new field names
## Oracle HCM / Taleo OAuth Credentials Guide
URL: https://docs.unified.to/guides/oracle_hcm_taleo_oauth_credentials_guide
# Oracle HCM / Taleo OAuth Credentials Guide
------
_April 6, 2026_
To connect Oracle HCM / Taleo to Unified, you only need to collect these 3 values from Oracle:
- Client ID
- Client Secret
- Servername
You will enter those directly into Unified.
## **What To Enter In Unified**
When creating the Oracle HCM / Taleo connection, fill in:
- Client ID
- Client Secret
- Servername
**Example Servername**
## **How To Get These Values From Oracle**
**1. Find Your Servername**
Ask your Oracle administrator for your Oracle Fusion HCM host name.
It usually looks like:
`.fa..oraclecloud.com`
Example:
[`example.fa.us2.oraclecloud.com`](https://example.fa.us2.oraclecloud.com/)
This exact value is what you should enter into Unified as the Servername.
**2. Create A Confidential Application In Oracle**
1. Sign in to Oracle as an administrator
2. Open Integrated applications
3. Click Add application
4. Choose Confidential Application
5. Enter a name such as Unified Oracle HCM Integration
6. Continue through the setup
**3. Enable OAuth Client Access**
During application setup, make sure OAuth client access is enabled for the application.
For this Unified connection, Oracle should issue credentials for a confidential application that can request access tokens for the HCM / Taleo APIs.
Your Oracle administrator may need to:
- enable OAuth for the app
- allow the app to access the appropriate Oracle HCM / Taleo resources
- activate the application once setup is complete
**4. Copy The Credentials**
After the confidential application is created and activated, open the application details and copy:
- Client ID
- Client Secret
These are the exact values to enter into Unified.
## **Final Checklist**
Before creating the connection in Unified, make sure you have:
- Client ID
- Client Secret
- Servername
## **Oracle References**
- OAuth configuration: [https://docs.oracle.com/en/cloud/saas/applications-common/25d/oaext/configure-oauth.html](https://docs.oracle.com/en/cloud/saas/applications-common/25d/oaext/configure-oauth.html)
- HCM REST API: [https://docs.oracle.com/en/cloud/saas/human-resources/farws/index.html](https://docs.oracle.com/en/cloud/saas/human-resources/farws/index.html)
## Retrieval-Augmented Generation (RAG)
URL: https://docs.unified.to/guides/retrieval_augmented_generation_rag
# Retrieval-Augmented Generation (RAG)
------
_February 13, 2026_
_Last updated: June 2026_
This page explains how to implement a RAG pipeline using Unified.
RAG is an implementation pattern built on top of Unified APIs. Unified provides real-time data access and normalized objects. You are responsible for embeddings, vector storage, and retrieval.
At a high level, RAG with Unified follows this sequence:
1. Subscribe to webhooks.
2. Receive created or updated objects.
3. Retrieve full content from the source API.
4. Chunk the content.
5. Generate embeddings.
6. Store embeddings in your vector database.
7. On query, retrieve relevant chunks and generate a response.
The pattern is the same across categories.
## What RAG Means in Practice
RAG allows you to answer questions using customer data from connected SaaS platforms.
Instead of relying only on model training data, your application:
- Retrieves relevant customer records.
- Supplies those records as context.
- Generates a response grounded in that context.
Unified enables real-time retrieval directly from source APIs and supports event-driven synchronization.
- Normalized across providers.
- Fetched in real time.
- Consistent in schema.
Unified does not store embeddings or maintain a vector index.
## Step 1: Connect Data Sources
Authorize integrations using Embedded Auth.
Common RAG source categories include:
- File Storage
- Knowledge Management
- CRM
- Ticketing
- ATS
- Messaging
Each authorized integration returns a `connection_id`.
RAG pipelines should treat `connection_id` as the tenant boundary.
## Step 2: Subscribe to Object Updates
Create webhook subscriptions for the objects you want to index.
**Backfill on first subscription**
Set `include_all: true` when creating the webhook subscription. This delivers all existing records to your endpoint before transitioning to incremental updates. Pages are delivered with `type: INITIAL-PARTIAL`; the final page is tagged `type: INITIAL-COMPLETE`. After that, the same subscription delivers ongoing changes tagged `NATIVE` or `VIRTUAL` depending on whether the source integration supports native webhooks.
Your endpoint receives the same payload structure for backfill and incremental updates — no separate backfill handler is required.
**On created or updated events:**
- Your endpoint receives the event.
- You call the appropriate retrieve endpoint to fetch the latest version of the object.
- If the object contains a `download_url`, fetch the content from that URL.
List endpoints often return metadata but not full content. For RAG, you must explicitly retrieve full text before chunking.
Native webhooks deliver real-time updates.
Virtual webhooks provide the same interface using managed polling and may introduce short delays
Webhooks are recommended over polling for RAG ingestion.
## Step 3: Chunk and Embed
Large text (files, pages, tickets, resumes, notes, transcripts) should be split into chunks before embedding.
Each chunk should include stable metadata such as:
- `connection_id` — tenant boundary; required for all retrieval filters
- `object_type` — e.g. `crm_deal`, `ats_candidate`, `ticketing_ticket`
- `object_id` — source record ID; used to target re-embedding on update
- `updated_at` — timestamp of the source record's last update
- `is_latest` — boolean flag indicating whether this chunk is current; set to `false` when a record updates and new chunks are inserted. This field is not returned by Unified — you maintain it in your vector store schema.
You may also include additional normalized fields as metadata filters.
Unified normalizes objects across providers. All normalized fields returned by Unified can be stored as metadata in your vector index and used for filtering at retrieval time.
You generate embeddings using the embedding model of your choice. Unified may provide access to embedding models, but embeddings and indexes are stored in your infrastructure.
## Step 4: Store in a Vector Database
Store embeddings in your vector database (for example, Pinecone or pgvector).
Include metadata to support:
- Tenant isolation (`connection_id`)
- Object filtering (`object_type`)
- Update replacement (`updated_at`)
- Permission enforcement (see below)
Unified does not:
- Cache end-customer payloads
- Store embeddings
- Maintain a vector index
Unified does not persist customer payloads or maintain your embedding index.
## Step 5: Retrieve and Generate
When a user submits a query:
1. Embed the query.
2. Retrieve the top matching chunks from your vector database.
3. Filter by `connection_id` and any relevant metadata.
4. Pass retrieved context to your model.
5. Return the generated answer.
If required, include citations that map back to `object_id` or `web_url`.
Unified does not perform retrieval. Unified ensures the indexed data remains synchronized with source APIs.
## Permissions
File objects include explicit permissions metadata.
Other objects (CRM, ATS, ticketing, messaging) do not return user- or group-level permission structures. If row-level enforcement is required, implement that logic in your application layer before returning retrieved results
Always filter retrieval results by tenant and permission constraints before generation.
## Real-Time Behavior
For RAG pipelines, freshness depends on update propagation:
- Native webhooks deliver real-time updates.
- Virtual webhooks use managed polling and typically operate with short delays.
- Most list endpoints support pagination and incremental filtering via `updated_gte`
To keep embeddings current:
- Re-fetch objects on update events.
- Re-chunk and re-embed.
- Replace outdated vectors in your index.
## When to Use RAG
Use this pattern when you need to:
- Build enterprise search across connected platforms.
- Ground AI responses in customer documents or records.
- Enable resume or candidate search.
- Support contract or document Q&A.
- Build account-level CRM assistants.
The ingestion architecture is consistent across categories. Object models and content retrieval methods vary by API.
## Related APIs
RAG pipelines commonly use:
- [File Storage API](https://docs.unified.to/storage/overview)
- [Knowledge Management API](https://docs.unified.to/kms/overview)
- [CRM API](https://docs.unified.to/crm/overview)
- [Ticketing API](https://docs.unified.to/ticketing/overview)
- [ATS API](https://docs.unified.to/ats/overview)
- [Messaging API](https://docs.unified.to/messaging/overview)
Refer to category documentation for supported objects and webhook availability.
## Related Guides
- [How to build Enterprise Search using RAG](https://unified.to/blog/how_to_build_enterprise_search_using_rag)
- [ATS to Vector DB: How to Power Talent Intelligence with Real-Time Data](https://unified.to/blog/ats_to_vector_db_how_to_power_talent_intelligence_with_real_time_data)
- [How to Build an AI-Powered Meeting Recording Summarization](https://unified.to/blog/how_to_build_an_ai_powered_meeting_recording_summarization_with_unified)
- [How to Train AI with CRM Data using Unified's CRM API](https://unified.to/blog/how_to_train_ai_with_crm_data_using_unified_crm_api)
- [How to Train AI with HRIS Data with Unified's HR & Directory API](https://unified.to/blog/how_to_train_ai_with_hris_data_with_unified_hr_and_directory_api)
[→ Start your 30-day free trial](https://app.unified.to/login)
[→ Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)
## SalesForce (External Client Apps) on Multiple Organizations
URL: https://docs.unified.to/guides/salesforce_external_client_apps_on_multiple_organizations
# SalesForce (External Client Apps) on Multiple Organizations
------
_April 15, 2026_
This guide will walk you through the required steps to support using a Salesforce External Client App (ECA) across multiple organizations. When an ECA is created in Salesforce, it can only connect to users within the organization that created it. To allow the ECA to be used across multiple organizations, you must complete the required setup steps before it can connect to users outside the original organization. The customer's Salesforce admin does not need to give anyone their Salesforce credentials, they can run the second set of steps below.
> ⚠️ This setup resolves the error 'Cross-org OAuth flows are not supported for this external client app'. That error usually means the External Client App was created in your SourceOrg but has not yet been installed/deployed/approved in the customer's TargetOrg.
### Terminology
| Term | Meaning |
| ----------------------- | ----------------------------------------------------------------------- |
| SourceOrg | The Salesforce org where the External Client App was created |
| TargetOrg | The customer's Salesforce org where users are trying to connect |
| APP_API_NAME | API Name of the External Client App |
| OAUTH_SETTINGS_API_NAME | Metadata name of the OAuth settings attached to the External Client App |
1. The first thing you need to do is create an ECA within Salesforce. If you have already done this proceed to step 2, otherwise check out our [how to guide](https://unified.to/blog/how_to_register_a_salesforce_developer_app_and_get_oauth_2_credentials).

1. [Install SalesForce CLI](https://developer.salesforce.com/tools/salesforcecli)
2. Generate a SalesForce project
```shell
sf project generate --name unified-sf
```
3. Navigate into project
```shell
cd unified-sf
```
4. Login to your Source organization, this is the organization where you have created the External client app
```shell
sf org login web --alias SourceOrg
```
5. Retrieve the app metadata (Replace APP_API_NAME with your app name, which can be found at Setup → External Client Apps → open your app → settings → basic information → API Name
```shell
sf project retrieve start --metadata ExternalClientApplication:APP_API_NAME --target-org SourceOrg
sf project retrieve start --metadata ExtlClntAppOauthSettings --target-org SourceOrg
```
> ℹ️ After retrieval, look in `force-app/main/default/extlClntAppOauthSettings/` for a file like `salesforce-test-app_oauth.extlClntAppOauthSettings-meta.xml`. The part before `.extlClntAppOauthSettings-meta.xml` is your `OAUTH_SETTINGS_API_NAME`. It is usually NOT the same as `APP_API_NAME` (it often ends with `_oauth`), so confirm it from the retrieved file name instead of guessing.
6. Create a zip of the project folder to give to the customer who wants to install your app.
```shell
zip -r ../my_project.zip . --exclude "*/.git/*" --exclude "*/.sfdx/*"
```
Customer Steps (Customer Salesforce admin)
1. Customer opens the project folder
```javascript
cd my_project
```
1. [Install SalesForce CLI](https://developer.salesforce.com/tools/salesforcecli)
2. Customer admin logs into the TargetOrg
```javascript
sf org login web --alias TargetOrg
```
1. The customer will need to make some changes within their Salesforce to deploy the app. In the SalesForce Dev Hub > Settings > Setup > Dev Hub, enable both 'Enable Unlocked Packages and Second-Generation Managed Packages' and 'Enable Dev Hub'


1. Customer deploys the ECA app
```javascript
sf project deploy start --metadata ExternalClientApplication:APP_API_NAME --target-org TargetOrg
sf project deploy start --metadata ExtlClntAppOauthSettings:APP_API_NAME_oauth --target-org TargetOrg
```
> ℹ️ Replace `APP_API_NAME_oauth` above with the real `OAUTH_SETTINGS_API_NAME` from the retrieved file name (it may differ from `APP_API_NAME`).
Alternative: deploy both metadata types together:
```shell
sf project deploy start --metadata ExternalClientApplication --metadata ExtlClntAppOauthSettings --target-org TargetOrg
```
1. The customer can then verify that the app has been added to their organization by visiting Setup → External Client Apps → external client app manager where they should see your app installed.

1. Configure and approve access in the TargetOrg. The admin should review and configure:
- Permitted users
- Profiles / permission sets
- OAuth policies
- Refresh token policy
- IP restrictions, if applicable
- Admin-approved users, if the org requires admin approval
2. The customer can now create a connection to your app
Once the app is deployed and access is granted, the end user can retry the normal Salesforce connection flow through Unified and the cross-org OAuth error will be resolved.
## Long-term application-distribution recommendation
For repeated customer onboarding, the preferred long-term approach is to package the External Client App as a Salesforce package and share a package install link with customer Salesforce admins, instead of asking each admin to manually run metadata deployment commands.
1. You create the External Client App once in SourceOrg.
2. You package the app and OAuth settings.
3. You share the package install link with customer admins.
4. The customer Salesforce admin installs the package into TargetOrg.
5. The customer Salesforce admin approves/configures access.
6. End users connect normally through OAuth.
## SAML Single-Sign-On
URL: https://docs.unified.to/guides/saml_single_sign_on
# SAML Single-Sign-On
------
_September 28, 2025_
SAML (Security Assertion Markup Language) is an XML-based standard for exchanging authentication and authorization data between parties, particularly between an identity provider (IdP) and a service provider (SP).
[Unified.to](https://unified.to/) currently supports JumpCloud SAML. [Let us know](https://unified.to/contact) if you need another SAML identity provider.
Here's how SAML works:
## **SAML Authentication Flow**
**Key Components:**
1. **Identity Provider (IdP)** - The system that authenticates users (e.g., Active Directory, Okta, Azure AD)
2. **Service Provider (SP)** - The application the user wants to access (e.g., your Unified.to app)
3. **User** - The person trying to log in
4. **SAML Assertion** - XML document containing authentication/authorization information
**Typical SAML SSO Flow:**
1. User → SP: "I want to access the application"
2. SP → User: Redirect to IdP with SAML AuthnRequest
3. User → IdP: Login with credentials
4. IdP → User: Redirect back to SP with SAML Response/Assertion
**Detailed Steps:**
1. **User Access Request**
- User visits your application and clicks "SAML SSO" login
- Application generates a SAML Authentication Request (AuthnRequest)
2. **Redirect to Identity Provider**
- User is redirected to their organization's IdP
- AuthnRequest contains information about the SP and requested attributes
3. **User Authentication**
- User enters their organizational credentials
- IdP validates the credentials
4. **SAML Response Generation**
- IdP creates a SAML Response containing a SAML Assertion
- Assertion includes user identity, authentication method, session info, etc.
5. **Response Processing**
- IdP redirects user back to SP with the SAML Response
- SP validates the assertion signature and extracts user information
- User is logged into the application
**SAML Assertion Contents:**
- **Subject**: Who the user is (NameID, email, etc.)
- **Authentication Statement**: How they were authenticated
- **Attribute Statement**: Additional user attributes (roles, groups, etc.)
- **Conditions**: Validity period, audience restrictions
- **Signature**: Cryptographic proof of authenticity
**Common SAML Bindings:**
- **HTTP Redirect**: Data passed via URL parameters
- **HTTP POST**: Data posted in form fields
- **HTTP Artifact**: Reference token exchanged for full assertion
**Security Features:**
- **Digital Signatures**: Ensure assertions haven't been tampered with
- **Encryption**: Protect sensitive data in transit
- **Time-based Conditions**: Assertions expire after set time
- **Audience Restrictions**: Limit which SPs can use the assertion
**Configuration Requirements:**
**For Service Provider (your app):**
- SAML metadata XML with entity ID, ACS URL, certificate
- User attribute mapping (email, name, roles)
- Certificate for signature validation
**For Identity Provider:**
- SP metadata containing endpoints and certificate
- User attribute configuration
- SSO URL configuration
This is why SAML is popular for enterprise SSO - it provides secure, standardized authentication that integrates with existing corporate identity systems without requiring users to manage separate passwords for each application.
## **JumpCloud SAML Integration Setup**
**1. JumpCloud Configuration (Identity Provider Side)**
### **Step 1: Create SSO Application in JumpCloud**
1. Log into JumpCloud Admin Portal
2. Navigate to **SSO Applications**
3. Click **+ Add New Application**
4. Search for "Custom SAML Application" or "Custom App"
5. Configure the application:
- **Display Label**: "Unified.to" (or your app name)
- **IdP Entity ID**: https://sso.jumpcloud.com/saml2/unified-to (or your preferred ID)
- **SP Entity ID**: Your application's entity ID (e.g., https://api.unified.to/saml/metadata)
### **Step 2: Configure SAML Attributes**
In the JumpCloud SSO configuration:
- **ACS URL**: https://api.unified.to/saml/acs (your assertion consumer service endpoint)
- **Audience**: https://api.unified.to/saml/metadata
- **Name ID Format**: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- **Attribute Mappings**:
- email → https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
- firstName → https://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
- lastName → https://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
- username → https://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
### **Step 3: Get JumpCloud Metadata**
- Download the IdP metadata XML from JumpCloud
- Note the **SSO URL** (usually https://sso.jumpcloud.com/saml2/unified-to)
- Get the **X.509 Certificate** for signature validation
# Configure SAML on Unified
If you haven't registered an account, sign-in with any of our available Social or OAuth2 OIDC login options. Make sure to choose the relevant data region.
Then proceed to the workspace settings and choose [SAML](https://app.unified.to/settings/saml).
Select your Identity Provider. Either upload the SAML manifest XML file or input the SAML configuration settings from your identity provider.
You can choose to restrict sign-ins to just SAML for your workspace. All existing and invited workspace members will then need to sign-in with SAML.
## Scaling MCP Tools with Anthropic's (& OpenAI's) Defer Loading
URL: https://docs.unified.to/guides/scaling_mcp_tools_with_anthropic_and_openai_defer_loading
# Scaling MCP Tools with Anthropic's (& OpenAI's) Defer Loading
------
_December 25, 2025_
Learn how to use Anthropic's and OpenAI's `defer_loading` tool search features with Unified's MCP server to efficiently manage hundreds of tools while maintaining high accuracy and context efficiency.
When building AI applications with MCP servers, including the [Unified MCP server](https://www.notion.so/mcp/overview), you quickly encounter a critical challenge: most LLM models struggle with large numbers of tools.
While Unified can provide thousands of tools across different integrations, traditional approaches hit two key limitations:
- **Context window bloat**: Tool definitions consume massive portions of your context (50 tools ≈ 10-20K tokens)
- **Tool selection degradation**: An LLM's ability to correctly select tools degrades significantly beyond 30-50 tools
Anthropic's new **defer_loading** feature solves both first problems by dynamically discovering and loading tools on-demand instead of loading all tool definitions upfront.
## The Problem: Too Many Tools
The Unified MCP server can expose tools from any connected integration— CRM, ATS, HRIS, ticketing, storage, and more. A single connection might offer 50+ tools, and with multiple integrations, you could easily have 200+ available tools. Overall, the Unified MCP server currently support more than 22,000 tools.
Traditional approach problems:
- Loading 200 tool definitions uses 40,000-80,000 tokens
- The LLM API struggles to select the correct tool from such a large set
- You waste context on tools that won't be used in that conversation
## The Solution: Anthropic's Defer Loading Tool Search
Anthropic's `defer_loading` feature works with two tool search variants:
### Tool Search Variants
**1. Regex Tool Search** (`tool_search_tool_regex_20251119`)
- Claude constructs regex patterns to search for tools
- Best for exact matches and pattern-based discovery
- Fast and efficient for well-named tools
**2. BM25 Tool Search** (`tool_search_tool_bm25_20251119`)
- Claude uses natural language queries to search
- Better for semantic understanding
- More flexible for varied naming conventions
### How It Works
1. You include a tool search tool in your tools list
2. You provide all tool definitions with `defer_loading: true`
3. Claude sees only the tool search tool initially
4. When Claude needs additional tools, it searches dynamically
5. The API returns 3-5 most relevant tools
6. These are automatically expanded into full definitions
7. Claude selects and invokes the appropriate tool
## Implementation
To use the new `defer_loading` tool option, follow these instructions (found [here](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool#mcp-integration)) when calling Anthropic's ``/v1/messages`` API:
- Add `"mcp-client-2025-11-20"` to the `anthropic-beta` header:
`anthropic-beta: advanced-tool-use-2025-11-20,mcp-client-2025-11-20`
- Add the Unified MCP server's URL as usual
```json
"mcp_servers": [
{
"type": "url",
"name": "unified-salesforce-server",
"url": "https://mcp-api.unified.to?token=x&connection=y"
}
],
```
- Then add an additional `tools` array with configuration on which tools to defer:
```json
"tools": [
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
},
{
"type": "mcp_toolset",
"mcp_server_name": "unified-salesforce-server",
"default_config": {
"defer_loading": true
},
"configs": {
"list_crm_contacts": {
"defer_loading": false
}
}
}
],
```
- Use the `default_config.defer_loading: true` option to make all tools deferrable
- Use the `configs.{tool_id}.defer_loading: true/false` to set an individual tool to defer (or not)
Or, alternatively, you can call the Unified MCP `/tools` endpoint with parameters `?type=anthropic&defer_tools=all` and then feed that `tools` result into Anthropic's API.
## Best Practices
### 1. Keep Core Tools Non-Deferred
If you have 3-5 tools that are frequently used, keep them as non-deferred.
### 2. Use Permissions to Scope Tools
Reduce the tool catalog size by requesting only the permissions you need:
```typescript
// Instead of all tools across all categories
permissions: 'crm_contact_read,crm_contact_write,crm_deal_read'
// This is better than loading 200+ tools from all categories
```
### 3. Restrict Tools
Use the Unified MCP `tools` parameter to restrict which tools will be returned back to the LLM API.
This has a different effect than the `defer_tools` parameter as it doesn't return restricted tools to the LLM API at all, while deferring tools means that the LLM API knows about the tool, but doesnt process it until it is needed.
### 4. Monitor Token Usage
Track your token consumption to understand the benefits:
```typescript
console.log(`Input tokens: ${response.usage.input_tokens}`);
console.log(`Output tokens: ${response.usage.output_tokens}`);
console.log(`Tool search requests: ${response.usage.server_tool_use?.tool_search_requests}`);
```
### 5. Combine with Prompt Caching
Use [prompt caching](https://www.notion.so/guides/building_ai_applications_with_unified_and_langbase) with defer_loading for multi-turn conversations:
```typescript
messages.push({
role: "user",
content: "Now find their recent deals",
cache_control: { type: "ephemeral" }
});
```
## Tool Search Limits
Be aware of these limits:
- **Maximum tools**: 10,000 tools in your catalog
- **Search results**: Returns 3-5 most relevant tools per search
- **Pattern length**: Maximum 200 characters for regex patterns
- **Model support**: Claude Sonnet 4.5+ and Opus 4.5+ only
## When to Use Defer Loading
**Good use cases:**
- 20+ tools available from Unified connections
- Multiple integrations (CRM + ATS + HRIS + Storage)
- Building multi-tenant applications where each tenant has different integrations
- Context window is getting tight with tool definitions
- Tool selection accuracy is degrading
**When traditional tool calling might be better:**
- Less than 10 tools total
- All tools are frequently used in every request
- Very focused single-integration use case
## Real-World Example: Multi-Integration Assistant
Here's a practical example of a customer support assistant that accesses multiple integrations:
```typescript
async function createSupportAssistant(crm_connection_id, hris_connection_id, zendesk_connection_id) {
// Fetch tools from multiple Unified connections
const crmTools = await fetchUnifiedTools(crm_connection_id, 'crm_contact_read,crm_deal_read', { type: 'anthropic', defer_tools: 'list_crm_'});
const ticketingTools = await fetchUnifiedTools(zendesk_connection_id, 'ticketing_ticket_read,ticketing_ticket_write', { type: 'anthropic', defer_tools: 'list_crm_'});
const hrisTools = await fetchUnifiedTools(hris_connection_id, 'hris_employee_read', { type: 'anthropic', defer_tools: 'list_crm_'});
// Combine all tools with defer_loading
const tools = [
...crmTools,
...ticketingTools,
...hrisTools
];
// Total: 150+ tools, with all listX tools being deffered
return await anthropic.beta.messages.create({
model: "claude-sonnet-4-5-20250929",
betas: ["advanced-tool-use-2025-11-20"],
max_tokens: 4096,
messages: [{
role: "user",
content: "Customer John Doe from Acme Corp called about ticket #12345. Show me his account info, open tickets, and any recent deals."
}],
tools: tools
});
}
```
## Resources
- [Unified MCP Server Overview](https://docs.unified.to/mcp/overview)
- [MCP Installation & Usage](https://docs.unified.to/mcp/installation)
- [MCP Server Options](https://docs.unified.to/mcp/server-options)
- [Anthropic Tool Search Documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool)
- [Anthropic MCP Integration Guide](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool#mcp-integration)
## TLDR
Anthropic's `defer_loading` feature is a game-changer for building AI applications with Unified's MCP server. By the LLM API dynamically loading tools on-demand, you can:
- **Scale to hundreds of tools** across multiple integrations
- **Reduce context usage** by 80-90%
- **Improve tool selection accuracy** significantly
- **Build more capable AI assistants** that access diverse data sources
Start by adding the tool search tool and marking your Unified MCP tools as deferred. Monitor your token usage and tool selection accuracy to see the immediate benefits.
The combination of Unified's extensive integration network and Anthropic's intelligent tool search opens up possibilities for building truly comprehensive AI agents that can work across your entire SaaS ecosystem.
## Set Environments for your Unified.to Workspace
URL: https://docs.unified.to/guides/set_environments_for_your_unified_workspace
# Set Environments for your Unified.to Workspace
------
_June 7, 2023_
We're eager to share the release of environments, a new feature that makes it easier to test and customize your integrations before releasing them to production. For example, you can create environments for staging and production, which gives you more control over testing integrations with our Unified API.
[**Try it now**](https://app.unified.to/?utm_source=blog&utm_medium=blog&utm_campaign=environments&utm_content=environments_blog)
## What is an environment?
Environments are configurable spaces that allow you to specify different integration credentials for different circumstances. Determine distinct access and configuration rules for various scenarios, such as production, staging, and testing, which gives you more flexibility and control over managing your release processes.
Environments can be added under any plan in your Unified.to account's Workspace.

## Capabilities of environments
- **Integration testing:** Before releasing integrations to production, you can now test them in a different environment.
- **Multiple environments:** You can set up multiple environments such as production, staging, and others personalized to you and your team's needs.
- **Environment-specific integrations:** You can now decide which integrations are active in each environment.
- **Application credential assignment:** Choose which application credentials to use for each integration in each environment.
## Benefits of environments
- **Greater flexibility:** You can tailor your integrations to meet the unique needs of each environment including client specifications.
- **Reduced errors:** Testing integrations in a staging environment before moving them to production helps minimize mistakes and unexpected errors.
- **Increased productivity:** This feature streamlines the management of your workspace, reducing time spent on setup and adjustments.
## When to use multiple environments
Set multiple environments regardless of what plan you're on for different scenarios:
**1. Localhost**
_**Create a sandbox environment without disrupting your team's workflow or end-user experience**_
Example: Suppose Laura, a developer, is working on integrating a new batch of HR services. She creates her own personal authentication environment within Unified.to, complete with application credentials specific to her environment. This allows Laura to experiment freely without affecting her app's live features or her fellow developers' work.
**2. Testing or QA**
_**Try out new integrations and run tests before releasing them to production**_
Example: Once Laura has completed her initial development work, she simply copies the credentials to the testing environment. This environment has its own set of credentials and often uses mocked or dummy data for testing purposes. In this stage, Laura and her team run a battery of tests to ensure the new HR integrations work as expected and don't disrupt other functionalities within the app.
**3. Staging**
_**Mirror your production environment to finely-tune performance and perform final tests**_
Example: After the new HR integrations pass all tests in the testing environment, it's promoted to the staging environment. This environment is a mirror image of the production environment, complete with the same application credentials used in production. The HR integrations are tested under conditions that closely mimic the live application. This final testing phase ensures the new batch of integrations are ready for deployment in the production environment.
**4. Production**
_**Your live environment where your integrations run and are available to end-users**_
Example: This is the live environment where the application operates, interacts with real data, and serves actual users. It uses production application credentials. With the successful deployment of the new HR integrations, all users of the app can now benefit from the new integrations Laura has been working on.
## How to set new environments
Implement this new feature in seconds. Follow these steps:
**Setting up Environments**
1. Log in to or create a [Unified.to account](https://app.unified.to/?utm_source=blog&utm_medium=blog&utm_campaign=environments&utm_content=environments_blog)
2. Go to **Workspace Settings** and insert the name of your new environment, (e.g., 'Staging') and press enter
3. Once entered, your environment can be used for configuring individual integrations and generating an Embedded Directory
**Configuring Integrations**
1. Navigate to **Integrations** and click ACTIVATE on any integration, for example, Workday

2. From the dropdown menu, select the environment where you to want to add credentials, such OAuth Client ID and Secret

3. Input your credentials for Workday for the selected environment and click ACTIVATE

4. Repeat for any integration and environment
## Embed Directory
After enabling integrations for a specific environment, you can generate an Embedded Directory for that environment.
1. Select the desired authentication environment from the dropdown menu.

2. This will generate a script with a new environment parameter for you.

If you're using our API to pull a list of active integrations for your directory, you can pass the environment query parameter to retrieve the correct integrations.
## Building the ultimate Unified API solution for integrations
This new authentication environment feature is just one of the many ways we're making Unified.to work better for developers who want to build API-first integrations with industry-leading HR, Sales, Marketing, and Support apps. We're excited for you to try Authentication Environments and see the difference it makes in managing your integrations pre-launch.
Our team is here to support you every step of the way. Enjoy exploring Unified.to and let us know if you need a hand.
You can try setting Unified.to's Authentication Environments at [unified.to/get-started](https://app.unified.to/?utm_source=blog&utm_medium=blog&utm_campaign=environments&utm_content=environments_blog).
## Setting up a Slack Bot Connection with Unified.to
URL: https://docs.unified.to/guides/setting_up_a_slack_bot_connection_with_unified
# Setting up a Slack Bot Connection with Unified.to
------
_June 16, 2026_
> This guide walks you through connecting Slack Bot (slackbot) to Unified.to — including the Slack app settings you need for sending messages, interactive buttons, and receiving button-click events as Unified MessagingEvent webhooks.
---
## Overview
A Slack Bot integration has three parts:
| Part | Where | Purpose |
| -------------------- | ------------------------ | ----------------------------------------------------------------------------- |
| Slack app | api.slack.com | OAuth credentials, bot scopes, and request URLs Slack calls when events occur |
| Connection | Unified dashboard | Authorizes your Slack workspace and stores the bot token |
| Webhook subscription | Unified dashboard or API | Tells Unified where to deliver normalized MessagingEvent payloads |
You can use Unified's shared Slack OAuth app (fastest) or register your own Slack app if you need full control over the app configuration.
---
## Important: two different URLs
Slack and your application use different webhook URLs. Do not point Slack at the same URL you use as your Unified hook_url.
| URL | Configured in | Receives |
| ------------------------- | --------------------------------------------------------- | -------------------------------------------------------- |
| Unified workspace webhook | Slack → Event Subscriptions & Interactivity → Request URL | Raw Slack Events API and interactive payloads from Slack |
| Your hook_url | Unified → Webhooks → Create Webhook | Normalized Unified MessagingEvent objects |
Flow: Slack → Unified workspace webhook → Unified converts the event → POST to your hook_url.
Use your regional Unified API host and include your workspace ID, for example: https://api.unified.to/webhook/workspace/slackbot?workspace_id=YOUR_WORKSPACE_ID
EU and AU workspaces should use the corresponding regional API host (for example api-eu.unified.to or api-au.unified.to).
---
## Step 1 — Create a Slack app
1. Go to https://api.slack.com/apps → Create New App → From scratch.
2. Name the app and select the Slack workspace you want to connect.
3. Click Create App.
---
## Step 2 — Add OAuth scopes
Open OAuth & Permissions → Scopes → Bot Token Scopes and add the scopes for the objects you plan to use.
Minimum for sending messages, reading history, and receiving button clicks:
| Scope | Purpose |
| ------------------------------------------------------------- | ------------------------------------------- |
| chat:write | Send messages and messages with buttons |
| channels:read / groups:read / im:read / mpim:read | List channels and DMs |
| channels:history / groups:history / im:history / mpim:history | Read message history |
| users:read / users:read.email / users.profile:read | Resolve message authors and button clickers |
| reactions:read | Reaction events (optional) |
| files:read / files:write | Attachments (optional) |
> These mirror the messaging_message_write and messaging_event_read scope sets for the slackbot integration. Add additional scopes only if you use those objects.
---
## Step 3 — Set the OAuth redirect URL
1. Under OAuth & Permissions → Redirect URLs, click Add New Redirect URL.
2. Paste the Unified OAuth callback URL shown in the dashboard when you connect Slack (bot). It looks like https://api.unified.to/oauth/code (use your region's host if applicable).
3. Save URLs.
---
## Step 4 — Enable Interactivity
1. Go to Interactivity & Shortcuts and turn Interactivity ON.
2. Set Request URL to your Unified workspace webhook (not your hook_url): https://api.unified.to/webhook/workspace/slackbot?workspace_id=YOUR_WORKSPACE_ID
3. Save.
Unified receives Slack block_actions payloads here, converts button clicks into MessagingEvent objects with type BUTTON_CLICK, and forwards them to your hook_url.
---
## Step 5 — (Optional) Enable Event Subscriptions
Enable this if you also want message, reaction, or channel membership events (not just button clicks).
1. Go to Event Subscriptions and turn it ON.
2. Set Request URL to the same Unified workspace webhook URL as Step 4: https://api.unified.to/webhook/workspace/slackbot?workspace_id=YOUR_WORKSPACE_ID
3. Under Subscribe to bot events, add the events you need, for example message.channels, app_mention, reaction_added, member_joined_channel.
4. Save.
---
## Step 6 — Activate the integration, create a connection, and add a webhook
In the Unified dashboard (or via API), complete all three steps below.
1. Go to Integrations and activate Slack (bot) for your environment. If you use your own Slack app, enter your Client ID and Client Secret.
2. Create a connection for the Slack (bot) integration and complete the OAuth flow to install the app into your Slack workspace.
3. Create a webhook on that connection with object type messaging_event and event created. Set hook_url to your application endpoint (the URL where you want to receive Unified payloads).
Your hook_url is only configured on the Unified webhook subscription. Slack should always send events to the Unified workspace webhook from Steps 4–5.
---
## Receiving events
When a user clicks a button or triggers a subscribed Slack event, Unified POSTs a normalized payload to your hook_url. Button clicks arrive as MessagingEvent objects with type BUTTON_CLICK — not as raw Slack block_actions JSON.
See the Unified webhook documentation for payload structure, signatures, and verification.
---
## Troubleshooting
| Symptom | Likely cause |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Slack URL verification fails | Request URL is missing workspace_id, uses the wrong regional host, or Unified cannot be reached from Slack |
| Button clicks never reach my application | Interactivity Request URL is not set to the Unified workspace webhook, or no Unified webhook with hook_url was created |
| My endpoint receives raw Slack JSON | Slack Request URL was set to your hook_url instead of the Unified workspace webhook |
| invalid_auth or missing scopes | Re-install the Slack app after adding scopes (Slack requires re-consent) |
## Setting up OAuth 2 Credentials for Greenhouse's APIs
URL: https://docs.unified.to/guides/setting_up_oauth_2_credentials_for_greenhouse_apis
# Setting up OAuth 2 Credentials for Greenhouse's APIs
------
_May 27, 2024_
## Important — Read this first!
Unified.to has _two_ integrations name with the name 'Greenhouse':
1. The first one that appears in the **Integrations** page is for Greenhouse's [**Harvest API**](https://support.greenhouse.io/hc/en-us/articles/360029266032-Harvest-API-overview) **(v3)**, which uses OAuth2 code-flow to authorize your application. For most of the use-cases, you should use this integration.
2. The second one that appears in the **Integrations** page is for Greenhouse's [**Candidate Ingestion API**](https://developers.greenhouse.io/candidate-ingestion.html), which uses OAuth 2 client-credentials-flow to authorize your application.
# Greenhouse Harvest API (v3)
1. [Integration request form](https://www.greenhouse.com/integration-partner#apply-now) should be submitted by every new company. _Applications are reviewed by Greenhouse on the first Monday of every month._ **If a company is already an official Greenhouse partner with a signed agreement, they may proceed directly to step 3.**
2. After completing a review, Greenhouse will share API agreement with company
3. Once the agreement is signed, the company should follow the steps [here](https://harvestdocs.greenhouse.io/docs/harvest-partner-oauth) to have a client application created for their integration. They will provide Greenhouse with a redirect_uri (https://api.unified.to/oauth/code) that will be attached to their specific integration's client application. Greenhouse will provide the client credentials (client_id and client_secret)
4. The company will input their (2) credentials in [https://app.unified.to/integrations/greenhouse](https://app.unified.to/integrations/greenhousehttps://app.unified.to/integrations/greenhouse). Unified will handle all authorization:
1. Access authorization, token management, refreshes and storage ([instructions found here](https://harvestdocs.greenhouse.io/docs/harvest-partner-oauth))
2. Proxy requests on behalf of the originating partner to harvest v3 APIs
3. Secure access schemes to proxy APIs from originating partner to UAPI provider that ensure no cross contamination
5. The company will complete the remaining onboarding steps with Greenhouse.
# Greenhouse Ingestion API
## Submit information about your application to Greenhouse
Before you can make use of Unified.to's Embedded Authorization component to let your users authorize Greenhouse to access their accounts, you will need to provide Greenhouse with the following:
1. **Application Name**: The name of your application as it would appear in Greenhouse.
2. **Application URL**: The URL of your application.
3. **Callback URL**: After the end user authorizes your application to access their account, Greenhouse will redirect them to this URL. Provide this one:
`https://api.unified.to/oauth/code`
4. **Logo Image**: A 128 by 128 pixel image that Greenhouse will include in their permissions modal.
## Receive the Consumer Key and Consumer Secret provided by Greenhouse
When Greenhouse receives the information listed above, they will provide a set of values including these two:
- A **consumer key**, which uniquely identifies your application
- A **consumer secret**, which acts as proof of your application's identity
As its name implies, the consumer secret is confidential. For security purposes, Greenhouse will encrypt the consumer secret before emailing it to you (they'll explain how to decrypt it).
## Activate Greenhouse in Unified.to
With your application registered with Greenhouse and the OAuth 2 credentials in hand, you can now activate the Greenhouse integration in Unified.to.
In Unified.to, go to the [**Active Integrations**](https://app.unified.to/integrations) page, find the Greenhouse Ingestion API item (and _not_ the Greenhouse Harvest API one) and click on it:

On the page that appears, do the following:
1. Select **Your OAuth 2 credentials**.
2. Enter the consumer key value into the **OAuth 2 Client ID** text field.
3. Enter the consumer secret value into the **OAuth 2 Client Secret** text field.
4. Click the **Activate** button to save the changes and activate the Greenhouse integration.

## Trello Connection Guide in Unified
URL: https://docs.unified.to/guides/trello_connection_guide_in_unified
# Trello Connection Guide in Unified
------
_April 17, 2026_
### Overview
To create a Trello connection in Unified, you need to:
- Create a Trello app
- Copy its API key
- Allow Unified domains as **Allowed Origins**
---
### Steps
**1. Create a Trello App**
- Go to **Trello Power-Ups Admin**
- Create a new app
- Open the app details page
**2. Get API Key**
- Navigate to the **API Key** section
- Copy your Trello API key
**3. Configure in Unified**
- Go to:[https://app.unified.to/integrations/trello](https://app.unified.to/integrations/trello)
- Paste the API key into the required field
**4. Add Allowed Origins in Trello**

Add the following URLs in your Trello app settings:
- [https://app.unified.to](https://app.unified.to/)
- [https://auth.unified.to](https://auth.unified.to/)
- The Unified API domain shown during setup, for example:
- [https://api.unified.to](https://api.unified.to/)
- [https://api-eu.unified.to](https://api-eu.unified.to/)
- (or your region-specific API domain)
**5. Save & Complete Setup**
- Save the Trello app settings
- Return to Unified
- Complete the connection flow
---
### Notes
- Make sure the **regional API domain** matches what is shown in the Unified app during setup
- This can vary depending on your environment or region
---
### Troubleshooting
If the connection does not work:
- Confirm the API key is from the correct Trello app
- Ensure all **Allowed Origins** are added exactly (no typos or missing domains)
- Verify the correct **regional API domain** is included
- Retry the connection after saving changes
## Understanding OAuth2 Authorization flows
URL: https://docs.unified.to/guides/understanding_oauth2_authorization_flows
# Understanding OAuth2 Authorization flows
------
_October 17, 2025_
_Last updated: May 2026_
[OAuth 2.0](https://oauth.net/2/) is an industry-standard protocol for authorization that lets applications request limited access to a user's data on an API service without sharing passwords. This overview highlights the core flows that Unified supports. OAuth itself has two major versions (1.0 and 2.0), and OAuth 2.0 defines several flows. Overall, Unified supports 70+ OAuth 2.0 variations across integrations, spanning integration-specific scope models, token formats, and authorization requirements.
We won't cover OAuth v1, which is no longer widely used and has largely been superseded by OAuth 2.0, published as RFC 6749 in October 2012. RFC 6749 states it "replaces" OAuth 1.0, though OAuth 1.0 (RFC 5849) remains published and is not formally deprecated.[[1]](https://rfc-editor.org/info/rfc6749)[[2]](https://rfc-editor.org/info/rfc5849)
[OAuth 2.1](https://oauth.net/2.1/) is still in progress (as of Oct 2025). It consolidates best practices from OAuth 2.0—removing deprecated flows like implicit and password, and enforcing stronger security defaults such as PKCE—so it is not fully backwards-compatible with every 2.0 flow.
---
## OAuth 2.0 Authorization Code Flow
The OAuth 2.0 Authorization Code flow (`code` flow) is designed for applications that need to access a user's data in a third-party service (e.g., Google, Salesforce, HubSpot) with the user's explicit consent. It's the standard behind options you see all over the internet like "Sign in with Google." This flow is used for delegated authorization—API access on behalf of a user—and can also support authentication when combined with OpenID Connect.
**How it works:**
- Your app redirects the user to the integration's authorization page. This URL contains your app's `client ID` from that integration, plus integration-specific permission `scopes` and a `redirect URL`. It can also contain additional information such as `state`, `PKCE challenge`, and many more integration-specific parameters.
- The end-user logs in to that integration (if not already logged in) and is shown what permissions your app is requesting. They can then approve or reject that access request.
- If approved, the integration redirects back to your app with a temporary code.
- Your backend exchanges the code for an access token (and optionally a refresh token). This step uses both the client ID and client secret, securing the transaction. The access token will have an expiry, which requires the use of the refresh token to obtain new access tokens.
- Your app uses the access token in the `Authorization` header to call that integration's API on behalf of the user.
**Unified.to** [handles all aspects of this OAuth2 flow](https://docs.unified.to/unified/integration/Authorize_new_connection):
- creating the authorizing URL correctly
- unifying permission scopes (e.g. crm_contact_read, accounting_invoice_write, …)
- exchanging codes for access tokens
- automatically refreshing access tokens (and optionally refresh tokens)
- calling the end API with the access token in the correct header and format
This happens with our pre-built components, or directly through our Authorization API.
Our customers just input their application's OAuth2 client ID and client secret, obtained once from the integration. Unified also provides instructions on how to obtain those OAuth credentials.

Currently, Unified.to supports 176 integrations that use OAuth2 `code` flow.
## OAuth 2.0 OpenID Connect (OIDC) Flow
The [OpenID Connect](https://openid.net/developers/how-connect-works/) flow is built on top of OAuth 2.0 and is used primarily for user authentication (identity), often alongside OAuth 2.0 authorization for API access.
It returns an `id_token`—a JWT (JSON Web Token) containing verified user identity claims, such as a name and email—and can also provide a `/userinfo` API endpoint to obtain the verified identity. Depending on the integration and the scopes requested (for example, `offline_access`), it can also issue access and refresh tokens.
When you see a "Sign in with Google" button, that is usually an OpenID Connect flow.
**Unified.to** [hides this complexity in our Unified Authentication API](https://docs.unified.to/auth/login/Sign_in_a_user), supporting OIDC along with integrations that only support OAuth2 code flow with non-standard `User Info` API endpoints.
## OAuth 2.0 Client Credentials Flow
OAuth 2.0 `client credentials` flow is for server-to-server (machine-to-machine) authentication. No user is involved; your backend service authenticates as itself to access application-level or tenant-level data—not an individual user's data. The customer (or a tenant admin) obtains a client ID and client secret from their integration and provides it to your application.
**How it works:**
- The customer or tenant admin generates an OAuth2 client ID and client secret in their integration and gives it to your application. They usually set what permissions they're willing to grant to those credentials.
- Your backend sends the client ID and secret to the integration's token endpoint.
- The integration returns an access token.
- Your backend uses the token to call APIs.
With Unified.to, your end-user will be asked to fill out these credentials, plus any other required credentials to access that integration's API.

Currently, Unified.to supports 29 integrations that use OAuth2 `client_credentials` flow.
## API Token / Key / Username & Password Authentication
APIs can also be authenticated with an `API key`, `API token`, or even a `username and password`. The keys and tokens consist of either random characters or an encoded [JWT](https://www.jwt.io/) (JSON Web Token).
They can be sent in the `Authorization` header, in a custom header, or in the URL query as a parameter.
If an API uses username and password for authentication instead of an API key, that combination is sent in the `Authorization` header, base64-encoded as "username:password".
Because these credentials must be kept secret, they should only be used in backend environments. Using them in client-side code (e.g., a browser) would risk leaking them to the end-user.
Unified.to can ask the end-user for any number of API keys/tokens and additional required API credentials. Optionally, our customers can request this information from their end-users and [create a connection manually](https://docs.unified.to/unified/connection/Create_connection).

If you're interested in reading about more complexities with OAuth2, read our blog post: "[How We Normalize OAuth Across 460+ APIs at Unified.to](https://unified.to/blog/how_we_normalize_oauth_across_apis_at_unified)".
## Understanding Response Time Headers in the Unified API
URL: https://docs.unified.to/guides/understanding_response_time_headers_in_the_unified_api
# Understanding Response Time Headers in the Unified API
------
_July 13, 2026_
Every response from the Unified API includes custom HTTP headers that break down exactly where time was spent during your request. This gives you visibility into whether latency is coming from the third-party integration or from Unified's processing layer.
### The Headers
**`X-Response-Time`** — The total end-to-end time for your request, measured from when the Unified API received it to when the response was sent back.
**`X-EndApi-Response-Time`** — The time spent waiting on the third-party API (e.g., Salesforce, Slack, HubSpot). This is the network round-trip to the downstream service plus its processing time.
**`X-Unified-Response-Time`** — The time spent inside Unified's own code — authentication, data mapping, validation, and response formatting. Calculated as the total time minus the end API time.
### When They Appear
`X-Response-Time` is included on every response. The two breakdown headers (`X-Unified-Response-Time` and `X-EndApi-Response-Time`) only appear when your request involved a call to a third-party API. Requests that are handled entirely by Unified (such as validation errors or metadata lookups) will only include `X-Response-Time`.
### Example
```plain text
HTTP/1.1 200 OK
X-Response-Time: 340ms
X-Unified-Response-Time: 12ms
X-EndApi-Response-Time: 328ms
```
In this example, the third-party API took 328ms to respond, while Unified added just 12ms of overhead for auth, field mapping, and validation.
### How to Use This
- **Debugging slow requests** — If `X-EndApi-Response-Time` is high, the bottleneck is the third-party provider, not Unified. You may want to check the provider's status page or consider caching on your end.
- **Monitoring** — Log these headers alongside your own application metrics to build a complete picture of your API call latency.
- **Support** — When reaching out about performance concerns, include these header values to help pinpoint the issue faster.
## Unified Assessment API: Embed Assessments Inside ATS Platforms
URL: https://docs.unified.to/guides/unified_assessment_api_embed_assessments_inside_ats_platforms
# Unified Assessment API: Embed Assessments Inside ATS Platforms
------
_February 9, 2026_
Most assessment integrations rely on application status changes or external tools. Recruiters switch systems, and providers handle fragmented integration logic. Some ATS platforms now support embedded assessment listings, where recruiters select and order assessments directly inside the ATS.
Unlike traditional integrations that rely on polling or status changes, the Assessment API enables **real-time, recruiter-initiated assessment requests executed directly inside ATS platforms.**
## What is an Assessment API?
An assessment API allows assessment providers to integrate directly into ATS platforms so recruiters can request candidate assessments without leaving the ATS. Instead of triggering assessments based on status changes, recruiters select assessment packages inside the hiring process, and the request is routed to the provider in real time.
The Assessment API supports both **embedded ATS integrations and** including package management, order handling, and result submission.
## Why ATS-native assessment integrations matter
Embedded assessment integrations increase completion rates and reduce friction because recruiters take action at the point of decision. There is no context switching, and results are written back to the system where hiring decisions are made.
## Three ways to integrate assessments with Unified
**1. Embedded (Assessment API)**
- Recruiter selects assessment inside ATS
- Best UX, requires ATS support
**2. Triggered (ATS API)**
- Trigger on status change
- Works across all ATS
**3. Verification-style (Verification API)**
- ATS/HRIS calls provider directly
- No marketplace required
Most assessment providers use a combination of all three to balance coverage and user experience.
## Unified Assessment API
The Unified Assessment API allows assessment and background check providers to integrate directly with Applicant Tracking Systems (ATS) platforms that support embedded assessment listings.
This API enables recruiters to request assessments for candidates directly from within their ATS. The recruiter does not need to log into a separate assessment platform or use a separate system.
Instead, the assessment package is selected inside the ATS, and the request is routed to your platform through Unified.
This is different from triggering assessments based on status changes. It is a recruiter-initiated flow inside the ATS itself.
### Core objects in the Assessment API
- **Packages**: define assessment offerings, metadata, scoring, and configuration
- **Orders**: represent assessment requests, candidate context, and lifecycle state
- **Responses**: structured results including scores, attributes, and artifacts
Orders include rich candidate, job, and application context, allowing providers to tailor assessments and return structured results directly into the ATS.
### Real-time, read/write assessment workflows
The Assessment API supports both:
- Creating and managing assessment packages
- Receiving assessment orders in real time
- Updating orders with structured results and scores
All requests are **stateless and routed directly to the source system**—no caching, no sync jobs, no stored records.
### **Using assessment data in AI systems**
Because assessment results are structured and returned in real time, they can be used to:
- Generate candidate summaries and scoring insights
- Feed structured evaluation data into copilots or hiring assistants
- Trigger automated follow-ups or recommendations
- Train models on hiring outcomes and performance signals
## What you can build with the Assessment API
- Embedded assessment marketplaces inside ATS platforms
- Partner integrations with ATS vendors (Greenhouse, Ashby, Workable)
- Recruiter-driven assessment workflows
- Real-time candidate evaluation pipelines
- AI scoring and feedback workflows tied to ATS activity
### Embedded assessment marketplaces inside ATS
Some ATS platforms support native assessment marketplaces, where providers can list assessment packages directly inside the product.
The Assessment API enables this model by:
- Listing your packages inside the ATS
- Allowing recruiters to select assessments directly
- Sending orders in real time
- Returning results back into the same flow
## **How the Assessment API differs from other Unified APIs**
The **Unified Verification API** is used by ATS or HRIS platforms to call verification or assessment providers in a unified way.
In that flow:
- The ATS or HRIS platform initiates the request.
- They do not need a direct partnership with the assessment provider.
- The call is routed through Unified using the unified verification data models.
The Assessment API is different.
It is designed for assessment providers who want their assessment packages listed directly inside an ATS that supports embedded assessment integrations.
The integration is initiated by the recruiter inside the ATS, not by the assessment provider or backend workflow.
### How This Differs from the Unified ATS API
The **Unified ATS API** allows assessment providers to listen for job application changes and trigger an assessment when an application reaches a specific status.
That flow works across most ATS platforms supported by Unified and does not require a special assessment marketplace integration.
It is best suited for ATS platforms that do not support embedded assessment listings.
The new Unified Assessment API works in a different way:
- It connects directly to ATS platforms that have a dedicated 'assessment API.'
- Your assessment packages are listed inside the ATS.
- A recruiter manually selects a package for a candidate.
- The ATS sends an assessment order through Unified.
- You process the assessment.
- You update the order.
- The results are written back into the ATS.
The key difference:
The recruiter stays inside the ATS the entire time.
There is no external trigger based on status change. The recruiter explicitly orders the assessment.
### When Should You Use Each?
For assessment providers:
- Use the **Assessment API** when the ATS supports embedded assessment listings and you have a partnership with that ATS.
- Use the **ATS API** for all other ATS platforms.
Most providers will use both:
- Assessment API for supported ATS platforms.
- ATS API as the broad coverage fallback.
The Assessment API reduces recruiter friction but is limited to ATS platforms that expose this functionality.
### Who this is for
- Assessment providers (coding tests, behavioral, psychometric, etc.)
- Background check and verification providers
- AI-based candidate evaluation platforms
- Hiring infrastructure tools integrating into ATS ecosystems
## Why this matters for assessment providers
- Higher completion rates (recruiter intent is explicit)
- Less friction (no external tools)
- Stronger ATS partnerships
- Better UX for hiring teams
## Supported ATS Integrations
Currently, the Unified Assessment API supports the following ATS platforms:
- Ashby
- Cornerstone
- Greenhouse
- Recruitee
- TalentLyft
- Workable
Each of these has a dedicated assessment integration that is separate from the standard ATS integration.
Only ATS platforms that support embedded assessment listings can be supported through this API.
Expansion to additional supported ATS platforms is ongoing.
## Getting Started
### Step 1: Create a Connection
First, you need to manually create a connection for your assessment integration. This connection will be used to authenticate requests from the ATS.
When creating the connection, you must supply a **Partner API Key** in the `connection.auth.token` field that you generate yourself. You will then share this API key with your end-customer to configure the integration in their ATS.
Always check the authentication requirements for each assessment integration as some will require additional authentication information. For example, Ashby also requires a `Customer API Key` and a `Partner ID`.
**Example Connection Creation:**
```json
{
"integration_type": "greenhouseassessment",
"auth": {
"token": "YOUR_PARTNER_API_KEY"
},
}
```
### Step 2: Configure Webhooks
You also need to set up webhooks to receive assessment orders from the ATS systems.
### Required Webhook
Create a webhook with the following configuration:
- **object_type**: `assessment_order`
- **event**: `created`
This webhook will be triggered whenever a recruiter requests an assessment for a candidate in the ATS.
### Optional Webhook
Some ATS vendors (like Ashby) also send order cancellation events. You can optionally create an additional webhook:
- **object_type**: `assessment_order`
- **event**: `deleted`
This webhook will notify you when an assessment order is cancelled.
### Step 3: Provide Configuration to Your End-Customer
Once you've created the connection and webhook, provide the following information to your end-customer:
1. **Base URL**: `https://api.unified.to/assessments/{connection_id}`
- Replace `{connection_id}` with the actual connection ID from Step 1
2. **Partner API Key**: The token value you set in `connection.auth.token`
Your end-customer will then input these credentials into their ATS software to enable the integration.
## **How the assessment flow works**
Here's how the assessment flow works:
1. **Package Configuration**: You create assessment packages via the Unified API (w/ assessment packages API endpoints)
2. **ATS Lists Packages**: The ATS queries your available packages
3. **Recruiter Orders Assessment**: A recruiter selects a package and orders an assessment for a candidate
4. **Webhook Notification**: [Unified.to](https://unified.to/) sends you a webhook with the order details
5. **Process Assessment**: You send the assessment to the candidate and process their response
6. **Submit Results**: You submit the assessment results back to [Unified.to](https://unified.to/) (w/ assessment order update API endpoint)
7. **Results in ATS**: The results appear in the ATS for the recruiter to review
## API Endpoints
### Assessment Packages
Manage your available assessment packages:
- `POST /assessment/{connection_id}/package` - Create a new package
- `GET /assessment/{connection_id}/package` - List all packages
- `GET /assessment/{connection_id}/package/{id}` - Get a specific package
- `PUT /assessment/{connection_id}/package/{id}` - Update a package
- `DELETE /assessment/{connection_id}/package/{id}` - Delete a package
### Assessment Orders
Update assessment order results:
- `PUT /assessment/{connection_id}/order/{id}` - Update an order with results
## Webhook Payload
When an assessment order is created, you'll receive a webhook with an `AssessmentOrder` payload:
```json
{
"id": "123",
"package_id": "your_package_id",
"candidate": {
"candidate_id": "candidate_456",
"email": "candidate@example.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+1 123 456 7890"
},
"application": {
"id": "application_789"
},
"job": {
"id": "job_012",
"name": "Software Engineer",
},
"status": "OPEN"
}
```
## Submitting Results
Once a candidate completes an assessment, submit the results using the update order endpoint:
```bash
PUT /assessment/{connection_id}/order/{order_id}
```
**Example Request:**
```json
{
"status": "COMPLETED",
"score": 85,
"max_score": 100,
"result_url": "",
"completed_at": "2024-01-15T10:30:00Z",
"attributes": [
{
"type": "TEXT",
"label": "Overall Assessment",
"value": "Strong candidate"
}
]
}
```
The results will then be written back to the ATS system, where recruiters can view them directly.
## Best Practices
Unlike traditional integrations that rely on polling or status changes, the Assessment API enables real-time, recruiter-initiated workflows directly within ATS platforms.
1. **Secure Your API Key**: Treat your Partner API Key as sensitive information. Only share it with trusted end-customers.
2. **Handle Webhooks Reliably**: Implement retry logic and idempotency checks in your webhook handler to ensure you don't miss or duplicate orders.
3. **Validate Webhook Data**: Always validate the webhook payload to ensure it contains the expected candidate and job information before processing.
4. **Monitor Order Status**: Track the status of assessment orders and handle cancellations appropriately when the `deleted` event webhook is received.
## Support
For questions or issues with the Unified Assessment API, please contact our support team or refer to the [Unified.to Documentation](https://docs.unified.to/).
What is an assessment API for recruiting platforms?
An assessment API allows providers to integrate directly with ATS platforms so recruiters can request, manage, and review candidate assessments without leaving the ATS. It enables real-time order handling and structured results returned to the same system.
How do assessment providers integrate with ATS platforms?
Assessment providers integrate with ATS platforms either through embedded assessment APIs or through status-based triggers. Embedded integrations allow recruiters to select and order assessments directly inside the ATS, while triggered integrations operate based on application state changes.
## Unified's MCP Server
URL: https://docs.unified.to/guides/unified_mcp_server
# Unified's MCP Server
------
_May 24, 2025_
Unified has launched an MCP server that connects any Unified connection to LLM (Large Language Model) providers supporting the newest MCP protocols. The available MCP `tools` will be determined by the integration's feature support and the connection's requested permissions.
All MCP tool calls are executed live against the source API. Unified does not cache or store end-customer data, and access is governed by scoped permissions and tool allow-listing.
## Use-cases:
- **End-Customer Conversation with LLMs**: Allows end-customers to interact with an LLM using permissioned tools backed by the data they've explicitly authorized through a Unified connection.
- **Debugging End-Customers' Connections**: Allows your product and support teams to assist your end-customers by debugging their Unified connections. Please make sure that you observe all privacy policies, both yours and your regions's, since end-customer data may be made available to the LLM based on the permissions and tools you explicitly configure. Unified provides options to restrict tools, scope permissions, and remove sensitive fields from responses.
MCP is a new protocol and it is moving fast. We expect more LLM & agent clients to support its newer Streamable HTTP transport protocol. We also expect that the MCP protocol will continue to expand quickly. Stay tuned as we also keep up.
## Sample queries:
- List candidates for job X
- List deals for contact with email address joe@foo.com
- update application 12345 with a status of HIRED
- summarize the accounting balance sheet report
- find the vacation policy by listing the pages in Notion
- post a message in Slack summarizing this deal
- create an invoice for customer X
- schedule an interview and notify the candidate
More information can be found at [https://docs.unified.to/mcp](https://docs.unified.to/mcp)
## Unlock real-time data with virtual webhooks
URL: https://docs.unified.to/guides/unlock_real_time_data_with_virtual_webhooks
# Unlock real-time data with virtual webhooks
------
_February 13, 2025_
Product teams building real-time applications face a common challenge: most third-party APIs don't offer webhooks (In fact, our [State of SaaS APIs report](https://unified.to/blog/the_state_of_saas_apis_2024) found only 11% of APIs have built-in support for webhooks). This leads to inefficient polling, manually handling rate limits, and writing custom logic just to keep data fresh.
But what if you could control webhook updates while reducing API costs and complexity?
That's exactly what Unified.to's [virtual webhooks](https://unified.to/blog/introducing_powerful_enhancements_to_webhooks) enable. In this article, we'll review:
- How virtual webhooks work
- Why they help control API costs by managing request frequency
- How they eliminate the need for custom rate-limit handling systems
- Why virtual webhooks are the fastest way to implement real-time updates
## Why use virtual webhooks?

Pairing Unified.to's virtual webhooks with your product's integration strategy means:
- **Webhooks for nearly every integration** – Get real-time updates even from APIs that don't natively support webhooks, all through a unified API.
- **Cost-efficient API usage** – Unlike native webhooks, which can be unpredictable in frequency and costly, virtual webhooks control API call frequency, reducing overages and optimizing usage.
- **One unified schema** – Whether the source supports native webhooks or requires polling, data is normalized into a consistent format—no custom polling logic needed.
- **Optimized sync performance** – Intelligent update detection, seamless and automatic rate-limit handling, and configurable polling intervals (e.g., every minute, every 30 minutes, every hour).
- **Faster time-to-market** – Virtual webhooks eliminate the need to build custom data collection.
- **Historical data access** – Instantly access past records when creating a webhook subscription, eliminating the need for separate API calls.
- **Built-in observability** – Monitor webhook activity with logs, audit trails, and health tracking.
- **Event-driven AI and automation** – Trigger AI workflows and automation in response to real-time data changes.
Instead of dealing with a patchwork of webhook, polling, and batch sync solutions, Unified.to's virtual webhooks create a consistent, scalable way to access real-time data across your integrations.
## How virtual webhooks work
Webhooks provide a push-based way to receive data updates, eliminating the need for polling. But most APIs don't support them, which forces developers to either:
1. Poll the API (which wastes API calls and delays updates), or
2. Manually mix polling and webhooks (adding complexity and inconsistencies).
Unified.to's virtual webhooks solve this by mimicking native webhooks, even when the source API doesn't provide them:
- We monitor your customers' connections on a scheduled interval that you configure (e.g., as frequently as every minute) and detect changes automatically
- We push updates to your webhook endpoint, just like a native webhook would
- You subscribe and manage webhooks the same way you would with a real webhook
No polling logic. No wasted API calls. Just real-time updates, everywhere.
[Explore documentation](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
## How virtual webhooks compare to traditional webhooks and polling
Most third-party APIs don't offer native webhooks, forcing teams to choose between batch syncs, polling, or manual workarounds.
| **Feature** | **Unified's Virtual + Native Webhooks** | **Native Webhooks** | **Polling** |
| ----------------------------------------------- | --------------------------------------- | ------------------- | ---------------------- |
| Works even if API doesn't support webhooks | ✅ Yes | ❌ No | ✅ Yes, but inefficient |
| Real-time event updates | ✅ Yes | ✅ Yes | ❌ No, delayed |
| API rate limit friendly | ✅ Yes | ✅ Yes | ❌ No, high API usage |
| Historical data retrieval | ✅ Yes | ❌ No | ❌ No |
| Fine-grained sync control | ✅ Yes (adjust polling frequency) | ❌ No | ✅ Yes, but manual |
| Built-in error handling & retries | ✅ Yes | ❌ Varies by API | ❌ No |
| Webhook observability (logs, health monitoring) | ✅ Yes | ❌ Varies by API | ❌ No |
Virtual webhooks provide all the benefits of native webhooks, even when APIs don't support them, while eliminating the inefficiencies of polling.
## Product use cases
Let's look at how developers and product teams can leverage virtual webhooks in real-world applications.
**AI-powered customer support**
Virtual webhooks can update AI models in real-time when new support tickets are created. Instead of polling a help desk API every few minutes, an AI assistant can instantly respond to customer inquiries or escalate issues.
[Unified Ticketing API](https://unified.to/ticketing)
[Build an AI Support Bot](https://unified.to/blog/how_to_build_a_discord_support_bot_with_unified_and_langbase)
**Automated financial reporting**
Fintech and accounting applications rely on up-to-date financial data to generate reports, sync transactions, and reconcile accounts. However, many accounting platforms lack real-time webhook support, meaning transaction updates often rely on batch syncs or polling.
With Unified.to's virtual webhooks, accounting software can:
- Get notified instantly when new transactions, invoices, or payments are recorded
- Trigger automatic reconciliation workflows instead of waiting for scheduled syncs
[Unified Accounting API](https://unified.to/accounting)
**Recruitment automation**
Hiring teams often integrate with multiple ATS platforms—some of which lack real-time event support. Virtual webhooks enable AI-powered candidate screening to kick in the moment a new application is received, ensuring faster and more efficient hiring workflows.
[Unified ATS API](https://unified.to/ats)
[How HeroHunt added 20 integrations in 1 week](https://unified.to/blog/how_herohunt_saved_ten_months_of_engineering_time_with_unified)
## How much polling actually costs
Polling isn't just inefficient—it's expensive.
Example:
- Polling every 5 minutes on 1000 accounts = 288,000 API requests per day
- Polling every 1 minute on 1000 accounts = 1.4 million API requests per day
- With virtual webhooks, API requests are dramatically reduced, as data is only fetched when changes occur. In low-activity accounts, this can mean near-zero requests, while high-activity accounts still see significant reductions compared to constant polling.
Instead of burning API quota on polling, virtual webhooks ensure data flows only when necessary, dramatically reducing costs and improving responsiveness.
## What happens if you don't use virtual webhooks?
- You could hit API rate limits while continuously polling for updates
- You could rely on batch syncs, leading to outdated or incomplete data
- You could spend months building and maintaining custom webhook fallbacks for every integration
Or… you could just use Unified.to's virtual webhooks to handle rate limits and get real-time updates across all your integrations.
## Start using Unified.to's virtual webhooks
Ready to stop polling and start working with real-time data everywhere? Here's how to get started:
- [Start a free trial](https://app.unified.to/login) to access virtual webhooks
- [Join our Discord](https://discord.gg/85z7HF7JbD) to connect with our team and get support
- [Review our documentation](https://docs.unified.to/guides/how_to_create_and_configure_webhooks) for step-by-step guidance on setting up webhooks
- [See our changelog](https://unified.to/changelog) for virtual webhook enhancements
## Use Unified to sign in your users into your application
URL: https://docs.unified.to/guides/use_unified_to_sign_in_your_users_into_your_application
# Use Unified to sign in your users into your application
------
_August 31, 2023_
Before your users can utilize your application and any of Unified.to's integrations, they need to sign in to your application.
Luckily, we've simplified user authentication through our Unified Authentication API. Just follow these steps.
## 1. Activate Authentication integrations
1.1 Click on the 'Integrations' menu, and then on the 'Authentication Integrations' option.

1.2 Activate the integrations that you would like to use to sign-in your users into your application by clicking on each desired integration.

1.3 If available, you can chose to use Unified's branding and name on the authentication page that your users see with signing-in to your application. We don't recommend this for production applications, but this is a great option to test the authentication feature.
1.4 If you want to add your own branding to the authentication pages, then you will need to first obtain your own OAuth2 application credentials from the specific integration vendors (eg. Google). For help on creating your own OAuth2 application, please see [Generating OAuth2 Credentials](https://unified.to/blog/start_here_how_to_generate_oauth2_credentials) or click on the 'Get your own OAuth2 credentials' link. Once you have that, enter your client ID and client secret that you obtained from the integration vendor.

1.5 Click on the 'Activate' button to enable this integration.
1.6 Repeat for all of the integrations that you want to use to authenticate your users.
## 2. Display sign-in links to your users
Now that you have a list of Authentication connections, you can incorporate them into your application.
You have two options:
1. Remember the integration types (ie. google, discord, workday) and create links that point to our authentication API endpoint:
`https//api.unified.to/unified/integration/login/{workspace_id}/{integration_type}?redirect=true`. You can style that link however you like.
2. Call the our API that returns a list of integrations; `https//api.unified.to/unified/integration/workspace/{workspace_id}?summary=true&active=true&categories=auth`. This will return all activated Authentication integrations and each will have information that you can use to style your Sign-in links;
```json
[
{
logo_url: "https://localhost:8000/docs/images/google.png",
name: "Google",
type: "google"
}
]
```
The Sign-in URL can have the following optional parameters:
`redirect` - `true` to redirect the user or if empty, will return the URL as string in its response
`success_redirect` - the URL that the user will be redirected to once they have successfully authenticated with that integration vendor
`failure_redirect` - the URL that the user will be redirected to if there is an error or other issue preventing the user from being authenticated by the integration vendor
`state` - a string that will be sent back to the success_redirect. You can use this to remember a user ID or other identifier.
1. An alternative to building your own Sign-in page is to use our Embedded Sign-in widget. Click on the Settings menu and then on the Embedded Sign-in option.

Copy the CSS and code and insert it into your own application.
## Step 3: Verify a sign-in attempt
Once the user clicks on the authentication button/link, they will be redirect to the integration vendor's authentication page.

btw: it is important to note that the authentication pages are not Unified's pages but that of the integration vendor. Unified never sees the user's login credentials including their password.
Once the user successfully signs-in, they are redirected back to your application (or to the location of the `success_redirect` URL parameter from step 2). A `jwt` parameter will be appended to that URL.
The `jwt` is a base64 encoded [JWT](https://jwt.io/) and is signed with your Workspace Secret. Verify the JWT with your workspace secret on your server (NOT in your browser as the workspace secret is not public).
Example code to verify a JWT on a NodeJS server:
```javascript
try {
let result = JWT.verify( req.payload.jwt, workspace.secret )
} catch (err) {
console.error(err);
}
```
The decoded JWT will contain a name and emails field:
```json
{
name: "Jane Smith",
emails: ["jane@foo.com", "jsmith89@gmail.com"]
}
```
Use the user's email to log them into your application as it is verified by the integration.
## Workday vs Workday Legacy — Why Unified.to Supports Both
URL: https://docs.unified.to/guides/workday_vs_workday_legacy_why_unified_supports_both
# Workday vs Workday Legacy — Why Unified.to Supports Both
------
_July 22, 2025_
Workday provides **two main API integration formats**:
1. **Workday REST API** — their latest modern API, but still under active development.
2. **Workday SOAP API** (often referred to as _Legacy_) — the older but mature API that still covers some critical endpoints not yet available in REST.
To give you maximum flexibility and to ensure you can access **all the data and actions you need**, Unified.to supports **both** APIs.
## Why You Might Need Both
While Workday is steadily improving their REST API, there are still a few important gaps. Here's why using both makes sense today:
1. **Application Creation** — Workday's REST API does not currently support creating job applications. This is only available through the Legacy SOAP API.
2. **Candidate Lists** — Retrieving a full list of candidates is not yet supported in REST but is fully available through the Legacy SOAP API.
3. **Create Job** — Similar to applications, creating jobs still relies on the Legacy API.
4. **Attachments** — The REST API **does** support listing and downloading attachments — which the Legacy SOAP API does not.
5. **Future Coverage** — Workday continues to expand REST API features. As new endpoints become available, Unified.to will adopt them immediately so you always have the best option.
| | WorkDay REST | WorkDay Legacy |
| ------------------ | --------------------------- | ------------------------- |
| ATS Activity | List | |
| ATS Application | | List, Get, Create, Update |
| ATS Candidate | Create, Get | List, Get, Create, Update |
| ATS Document | List, Get, Create, Download | |
| ATS Interview | List, Get | List, Get, Create, Update |
| ATS Job | List, Get | List, Get, Create, Update |
| ATS Scorecard | List, Get | List, Get, Create, Update |
| HR Employee | List, Get | List, Get, Create, Update |
| HR Group | List, Get | |
| HR Timeoff | List, Get | |
| Accounting Contact | List, Get | |
| Accounting Invoice | List, Get | |
## Frequently Asked Questions & Answers
**Question: Do I have to send XML requests for Workday Legacy?**
**Answer:** No! You always send JSON requests to Unified.to. We handle the conversion to XML behind the scenes for the SOAP API.
**Question: Can I use both the REST and Legacy APIs at the same time?**
**A****nswer:** Absolutely. Unified.to lets you mix and match endpoints. For example, you can fetch attachments with REST and create applications with Legacy — all through the same unified model.
**Question: Is the Workday Legacy connector going to be deprecated?**
**Answer:** Not anytime soon. Many Workday customers still rely on SOAP endpoints for critical operations. Unified.to will continue supporting it as long as Workday does.
**Question: Is authentication different for Legacy vs REST?**
**Answer:** Yes — Workday REST typically uses OAuth 2.0, while the Legacy SOAP API uses basic authentication (aka WS-Security). Unified.to handles this complexity for you.
**Question: Are the data models the same between REST and Legacy?**
**Answer:** Yes — Unified.to normalizes the data, so you get a consistent unified schema, no matter which Workday API is behind the scenes.
**Question: Can I switch from Legacy to REST later?**
**Answer:** Yes. As Workday expands REST support, you can migrate endpoints over time. Our team will help you adapt with minimal changes on your side.
**Question: Will using both cost more?**
**Answer:** No — there's no extra fee for using both. You only pay for your overall Unified.to usage.
## Conclusion
Workday is gradually moving more features to its modern REST API — but some critical workflows still require the mature SOAP-based Legacy API. To ensure you don't lose any capability, Unified.to supports **both** — giving you the best of both worlds.
**Our recommendation:** Use the REST API wherever possible for modern features and attachments, and rely on the Legacy API to cover any gaps (like creating jobs or applications). Unified.to handles all the complexity behind the scenes, so you can build faster with no surprises.
**Have more questions?**
Reach out to our support team — we're here to help you get the best out of both Workday APIs!
## Working with hierarchical tree data in Storage, Messaging, and KMS APIs
URL: https://docs.unified.to/guides/working_with_hierarchical_tree_data_in_storage_messaging_and_kms_apis
# Working with hierarchical tree data in Storage, Messaging, and KMS APIs
------
_November 7, 2024_
This guide explains how to retrieve and traverse hierarchical data structures (i.e., trees) when working with APIs that support parent-child relationships.
## Overview
Many APIs organize their data in tree-like structures, where records can have parent-child relationships with each other.
1. The **File Storage API** represents both folders and files as a single object type, making tree traversal straightforward. While you can think of folders as the 'containers' for other files and folders, the API treats them all as the same object type.
2. The **Messaging API** and **Tasks API** use a simple parent-child relationship where each object type can contain children of the next type (e.g., projects contain tasks, tasks contain comments, channels contain messages). Objects may also have parents of the same type.
3. Some **KMS APIs** have more complex relationships where pages and spaces can reference each other in multiple directions e.g., a mesh. While most KMS platforms use a simple tree structure (like Confluence), some (like Notion) support this more flexible mesh-like organization.
This guide will show you how to efficiently traverse these hierarchical structures to retrieve all the data you need.
## Before you begin
This guide assumes you have:
- Basic understanding of [tree data structures](https://en.wikipedia.org/wiki/Tree_(abstract_data_type))
## Understanding parent-child relationships
When an API endpoint supports a `parent_id` parameter, it typically indicates that the data is organized in a hierarchical structure. To fully traverse this structure, you'll need to:
1. Get the top-level entities (parent nodes)
2. For each parent, retrieve its children by passing the parent's ID
3. Recursively repeat this process for any children that can also be parents
4. For KMS Page, you can use its `has_children` field to determine if there are children pages to query. If `has_children` is equal `false` , then no children pages exist. Some integrations di not return this information, so if this field is `undefined` or missing, then you should treat it as a `true`
### Example: Messaging platforms
Let's look at how this works with Discord:
1. Discord servers (also called "guilds") are top-level parents
2. Channels exist within servers as children
3. Messages are also related to channels through their `parent_id`
4. To get all channels:
- First retrieve all guilds (servers)
- Then get channels for each guild using the guild's ID as the `parent_channel_id`
```javascript
async function getAllChannels(connectionId) {
// Get all top-level guilds first
const guilds = await sdk.messaging.listMessagingChannels({
connectionId,
});
let allChannels = [];
// For each guild, get its channels
for (const guild of guilds.data) {
const channels = await sdk.messaging.listMessagingChannels({
connectionId,
parentChannelId: guild.id,
});
allChannels = allChannels.concat(channels.data);
}
return allChannels;
}
```
**API reference:** [Messaging API](https://docs.unified.to/messaging/overview)
### Example: File storage systems
File storage systems are another common example of hierarchical data. Here's how to recursively traverse a file system to get all files:
```javascript
async function getAllFiles(connectionId, parentId) {
const response = await sdk.storage.listStorageFiles({
connectionId,
parentId,
});
if (!response.data) {
return [];
}
let allFiles = [];
for (const item of response.data) {
if (item.type === 'FOLDER') {
// Recursively get files from subfolders
const subfolderFiles = await getAllFiles(connectionId, item.id);
allFiles = allFiles.concat(subfolderFiles);
} else {
allFiles.push(item);
}
}
return allFiles;
}
```
**API reference:** [File](https://docs.unified.to/storage/overview)[ ](https://docs.unified.to/storage/overview)[Storage API](https://docs.unified.to/storage/overview)
## Best practices
When working with hierarchical data:
1. **Implement pagination**: Some nodes might have many children. Use the `limit` and `offset` parameters to handle large datasets.
```javascript
async function getAllChannelsWithPagination(connectionId, parentId = undefined) {
let allChannels = [];
let offset = 0;
const limit = 100;
while (true) {
const response = await sdk.messaging.listMessagingChannels({
connectionId,
parentChannelId: parentId,
limit,
offset,
});
if (!response.data || response.data.length === 0) {
break;
}
allChannels = allChannels.concat(response.data);
offset += limit;
}
return allChannels;
}
```
1. **Handle rate limits**: When recursively fetching data, you might hit API rate limits. Implement backoff strategies and respect the provider's limits.
2. **Cache parent IDs**: If you need to traverse the same structure multiple times, consider caching the parent-child relationships to reduce API calls.
## Integration-specific gotchas
### Working with SharePoint
SharePoint implements a distinct three-level hierarchy that requires special attention:
1. **Sites** (top level)
2. **Drives** (within sites)
3. **Folders/Files** (within drives)
To work with SharePoint's file system:
1. First retrieve the site ID (top-most parent)
2. Use the site ID to list drives
3. Use the drive ID to create or access folders and files
**Important notes:**
- You cannot create files or folders directly at the site level - you must first select a drive
- Write operations are only supported for folders and files, not for sites or drives
- Pagination is not supported for sites and drives listings
## See also
- [How to use the Unified File Storage API](https://docs.unified.to/guides/how_to_use_the_unified_file_storage_api#how-to-use-the-unified-file-storage-api)
---
# Tutorials
## Build a simple Javascript app that calls the Unified.to API
URL: https://docs.unified.to/tutorials/build-a-simple-javascript-app
# Build a simple Javascript app that calls the Unified.to API
In this tutorial, you’ll build a simple Javascript application that calls the Unified API to list candidates from Lever, an applicant tracking system (ATS). It assumes you have basic knowledge of:
- HTML
- Javascript
- Web development
By the end of this tutorial, you’ll be able to:
1. Activate integrations in Unified.to
2. Create connections to third-party API providers
3. Embed Unified.to's Authorization component to display integrations in your app
4. Call the Unified API and display the results
## Background
[Unified.to](http://unified.to/) is a platform that provides a unified (i.e. single) API for integrating various software applications. In this tutorial, we'll connect to Lever, an Applicant Tracking System (ATS) used by recruiters and talent teams. An ATS like Lever helps companies manage their hiring process by organizing candidate information, job postings, and application workflows.
However, oftentimes app developers will want to integrate with multiple vendors. Integrating directly with multiple platforms can be challenging due to differences in APIs and data models. This is where [Unified.to](http://unified.to/) comes in. It provides a standardized way to interact with multiple platforms through a unified API. This means you can write code once and integrate with multiple systems without having to learn each individual API.
## Prerequisites
Before we begin, make sure you have:
1. [Signed up](https://unified.to/) for a Unified.to account
2. Completed our [Get Started](https://app.unified.to/start) guide in the app (optional, but recommended)
3. Downloaded a code editor of your choice, like VS Code
4. Installed NPM and Node.js on your system
## Project setup
We've prepared a simple JavaScript and HTML application for you to build upon, which can find [here](https://github.com/unified-to/unified-ats-tutorial). This will serve as the foundation for our tutorial and we recommend that you download it and follow along. Run the commands below:
```js [shell]
// inside the folder where you want to download the repo
git clone git@github.com:unified-to/unified-javascript-tutorial.git
cd unified-javascript-tutorial
npm i
```
The app should now be available on `localhost:1234`.
This repository contains a basic structure for a single-page application that we'll develop with Unified.to. Take a moment to familiarize yourself with the files we’ll be working with:
- `index.html`: The main HTML file for our application.
- `app.js`: Where we'll add our JavaScript code. It contains placeholders and predefined variables for your convenience.
- `.env`: Where you’ll add sensitive information, like your API token.
Open the folder in your preferred code editor.
## Step 1: Retrieve your workspace ID and API token from Unified.to
You can find these values in your developer account under [_Settings > API Keys_](https://app.unified.to/settings/api). Record your Workspace ID and API Token in the `.env` file.
```js [.env]
UNIFIED_WORKSPACE_ID = your_workspace_id_here;
UNIFIED_API_KEY = your_api_token_here;
```
Now that you've updated `.env`, we can start our app. Run the following from the command line:
```js [shell]
npm run start
```
You can view your app on `localhost:1234`.
## Step 2: Activate an integration in the Sandbox environment
The Sandbox environment is where you can activate, explore, and try out integrations in a safe and non-destructive environment. We're going to call the ATS endpoint by the end of this tutorial, so let's activate an ATS integration.
### Check that you are in the Sandbox environment
1. Navigate to [_Integrations_](https://app.unified.to/integrations)
2. Look for the environment selector near the top of the page. Click on it and select 'Sandbox'. Any subsequent actions you take will happen in the sandbox environment.
### Activate Lever
1. On the same page (Active Integrations), search for 'Lever' by typing it into the search box.
2. Click on the Lever card to open its details page.
3. Leave the default settings as-is. Notice that since we’re in the Sandbox environment, the credentials are pre-filled with mock values. Lever uses OAuth 2 by default for authorization.
4. At the bottom of the page, click 'Activate’.
The Lever integration is now activated in your Sandbox environment. This allows you to make test API calls and get a feel for the Unified API without needing to connect to a real Lever account.
## Step 3: Add the Authorization embedded component to your app
The Authorization embedded component displays a list of your activated integrations. Your customers can use it to authorize access to third-party vendors for your app. We'll dynamically inject the widget script into the DOM using JavaScript, which allows us to use the environment variables we defined earlier.
1. Open `app.js` and add the following code:
```js [app.js]
function embedAuthorizationComponent() {
const script = document.createElement('script');
script.src = `https://api.unified.to/docs/unified.js?wid=${UNIFIED_WORKSPACE_ID}&did=unified_widget&env=Sandbox`;
script.async = true;
script.onload = () => {
console.log('Unified.to component script loaded successfully');
};
script.onerror = () => {
console.error('Failed to load Unified.to component script');
};
// The script will search the DOM for a div with the id "unified_widget"
document.body.appendChild(script);
}
// Inject the widget on page load
document.addEventListener('DOMContentLoaded', () => {
embedAuthorizationComponent();
});
```
2. In `index.html`, add the following under `` e.g:
```html [index.html]
```
Now, when your application loads, it will dynamically inject the [Unified.to](http://unified.to/) widget into the DOM. Refresh the page, you should see the widget will appear displaying a list of activated integrations.
## Step 4: Handle and store the connection ID
When a user clicks on the Lever integration in the Unified.to widget, a new page will open where they will be asked to grant access to Lever before being redirected back to your app.
The redirect URL contains important parameters, including `id` , which represents the Connection ID. We need to store this ID for making subsequent calls to the Unified API.
Add the following code to `app.js` to handle the connection process:
```js [app.js]
function handleConnectionCallback() {
const urlParams = new URLSearchParams(window.location.search);
const connectionId = urlParams.get('id');
if (connectionId) {
console.log('New connection created:', connectionId);
// Store the connectionId for later use
localStorage.setItem('unifiedConnectionId', connectionId);
} else {
console.log('No connection ID found in URL parameters');
}
}
// Execute the handler when the page loads
window.addEventListener('load', handleConnectionCallback);
```
**Warning:** Storing the Connection ID in `localStorage` is used here for simplicity and learning purposes only. In a production environment, you should securely store this information server-side, associated with the user's account. Refer to our guide on [How to Associate a Connection ID with your End-User](https://docs.unified.to/guides/how_to_associate_a_connection_id_with_your_end_user) for best practices.
## Step 5: Create and verify the connection
Let’s walk through creating a connection to confirm that it works.
### Create the Connection
1. In the widget, click on the Lever integration.
2. **Authorization Flow**:
- A new page will open with a disclaimer that you are on the Tester plan.
- Click **Continue** to grant access to Lever. Note: The authorization flow is simplified in the Sandbox environment. In Production, your user will be shown the scopes you are requesting and asked to grant you access.
3. After granting access, you'll be automatically redirected back to your app.
### Verify the Connection
Let's verify that the connection was created successfully:
1. **Check the URL**:
- Look at the URL in your browser's address bar.
- You should see a parameter `id` in the URL. This is your Connection ID.
- For example, the URL might look like: `http://localhost:1234?id=abc123...`
2. **Console Output**:
- Open your browser's developer tools.
- Go to the Console tab.
- You should see a message similar to: `New connection created: abc123...`
- This confirms that our `handleConnectionCallback` function has captured the Connection ID.
3. **Verify Local Storage** (Optional):
- In the developer tools, go to the Application tab.
- Under Storage, select Local Storage and your domain.
- You should see an item named `unifiedConnectionId` with the value matching the ID from the URL.
## Step 6: Set up API call to the Unified ATS Endpoint
In this step, we'll implement the API call to fetch candidate data from the Unified ATS endpoint.
### Implement the API call function
In `app.js`, add the following function to fetch ATS candidate data from the ATS endpoint:
```js [app.js]
async function fetchATSCandidates(connectionId) {
const options = {
method: 'GET',
url: `https://api.unified.to/ats/${connectionId}/candidate`,
headers: {
authorization: `bearer ${UNIFIED_API_KEY}`,
},
params: {
limit: 20,
offset: 0,
},
};
try {
const response = await axios.request(options);
return response.data;
} catch (error) {
console.error('Error fetching ATS candidates:', error);
return null;
}
}
```
**API Reference:** [List all candidates](/ats/candidate/List_all_candidates)
Notice that we don't need to specify `Sandbox` as an environment parameter in the API call - the connection we created is part of the Sandbox environment, so any data that comes back from it will be synthetic.
### Fetch and display candidates in the UI
In `index.html`, add the following under `` e.g:
```html [index.html]
```
### Implement the click handler
Add the following code to your `app.js` file to handle the button click and display the results:
```js [app.js]
function displayCandidates(candidates) {
const candidatesList = document.getElementById('candidates-list');
candidatesList.innerHTML = '
`;
candidatesList.appendChild(candidateElement);
});
}
document.getElementById('fetchCandidatesBtn').addEventListener('click', async () => {
const connectionId = localStorage.getItem('unifiedConnectionId');
if (!connectionId) {
console.error('No connection ID found. Please connect to Lever first.');
return;
}
const candidates = await fetchATSCandidates(connectionId);
if (candidates) {
displayCandidates(candidates);
} else {
console.log('No candidates found or error occurred');
document.getElementById('candidatesList').innerHTML =
'
No candidates found or an error occurred.
';
}
});
```
## Step 7: Call the Unified API
Now, let's test our application to ensure everything is working correctly.
Click **Fetch Candidates**. You should see a list of candidates appear on your screen. Since we're working in the Sandbox environment, these will be mock candidates generated by Unified.to.

**Troubleshooting**: If you don't see any candidates or encounter an error, check the browser console for any error messages or verify that your Unified API Key is correctly set in the `.env` file.
## Summary
Congratulations! You've built a Javascript app that can call the Unified.to API and fetch candidates from Lever. In this tutorial, you saw how to:
- Activate integrations in Unified.to
- Create connections by granting access to third-party vendors
- Add the [Unified.to](http://unified.to/) Embedded Widget to your app
- Make API calls to our unified API
## Next Steps
From here, you can start adding integrations from [Unified.to](http://unified.to/) into your own project. The following resources can help you on your way:
- Use one of our [SDKs](/reference/sdks)
- Get familiar with [webhooks](/concepts/webhooks)
- See how to customize the auth flow with our [Customize your authorization flow with the Unified API](/tutorials/customize-auth-flow) tutorial
Happy building!
## Customize your authorization flow with the Unified API
URL: https://docs.unified.to/tutorials/customize-auth-flow
# Customize your authorization flow with the Unified API
In this tutorial, you'll build a Javascript app that asks users to grant access to their third-party accounts with the Unified API. Instead of using the Authorization embedded component as in the [starter tutorial](/tutorials/build-a-simple-javascript-app), we'll implement this authorization flow from scratch. This work flow is intended for experienced developers who want more control over the authorization process or need to customize the user experience.
By the end of this tutorial, you'll be able to:
1. Fetch and display a list of available integrations from Unified.to
2. Create an authorization URL for a selected integration
3. Handle the authorization callback and create a connection
4. Use the connection to fetch data from an API provider
## Prerequisites
Before we begin, make sure you have:
1. [Signed up](https://unified.to/) for a Unified.to account.
2. Completed our [Get Started](https://app.unified.to/start) guide in the app.
3. Downloaded a code editor of your choice, like VS Code.
4. Installed NPM and Node.js on your system.
## Project setup
We'll use the same project structure as the previous tutorial. Start by cloning the repository and installing dependencies:
```js [shell]
git clone git@github.com:unified-to/unified-javascript-tutorial.git
cd unified-javascript-tutorial
npm i
```
The app should now be available on `localhost:1234`. Open the folder in your preferred code editor.
## Step 1: Retrieve your workspace ID and API token
1. Log in to your Unified.to account
2. Go to [Settings > API Keys](https://app.unified.to/settings/api)
3. Copy your Workspace ID and API Token
4. Open the `.env` file in your project and add these values:
```js [.env]
UNIFIED_WORKSPACE_ID = your_workspace_id_here;
UNIFIED_API_KEY = your_api_token_here;
```
Now that you've updated `.env`, we can start our app. Run the following from the command line:
```js [shell]
npm run start
```
You can view your app on `localhost:1234`.
## Step 2: Activate an integration in the Sandbox environment
_If you've already activated some ATS integrations in the Sandbox environment, skip ahead to step 3._
The Sandbox environment is where you can activate, explore, and try out integrations in a safe and non-destructive environment. We're going to call the ATS endpoint by the end of this tutorial, so let's activate an ATS integration.
### Check that you are in the Sandbox environment
1. Navigate to [_Integrations_](https://app.unified.to/integrations)
2. Look for the environment dropdown near the top of the page. Click on the dropdown that contains 'ENV' and select 'ENV: SANDBOX'. Any subsequent actions you take will happen in the Sandbox environment.
### Activate Lever
1. On the same page (Active Integrations), search for 'Lever' by typing it into the search box.
2. Click on the Lever card to open its details page.
3. Leave the default settings as-is. Notice that since we’re in the Sandbox environment, the credentials are pre-filled with mock values. Lever uses OAuth 2 by default for authorization.
4. At the bottom of the page, click 'Activate’.
The Lever integration is now activated in your Sandbox environment. This allows you to make test API calls and get a feel for the Unified API without needing to connect to a real Lever account.
## Step 3: Fetch and display available integrations
Time to get coding! First, let's write a function to fetch all of the available and activated integrations from your workspace. Add the following to `app.js`:
```js [app.js]
async function fetchIntegrations() {
try {
const response = await axios.get('https://api.unified.to/unified/integration/workspace', {
params: {
workspace_id: UNIFIED_WORKSPACE_ID,
env: 'Sandbox',
categories: 'ats',
},
headers: {
Authorization: `Bearer ${UNIFIED_API_KEY}`,
},
});
return response.data;
} catch (error) {
console.error('Error fetching integrations:', error);
return [];
}
}
```
**API reference**: [Get all integrations](/unified/integration/Returns_all_integrations)
### Render integrations in the UI
We also need to display the integrations in the UI. We'll do this programatically by passing the response from the above function to `displayIntegrations()`. When the app loads, we'll fetch the integrations and then render them on the page. Add the following to `app.js`:
```js [app.js]
function displayIntegrations(integrations) {
const integrationsListElement = document.getElementById('integrations-list');
integrations.forEach((integration) => {
const listItem = document.createElement('div');
listItem.innerHTML = `
`;
integrationsListElement.appendChild(listItem);
});
}
document.addEventListener('DOMContentLoaded', async () => {
const integrations = await fetchIntegrations();
displayIntegrations(integrations);
});
```
Now, let's update the view by creating a container to hold the integrations. In `index.html`, add the following under `` e.g:
```html [index.html]
Available Integrations
```
At this point, you should see a list of integrations displayed on the page (if you're just starting out, then the list may only contain one item, but that's OK!)
## Step 4: Create an authorization URL
Now that we're displaying our integrations to our users, let's ask them to grant us access to their third-party accounts when they click on an integration. A few things will happen:
- When a user clicks on an integration, they will be redirected to the authorization page for that integration.
- The user will be asked to authorize your app with the third-party provider.
- After granting access to your app, a connection will be created. The user will be redirected to your `success_redirect` URL.
Add the following to `app.js`:
```js [app.js]
function createAuthLink(integrationType) {
const baseUrl = 'https://api.unified.to/unified/integration/auth';
const params = new URLSearchParams({
redirect: '1',
env: 'Sandbox',
success_redirect: window.location.href,
failure_redirect: window.location.href,
scopes: 'ats_candidate_read',
state: 'abc-123-def-456',
});
return `${baseUrl}/${UNIFIED_WORKSPACE_ID}/${integrationType}?${params.toString()}`;
}
```
**API reference**: [authorize new connection via authorization URL](https://docs.unified.to/unified/integration/Authorize_new_connection)
### Breaking down the authorization URL API call
Let's review the structure of the API call we made to create an authorization URL:
`https://api.unified.to/unified/integration/auth/{workspace_id}/{integration_type}?{query_parameters}`
- `workspace_id`: Your Unified.to workspace ID
- `integration_type`: The name of the integration (e.g., 'lever')
- Query parameters:
- `redirect=1`: Indicates that this is a redirect request. The server will return a `302` status code and redirect the user. We recommend that you use this method as it is more secure and robust.
- `env=Sandbox`: Specifies the environment (use 'Sandbox' for testing)
- `success_redirect`: URL to redirect after successful authorization
- `failure_redirect:` URL to redirect after failed authorization
- `scopes`: Comma-separated list of required scopes (e.g., 'ats_candidate_read'). It's a best practice to always include the scopes you need.
- `state`: Data that you want to send back to your success URL in the URL parameters. A common use case is to put your user ID here so that after they have completed the auth flow, you can use the user ID to associate them with the connection you just made.
### Update the display logic for integrations
We need to update the `displayIntegrations()` function we created earlier. Replace it with the following:
```js [app.js]
function displayIntegrations(integrations) {
const integrationsListElement = document.getElementById('integrations-list');
integrations.forEach((integration) => {
const listItem = document.createElement('div');
const authLink = createAuthLink(integration.integration_type);
listItem.innerHTML = `
${integration.integration_type}
`;
integrationsListElement.appendChild(listItem);
});
}
```
**API reference**: [Create an authorization URL](/unified/integration/Authorize_new_connection) (a.k.a. create a connection indirectly).
When you click on one of your integrations now, you'll be taken to an authorization page. In the next step, we'll handle what happens after an integration is authorized.
## Step 5: Handle the authorization callback
After a user successfully authorizes your application, they'll be redirected to your app with some new parameters in the URL. One of these parameters is `id`, which represents the ID of the newly created connection. We will need to store this connection ID in order to use it in future API calls.
Create a function to handle the connection ID:
```js [app.js]
function handleAuthCallback(connectionId) {
localStorage.setItem('unifiedConnectionId', connectionId);
// Clear the URL parameters
window.history.replaceState({}, document.title, window.location.pathname);
}
```
Update the `DOMContentLoaded` event listener to call this function when the page is loaded and a connection ID is present i.e. when the user is redirected to your app.
```js [app.js]
// Add the following to the `DOMContentLoaded` event listener
const urlParams = new URLSearchParams(window.location.search);
const connectionId = urlParams.get('id');
if (connectionId) {
handleAuthCallback(connectionId);
}
```
**Note**: In a "real" app, you should store the connection ID in your own database.
## Step 6: Use the connection to call the Unified API
Let's update our code to fetch candidates using the connection we just created. (Fun fact: From this point onwards, the code you're seeing is the same as the code from the starter tutorial).
```js [app.js]
async function fetchATSCandidates(connectionId) {
const options = {
method: 'GET',
url: `https://api.unified.to/ats/${connectionId}/candidate`,
headers: {
authorization: `bearer ${UNIFIED_API_KEY}`,
},
params: {
limit: 20,
offset: 0,
},
};
try {
const response = await axios.request(options);
return response.data;
} catch (error) {
console.error('Error fetching ATS candidates:', error);
return null;
}
}
```
**API Reference:** [List all candidates](/ats/candidate/List_all_candidates)
Notice that we don't need to specify `Sandbox` as an environment parameter in the API call - the connection we created is part of the Sandbox environment, so any data that comes back from it will be synthetic.
Finally, let's display the candidates in the UI. Add the following code to your app:
```js [app.js]
function displayCandidates(candidates) {
const candidatesList = document.getElementById('candidates-list');
candidatesList.innerHTML = '
`;
candidatesList.appendChild(candidateElement);
});
}
document.getElementById('fetchCandidatesBtn').addEventListener('click', async () => {
const connectionId = localStorage.getItem('unifiedConnectionId');
if (!connectionId) {
console.error('No connection ID found. Please connect to Lever first.');
return;
}
const candidates = await fetchATSCandidates(connectionId);
if (candidates) {
displayCandidates(candidates);
} else {
console.log('No candidates found or error occurred');
document.getElementById('candidatesList').innerHTML =
'
No candidates found or an error occurred.
';
}
});
```
In `index.html`, add the following lines under `` e.g:
```html [index.html]
```
With all the pieces in place, you can now give this auth flow a try. Click on an integration, authorize your app, and then click on **Fetch candidates** to see what happens.
## Summary
Congratulations! You've successfully built your own custom authorization flow to creates connections with the Unified API. This tutorial demonstrated how to:
1. Fetch and display available integrations
2. Redirect users to an authorization page for a selected integration
3. Handle the auth callback and create a connection
4. Use the connection to fetch and display data from a third-party API provider
By implementing this authorization flow yourself, you have more control over the user experience and can customize it to fit your application's needs.
Happy building!
---
# API Reference
## Data Types
URL: https://docs.unified.to/reference/datatypes
# Data Types
| Type | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| string | a string |
| number | either an integer or double/float |
| array | a list / array of one data type |
| boolean | true or false |
| object | any object structure |
| date | Dates/Timestamps use UTC time and are formatted as ISO 8601 strings (without milliseconds). eg. `2015-04-16T09:14:57Z` |
| ID | All object IDs are 24 byte hexadecimal strings that are created by our MongoDB servers and represent a [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/). |
| undefined | some values could be `undefined` if no information is available for that field |
## Working with Custom & Original Fields
URL: https://docs.unified.to/reference/fields
# Working with Custom & Original Fields
When integrating with platforms and SaaS apps, you often need control over which fields to receive or send. This guide explains how to work with both standard and custom fields.
## Requesting Specific Fields
By default, API calls return all available fields for an object. However, you can optimize your requests by specifying only the fields you need using the `fields` parameter:
Some upstream APIs also expose **slow fields** that require additional API calls per record (for example, Greenhouse application `offers` or `raw.offers`). These can be excluded or included by default at the workspace level and overridden per request using `fields`. For details, see [Slow fields](/reference/slow-fields).
```
GET /crm/{connection_id}?/contact?fields=id,name,emails
```
## Accessing Original Data (Raw Fields)
### Reading Original Data
Sometimes you need access to the original, unmodified data from an integration (for example, to access platform-specific fields). To receive this data:
1. Include `raw` in your `fields` parameter
2. The response will include a `raw` object containing the original data from the source
```
GET /crm/{connection_id}?/contact?fields=id,name,emails,raw
```
## Writing Original Data
When creating or updating records, you can include integration-specific data that isn't part of our unified data model:
```
{
"name": "John Doe",
"raw": {
"custom_integration_field": "value"
}
}
```
## Accessing the original ID of the object
Usually, Unified.to will return the object's ID in the `id` field.
But sometimes, Unified.to is required to transform that original ID with additional information. If you require the original object's ID (for example, using our Passthrough API), it will be available in the `raw.__id` field.
## Working with Custom Original Fields
Some CRM platforms (like HubSpot and Salesforce) require specific fields to be explicitly included in the API call i.e., they won't be returned if you only added `raw` to the request parameters. If your app requires specific fields in this way ("custom fields"):
1. Include `raw` in your fields parameter
2. Add the specific custom fields you need with the `raw.` prefix
For example, to request custom fields named `leadFunnelStage` and `customerSegment`:
```
GET /crm/{connection_id}?/contact?fields=id,name,raw,raw.leadFunnelStage,raw.customerSegment
```
## Mock API Server
URL: https://docs.unified.to/reference/mock
# Mock API Server
Unified.to has a "mock server" that uses pre-generated synthentic data that emulates the data available in any of our integrations.
## How to call the mock server
You can call any REST API endpoint with a `X-Mock: 1` header. Make sure to have an empty `Authorization` header and use URL IDs and payload data that are properly formatted (e.g. ID=5de520f96e439b002043d8dc).
## Sandbox environment
You can also use the "Sandbox" environment when making API calls as that will also use the Mock API Server.
`?env=Sandbox`
## Pagination
URL: https://docs.unified.to/reference/pagination
# Pagination
All integrations use the same pagination parameters. But not all integrations support all of the pagination parameters. Please see the Feature Support tab in the integration's page in app.unified.to.
## Limit
By default, most list endpoints return a maximum of 100 records per page. You can change the number of records on a per-request basis by passing a `limit` parameter (in the request URL parameters for REST).
Example: `limit=50`
However, you can't exceed 100 records per page on most endpoints.
You will know that there aren't any more records, when the number of returned results is less than your requested `limit`.
## Offset
When the response exceeds the requested results, you can paginate through the records by padding a `offset` parameter that specifies the position of the first result. This parameter is zero-based regardless of integration's paging mechanism.
## Filtering with Updated Since, Start, or End
You can filter the list by specifying the minimum updated date `updated_gte`. This parameter returns lists that have their updated date at of greater than the specific value.
For some integrations, you can also use `start_gte` and `end_le` to filter results for that timeframe.
All dates are in a `YYYY-MM-DDTHH:MM:SSZ` format. You can leave off the time and the timezone if you like. The default timezone is `UTC`.
## Filtering with Query
You can filter the list by specifying a `query` parameter. The search will be integration-specific, but will only be used to filter by `email` or `name`. eg. `query=John` `query=john@example.com`.
If you require additional filtering, you must synchronize your customer's data into your own database where you would then be able to filter on any field.
Unified.to STRONGLY recommends that you synchronize data if you plan on doing queries. It will be much faster, you will be able to query using whatever criteria you want, and the queries will be consistent and not integration-specific. We recommend using our webhooks for synchronization.
## Sorting & Ordering
You can sort the results the `sort` parameter. Valid options include `name`, `updated_at`, and `created_at`. You can order order the results with the `order` parameter which only has two options: `asc` and `desc`.
## Rate limits
URL: https://docs.unified.to/reference/rate_limits
# Rate limits
Rate limiting is an important aspect of working with APIs that helps maintain system stability and ensure fair usage across all users. This guide explains how rate limiting works at Unified.to and how it affects your API usage.
## Background
Historically, rate limiting has been a necessary part of API design to:
- Prevent server overload from too many simultaneous requests
- Ensure fair resource distribution among all users
- Protect APIs from abuse or unintended high-volume usage
In Unified.to's case, we act as a real-time intermediary between your application and various API platforms. While Unified.to has its own set of rate limits, we also need to respect and enforce the limits set by each API platform to maintain stable integrations.
## How are rate limits determined at Unified.to?
Rate limits at Unified.to are determined by two factors:
1. **SaaS platform limits**: Each underlying platform (like HubSpot, Salesforce, etc.) has their own rate limits that determine how many API calls you can make within a given time period. These are the primary limiting factor as we must respect these limitations to maintain stable integrations.
2. **Unified.to limits**: We also implement our own rate limits to ensure fair usage across all users. However, these are generally more generous than the platform-specific limits. Our rate-limit error will have a message of "Too many requests to Unified.to".
You can find the specific rate limits for each integration under the **Feature Support** tab on https://app.unified.to/. When you hit a rate limit, you'll receive a 429 (Too Many Requests) response.
## Handle rate limits yourself
When making direct API calls, you'll need to implement your own rate limit handling strategy. The most common approach is to use a backoff and retry mechanism:
### Backoff and retry strategy
When you receive a rate limit error (HTTP 429), your application should:
1. Temporarily pause making requests
2. Wait for a period of time
3. Retry the request
The waiting period typically follows an exponential backoff pattern, where each subsequent retry waits longer than the previous one. For example:
- First retry: Wait 1 second
- Second retry: Wait 2 seconds
- Third retry: Wait 4 seconds
- Fourth retry: Wait 8 seconds
This exponential increase helps prevent overwhelming the API while still maintaining functionality.
### Best practices for handling rate limits
- Add randomness (jitter) to your retry delays to prevent multiple clients from retrying simultaneously
- Set a maximum number of retry attempts
- Log rate limit occurrences to help identify patterns and adjust your strategy
- Consider implementing a request queue to manage high-volume operations
## Use webhooks to avoid worrying about rate limits
While you can implement your own rate limit handling, we strongly recommend using our webhooks instead. Here's why:
- **Easier and faster integration build time**: Unified.to manages all rate limiting, backoff, and retry logic for you. You can also get all existing data via an initial sync, too
- **Efficient resource usage**: Instead of polling for changes, you receive updates only when they occur
- **Reliable delivery**: We'll keep trying to deliver webhook events even if your endpoint is temporarily unavailable
- **Automatic scaling**: Our webhook system automatically adjusts to rate limits across different providers
You can read more about our webhooks retry mechanism [here](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks#a-note-on-our-webhook-retry-mechanism).
## Related resources
- [Understanding webhooks](https://docs.unified.to/reference/webhooks)
- [How to create and configure webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
- [Pagination](https://docs.unified.to/reference/pagination)
## REST API
URL: https://docs.unified.to/reference/rest
# REST API
This API is organized around [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer){:target="\_blank"} and utilizes predictable resource-oriented URLs, accepts various request formats, returns responses in multiple formats, and uses standard HTTP response codes, authentication, and verbs.
It provides predictable URLs for accessing resources and uses built-in HTTP features to receive commands and return responses. This makes it easy to communicate with from a wide variety of environments, from command-line utilities to gadgets to the browser URL bar itself.
The API accepts JSON in requests and multiple content types for responses, including JSON, CSV, XML, NDJSON, and SCIM+JSON. Only the UTF-8 character encoding is supported for both requests and responses.
## Authentication
Send your [API token](https://app.unified.to/settings/api){:target="\_blank"} in the `Authorization` HTTP header for each API request.
Example: `Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9`
All API requests must be made over [HTTPS](http://en.wikipedia.org/wiki/HTTP_Secure){:target="\_blank"}. API requests made over plain HTTP or without authentication will fail. Furthermore, all connections must support the TLS 1.2 version for their SSL/HTTPS protocol.
Find your workspace API Token at [app.unified.to/settings/api](https://app.unified.to/settings/api){target="\_blank"}
## API URLs
Unified has two separate data-regions around the world currently. To use the API URL in a data-region, you must register an account with our application in that data region.
| Region | API URL | Application URL |
| ------------- | ------------------------- | ------------------------- |
| North America | https://api.unified.to | https://app.unified.to |
| Europe | https://api-eu.unified.to | https://app-eu.unified.to |
| Australia | https://api-au.unified.to | https://app-au.unified.to |
## HTTP Methods
| Method | Description |
| ------------------- | ------------------------------------------------------- |
| **GET** | Retrieve a single resource or get an array of resources |
| **PUT** / **PATCH** | Update a single resource partially or fully |
| **POST** | Create a single resource |
| **DELETE** | Remove a single resource |
## Request Format
This API accepts [JSON](https://www.json.org/){:target="\_blank"} payloads. You must supply a `Content-Type: application/json` header in PUT and POST requests.
The documented JSON properties for this API are case sensitive. For example, `id` and `Id` would not constitute the same property.
## Response Format
This API can return responses in multiple formats. You can specify your preferred response format by setting the `Accept` header in your request. The supported response formats are:
| Format | Header |
| ------------------------------- | ------------------------------- |
| JSON (default) | `Accept: application/json` |
| NDJSON (Newline Delimited JSON) | `Accept: application/ndjson` |
| CSV (Comma-Separated Values) | `Accept: text/csv` |
| XML | `Accept: text/xml` |
| SCIM+JSON (for SCIM endpoints) | `Accept: application/scim+json` |
If no `Accept` header is specified, the API will default to returning JSON responses.
When you successfully create or update a resource, the API will respond with the resulting resource in the requested format.
This API responds to successful requests with HTTP status codes in the 200 or 300 range, and with HTTP status codes in the 400 range if an error occurred.
A list of objects is sent as an array of objects in JSON and NDJSON formats, as rows in CSV format, or as nested elements in XML format. All other operations return a single object.
Note: The SCIM+JSON format is only available for specific endpoints (e.g. employee and group).
## Common HTTP Status Codes
| Code | Description |
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **200** | Ok: The request was successful. |
| **400** | Bad Request: Required parameters are missing or in the wrong format. Check the request payload against the API documentation. |
| **401** | Unauthorized: The connection is likely broken and requires recreation. This typically occurs when app access has been revoked or authentication credentials are invalid. |
| **403** | Forbidden: The connection lacks the required permissions or scopes. This usually indicates a mismatch between the scopes configured in your provider's developer account and what Unified.to is requesting. |
| **404** | Not Found: The requested resource was not found. |
| **429** | Rate Limit Exceeded: The provider's API rate limit has been reached. Each platform (and customer plan) has different rate limits. Consider using webhooks or implementing request throttling. |
| **500** | Internal Server Error: Our server encountered an unexpected condition. We monitor these errors and will work to resolve them. For platform issues, retry after a short delay. |
| **501** | Not Implemented: The requested functionality is not supported by this integration. Check the integration's documentation for supported features. |
For more help with error codes, review our [troubleshooting guide](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_connections).
## SDKs
URL: https://docs.unified.to/reference/sdks
# SDKs
Use the following SDKs to access our API. Note that each SDK will use that language's standard naming convention for field names. for example, TypeScript uses CamelCase, but all of our documenation uses snake_case.
- **Typescript:** [npmjs.com/package/@unified-api/typescript-sdk](https://www.npmjs.com/package/@unified-api/typescript-sdk){:target="\_blank"}
::sdk-version{name="typescript"}
::
- **Python:** [github.com/unified-to/unified-python-sdk](https://github.com/unified-to/unified-python-sdk){:target="\_blank"}
::sdk-version{name="Python"}
::
- **PHP:** [github.com/unified-to/unified-php-sdk](https://github.com/unified-to/unified-php-sdk){:target="\_blank"}
::sdk-version{name="php"}
::
- **Java:** [central.sonatype.com/artifact/to.unified/unified-java-sdk](https://central.sonatype.com/artifact/to.unified/unified-java-sdk){:target="\_blank"}
::sdk-version{name="java"}
::
- **Go:** [github.com/unified-to/unified-go-sdk](https://github.com/unified-to/unified-go-sdk){:target="\_blank"}
::sdk-version{name="go"}
::
- **C#:** [nuget.org/packages/UnifiedTo](https://www.nuget.org/packages/UnifiedTo){:target="\_blank"}
::sdk-version{name="csharp"}
::
- **Ruby:** [github.com/unified-to/unified-ruby-sdk](https://github.com/unified-to/unified-ruby-sdk){:target="\_blank"}
::sdk-version{name="ruby"}
::
- **Other:** You can generate SDKs in other languages by utilizing our [openapi.json](https://api.unified.to/openapi.json){:target="\_blank"} with tools like [OpenAPI Generator](https://openapi-generator.tech/){:target="\_blank"}.
# Specification Files
Access our API specifications in various formats to integrate with your preferred tools and workflows. They are updated, at least, daily.
- **OpenAPI specification:** [openapi.json](https://api.unified.to/openapi.json){:target="\_blank"} / [openapi.yaml](https://api.unified.to/openapi.yaml){:target="\_blank"}
::callout
The openapi.json/.yaml file contains our API specification in OpenAPI 3.0 format - the modern standard for REST API documentation. This specification offers enhanced features over Swagger 2.0, including improved security scheme definitions and more flexible parameter descriptions. It's great for generating modern API documentation, client SDKs, and mock servers.
You can download one [openapi.json](https://api.unified.to/openapi.json){:target="\_blank"} for all of our Unified APIs, or you can download a openapi.json for each API.
- [openapi-accounting.json](https://api.unified.to/docs/openapi-accounting.json){:target="\_blank"} / [openapi-accounting.yaml](https://api.unified.to/docs/openapi-accounting.yaml){:target="\_blank"}
- [openapi-ads.json](https://api.unified.to/docs/openapi-ads.json){:target="\_blank"} / [openapi-ats.yaml](https://api.unified.to/docs/openapi-ads.yaml){:target="\_blank"}
- [openapi-assessment.json](https://api.unified.to/docs/openapi-assessment.json){:target="\_blank"} / [openapi-ats.yaml](https://api.unified.to/docs/openapi-assessment.yaml){:target="\_blank"}
- [openapi-ats.json](https://api.unified.to/docs/openapi-ats.json){:target="\_blank"} / [openapi-ats.yaml](https://api.unified.to/docs/openapi-ats.yaml){:target="\_blank"}
- [openapi-calendar.json](https://api.unified.to/docs/openapi-calendar.json){:target="\_blank"} / [openapi-calendar.yaml](https://api.unified.to/docs/openapi-calendar.yaml){:target="\_blank"}
- [openapi-commerce.json](https://api.unified.to/docs/openapi-commerce.json){:target="\_blank"} / [openapi-commerce.yaml](https://api.unified.to/docs/openapi-commerce.yaml){:target="\_blank"}
- [openapi-crm.json](https://api.unified.to/docs/openapi-crm.json){:target="\_blank"} / [openapi-crm.yaml](https://api.unified.to/docs/openapi-crm.yaml){:target="\_blank"}
- [openapi-enrichment.json](https://api.unified.to/docs/openapi-enrichment.json){:target="\_blank"} / [openapi-enrichment.yaml](https://api.unified.to/docs/openapi-enrichment.yaml){:target="\_blank"}
- [openapi-hris.json](https://api.unified.to/docs/openapi-hris.json){:target="\_blank"} / [openapi-hris.yaml](https://api.unified.to/docs/openapi-hris.yaml){:target="\_blank"}
- [openapi-kms.json](https://api.unified.to/docs/openapi-kms.json){:target="\_blank"} / [openapi-kms.yaml](https://api.unified.to/docs/openapi-kms.yaml){:target="\_blank"}
- [openapi-lms.json](https://api.unified.to/docs/openapi-lms.json){:target="\_blank"} / [openapi-lms.yaml](https://api.unified.to/docs/openapi-lms.yaml){:target="\_blank"}
- [openapi-martech.json](https://api.unified.to/docs/openapi-martech.json){:target="\_blank"} / [openapi-martech.yaml](https://api.unified.to/docs/openapi-martech.yaml){:target="\_blank"}
- [openapi-messaging.json](https://api.unified.to/docs/openapi-messaging.json){:target="\_blank"} / [openapi-messaging.yaml](https://api.unified.to/docs/openapi-messaging.yaml){:target="\_blank"}
- [openapi-payment.json](https://api.unified.to/docs/openapi-payment.json){:target="\_blank"} / [openapi-payment.yaml](https://api.unified.to/docs/openapi-payment.yaml){:target="\_blank"}
- [openapi-repo.json](https://api.unified.to/docs/openapi-repo.json){:target="\_blank"} / [openapi-repo.yaml](https://api.unified.to/docs/openapi-repo.yaml){:target="\_blank"}
- [openapi-scim.json](https://api.unified.to/docs/openapi-scim.json){:target="\_blank"} / [openapi-scim.yaml](https://api.unified.to/docs/openapi-scim.yaml){:target="\_blank"}
- [openapi-storage.json](https://api.unified.to/docs/openapi-storage.json){:target="\_blank"} / [openapi-storage.yaml](https://api.unified.to/docs/openapi-storage.yaml){:target="\_blank"}
- [openapi-task.json](https://api.unified.to/docs/openapi-task.json){:target="\_blank"} / [openapi-task.yaml](https://api.unified.to/docs/openapi-task.yaml){:target="\_blank"}
- [openapi-ticketing.json](https://api.unified.to/docs/openapi-ticketing.json){:target="\_blank"} / [openapi-ticketing.yaml](https://api.unified.to/docs/openapi-ticketing.yaml){:target="\_blank"}
- [openapi-uc.json](https://api.unified.to/docs/openapi-uc.json){:target="\_blank"} / [openapi-uc.yaml](https://api.unified.to/docs/openapi-uc.yaml){:target="\_blank"}
- [openapi-verification.json](https://api.unified.to/docs/openapi-verification.json){:target="\_blank"} / [openapi-verification.yaml](https://api.unified.to/docs/openapi-verification.yaml){:target="\_blank"}
::
- **Swagger specification:** [swagger.json](https://api.unified.to/swagger.json){:target="\_blank"} / [swagger.yaml](https://api.unified.to/swagger.yaml)
::callout
Our swagger.json/.yaml file provides a detailed API specification in the Swagger 2.0 format. It's ideal for developers who are working with older tools or platforms that haven't yet migrated to OpenAPI 3.0. Use this specification to generate client libraries, automate testing, and integrate with legacy systems.
::
- **Postman collection:** [collection.json](https://api.unified.to/collection.json){:target="\_blank"}
::callout
Our `collection.json` file is a ready-to-use Postman collection that lets you start testing our API endpoints immediately. Import this file into Postman to get a complete set of pre-configured API requests. It's an excellent resource for API exploration and testing without writing any code.
You can download one [collection.json](https://api.unified.to/collection.json){:target="\_blank"} for all of our Unified APIs, or you can download a collection.json for each API.
- [collection-accounting.json](https://api.unified.to/docs/collection-accounting.json){:target="\_blank"}
- [collection-ads.json](https://api.unified.to/docs/collection-ads.json){:target="\_blank"}
- [collection-assessment.json](https://api.unified.to/docs/collection-assessment.json){:target="\_blank"}
- [collection-ats.json](https://api.unified.to/docs/collection-ats.json){:target="\_blank"}
- [collection-calendar.json](https://api.unified.to/docs/collection-calendar.json){:target="\_blank"}
- [collection-commerce.json](https://api.unified.to/docs/collection-commerce.json){:target="\_blank"}
- [collection-crm.json](https://api.unified.to/docs/collection-crm.json){:target="\_blank"}
- [collection-enrichment.json](https://api.unified.to/docs/collection-enrichment.json){:target="\_blank"}
- [collection-hris.json](https://api.unified.to/docs/collection-hris.json){:target="\_blank"}
- [collection-kms.json](https://api.unified.to/docs/collection-kms.json){:target="\_blank"}
- [collection-lms.json](https://api.unified.to/docs/collection-lms.json){:target="\_blank"}
- [collection-martech.json](https://api.unified.to/docs/collection-martech.json){:target="\_blank"}
- [collection-messaging.json](https://api.unified.to/docs/collection-messaging.json){:target="\_blank"}
- [collection-payment.json](https://api.unified.to/docs/collection-payment.json){:target="\_blank"}
- [collection-repo.json](https://api.unified.to/docs/collection-repo.json){:target="\_blank"}
- [collection-scim.json](https://api.unified.to/docs/collection-scim.json){:target="\_blank"}
- [collection-storage.json](https://api.unified.to/docs/collection-storage.json){:target="\_blank"}
- [collection-task.json](https://api.unified.to/docs/collection-task.json){:target="\_blank"}
- [collection-ticketing.json](https://api.unified.to/docs/collection-ticketing.json){:target="\_blank"}
- [collection-uc.json](https://api.unified.to/docs/collection-uc.json){:target="\_blank"}
- [collection-verification.json](https://api.unified.to/docs/collection-verification.json){:target="\_blank"}
Visit the Unified postman workspace at [Postman](https://www.postman.com/unified-to/unified-api-workspace/overview){:target="\_blank"}.
::
- **Supabase SQL DDL:** [Unified API's DDL](https://api.unified.to/docs/supabase-ddl.sql){:target="\_blank"}
::callout
You can use our SQL DDL to create databases in Supabase matching our data-model schemas.
You can download one [DDL SQL](https://api.unified.to/docs/supabase-ddl.sql){:target="\_blank"} for all of our Unified APIs, or you can download a DDL SQL for each API, which will contain just the data models for that API category.
- [supabase-ddl-accounting.sql](https://api.unified.to/docs/supabase-ddl-accounting.sql){:target="\_blank"}
- [supabase-ddl-ads.sql](https://api.unified.to/docs/supabase-ddl-ads.sql){:target="\_blank"}
- [supabase-ddl-assessment.sql](https://api.unified.to/docs/supabase-ddl-assessment.sql){:target="\_blank"}
- [supabase-ddl-ats.sql](https://api.unified.to/docs/supabase-ddl-ats.sql){:target="\_blank"}
- [supabase-ddl-calendar.sql](https://api.unified.to/docs/supabase-ddl-calendar.sql){:target="\_blank"}
- [supabase-ddl-commerce.sql](https://api.unified.to/docs/supabase-ddl-commerce.sql){:target="\_blank"}
- [supabase-ddl-crm.sql](https://api.unified.to/docs/supabase-ddl-crm.sql){:target="\_blank"}
- [supabase-ddl-enrichment.sql](https://api.unified.to/docs/supabase-ddl-enrichment.sql){:target="\_blank"}
- [supabase-ddl-hris.sql](https://api.unified.to/docs/supabase-ddl-hris.sql){:target="\_blank"}
- [supabase-ddl-kms.sql](https://api.unified.to/docs/supabase-ddl-kms.sql){:target="\_blank"}
- [supabase-ddl-lms.sql](https://api.unified.to/docs/supabase-ddl-lms.sql){:target="\_blank"}
- [supabase-ddl-martech.sql](https://api.unified.to/docs/supabase-ddl-martech.sql){:target="\_blank"}
- [supabase-ddl-messaging.sql](https://api.unified.to/docs/supabase-ddl-messaging.sql){:target="\_blank"}
- [supabase-ddl-payment.sql](https://api.unified.to/docs/supabase-ddl-payment.sql){:target="\_blank"}
- [supabase-ddl-repo.sql](https://api.unified.to/docs/supabase-ddl-repo.sql){:target="\_blank"}
- [supabase-ddl-scim.sql](https://api.unified.to/docs/supabase-ddl-scim.sql){:target="\_blank"}
- [supabase-ddl-storage.sql](https://api.unified.to/docs/supabase-ddl-storage.sql){:target="\_blank"}
- [supabase-ddl-task.sql](https://api.unified.to/docs/supabase-ddl-task.sql){:target="\_blank"}
- [supabase-ddl-ticketing.sql](https://api.unified.to/docs/supabase-ddl-ticketing.sql){:target="\_blank"}
- [supabase-ddl-uc.sql](https://api.unified.to/docs/supabase-ddl-uc.sql){:target="\_blank"}
- [supabase-ddl-verification.sql](https://api.unified.to/docs/supabase-ddl-verification.sql){:target="\_blank"}
::
- **Snowflake SQL DDL:** [Unified API's DDL](https://api.unified.to/docs/snowflake-ddl.sql){:target="\_blank"}
::callout
You can use our SQL DDL to create tables in Snowflake matching our data-model schemas.
You can download one [DDL SQL](https://api.unified.to/docs/snowflake-ddl.sql){:target="\_blank"} for all of our Unified APIs, or you can download a DDL SQL for each API, which will contain just the data models for that API category.
- [snowflake-ddl-accounting.sql](https://api.unified.to/docs/snowflake-ddl-accounting.sql){:target="\_blank"}
- [snowflake-ddl-ads.sql](https://api.unified.to/docs/snowflake-ddl-ads.sql){:target="\_blank"}
- [snowflake-ddl-assessment.sql](https://api.unified.to/docs/snowflake-ddl-assessment.sql){:target="\_blank"}
- [snowflake-ddl-ats.sql](https://api.unified.to/docs/snowflake-ddl-ats.sql){:target="\_blank"}
- [snowflake-ddl-calendar.sql](https://api.unified.to/docs/snowflake-ddl-calendar.sql){:target="\_blank"}
- [snowflake-ddl-commerce.sql](https://api.unified.to/docs/snowflake-ddl-commerce.sql){:target="\_blank"}
- [snowflake-ddl-crm.sql](https://api.unified.to/docs/snowflake-ddl-crm.sql){:target="\_blank"}
- [snowflake-ddl-enrichment.sql](https://api.unified.to/docs/snowflake-ddl-enrichment.sql){:target="\_blank"}
- [snowflake-ddl-hris.sql](https://api.unified.to/docs/snowflake-ddl-hris.sql){:target="\_blank"}
- [snowflake-ddl-kms.sql](https://api.unified.to/docs/snowflake-ddl-kms.sql){:target="\_blank"}
- [snowflake-ddl-lms.sql](https://api.unified.to/docs/snowflake-ddl-lms.sql){:target="\_blank"}
- [snowflake-ddl-martech.sql](https://api.unified.to/docs/snowflake-ddl-martech.sql){:target="\_blank"}
- [snowflake-ddl-messaging.sql](https://api.unified.to/docs/snowflake-ddl-messaging.sql){:target="\_blank"}
- [snowflake-ddl-payment.sql](https://api.unified.to/docs/snowflake-ddl-payment.sql){:target="\_blank"}
- [snowflake-ddl-repo.sql](https://api.unified.to/docs/snowflake-ddl-repo.sql){:target="\_blank"}
- [snowflake-ddl-scim.sql](https://api.unified.to/docs/snowflake-ddl-scim.sql){:target="\_blank"}
- [snowflake-ddl-storage.sql](https://api.unified.to/docs/snowflake-ddl-storage.sql){:target="\_blank"}
- [snowflake-ddl-task.sql](https://api.unified.to/docs/snowflake-ddl-task.sql){:target="\_blank"}
- [snowflake-ddl-ticketing.sql](https://api.unified.to/docs/snowflake-ddl-ticketing.sql){:target="\_blank"}
- [snowflake-ddl-uc.sql](https://api.unified.to/docs/snowflake-ddl-uc.sql){:target="\_blank"}
- [snowflake-ddl-verification.sql](https://api.unified.to/docs/snowflake-ddl-verification.sql){:target="\_blank"}
::
- **Cloudflare Workers:** [github repo](https://github.com/unified-to/unified-cloudflare-worker){:target="\_blank"}
::callout
You can use our API in a Cloudflare Worker by using our Cloudflare Worker template
[](https://deploy.workers.cloudflare.com/?url=https://github.com/unified-to/unified-cloudflare-worker){:target="\_blank"}
::
## Slow fields
URL: https://docs.unified.to/reference/slow-fields
# Slow fields
Some fields in upstream APIs are expensive to fetch because they require extra API calls per record. We call these **slow fields**.
Slow fields let you choose between **faster responses** and **more complete data**. You control the default behavior at the workspace level, and you can always override it per request with the `fields` query parameter.
---
## What are slow fields?
**Slow fields** are resource fields that require additional API calls to fetch from the upstream provider.
Examples:
- A job application’s `offers` in Greenhouse requires a separate API call per application.
- Nested raw objects may expose additional slow fields like `raw.offers`.
Because these fields can be expensive to retrieve, we treat them specially:
- They can be **excluded by default** to keep responses fast.
- They can be **included when explicitly requested** using the `fields` parameter.
- Or they can be **included by default** if you prefer completeness over latency.
---
## Workspace setting: Slow fields opt-in
You can control how slow fields behave by default from your workspace settings in the Unified app under **Settings → Workspace → Slow fields opt-in** ([workspace settings](https://app.unified.to/settings/workspace)).
- `false` (**Opt-out / recommended default**)
- Slow fields are **excluded by default**.
- Fewer upstream calls and lower latency.
- Slow fields are only fetched when explicitly requested via `fields`.
- `true` (**Opt-in**)
- Slow fields are **included by default**.
- Responses are more complete.
- Responses can be slower due to additional API calls.
You can still override this default on any request using the `fields` parameter. For a general overview of how `fields` works (including raw fields), see [Working with Custom & Original Fields](/reference/fields).
---
## How slow fields interact with `fields=…`
You can control which fields are returned using the `fields` query parameter. The behavior depends on how you’ve configured **Slow fields opt-in** for your workspace.
### When Slow fields opt-in is set to **Opt-out** (default)
Slow fields are excluded unless you request them:
```http
GET /ats/{connection_id}/application
```
Returns standard (fast) fields only.
```http
GET /ats/{connection_id}/application?fields=offers
```
Returns only the `offers` field (a slow field; this may trigger extra upstream API calls).
```http
GET /ats/{connection_id}/application?fields=job_id,candidate_id
```
Returns only the requested fast fields (no slow fields).
### When Slow fields opt-in is set to **Opt-in**
Slow fields are included unless you filter them out:
```http
GET /ats/{connection_id}/application
```
Returns all fields, including slow fields (slower, more complete).
```http
GET /ats/{connection_id}/application?fields=job_id,candidate_id
```
Returns only the requested fields (fast, excludes other slow fields).
```http
GET /ats/{connection_id}/application?fields=job_id,offers
```
Returns `job_id` plus the `offers` slow field (may trigger extra upstream API calls).
---
## Raw objects and slow fields
The same logic applies to nested raw objects:
- `fields=raw.offers`
Requests the slow `offers` field inside the raw payload.
- `fields=raw.last_activity_at`
Requests only a non-slow raw field (no extra API calls).
Slow fields are always explicit: if you do not request them (and your workspace is not opted in by default), they will not be fetched.
---
## When to opt in vs opt out
**Recommended patterns:**
1. **High‑volume or latency‑sensitive traffic**
- Set **Slow fields opt-in** to **Opt-out**.
- Only request slow fields when you actually need them, via `fields=…`.
2. **Comprehensive data syncs or backfills**
- Set **Slow fields opt-in** to **Opt-in**, _or_
- Leave it as **Opt-out** and explicitly request all needed slow fields with `fields`.
3. **Webhooks and push-based flows**
- Configure the `fields` parameter to include only the fields your consumer needs.
- Avoid including slow fields unless your webhook handler can tolerate the extra latency.
---
## Identifying which fields are slow
Slow fields are **integration- and object-specific**. For example:
- **Greenhouse Applications**: `offers`, `raw.offers`
Other integrations and object types may expose different slow fields. To see which fields are slow for a given object, check the integration-specific documentation or contact support.
## Introduction to webhooks
URL: https://docs.unified.to/reference/webhooks
# Webhooks
This guide covers the basics of webhooks and their underlying mechanisms.
For our webhook API reference, see: [Webhook API Overview](https://docs.unified.to/unified/webhook/model)
To learn how to use webhooks, see: [How to create and configure webhooks](https://docs.unified.to/guides/how_to_create_and_configure_webhooks)
If you’re having trouble with webhooks, see: [How to troubleshoot unhealthy webhooks](https://docs.unified.to/guides/how_to_troubleshoot_unhealthy_webhooks)
## Background: What are webhooks?
Webhooks allow servers to send real-time notifications to other servers when a specific event occurs. They are sometimes called “HTTP callbacks” or “push APIs”.
Webhooks enable instant communication between different systems, making them crucial for maintaining data synchronization and triggering automated workflows. For instance, Stripe's payment processing system uses webhooks to notify a merchant's server immediately when a customer completes a purchase, allowing for instant order fulfillment and inventory updates.
Think of webhooks as a delivery service that brings packages directly to your doorstep. In contrast, traditional REST APIs are like repeatedly checking your mailbox to see if you've received any mail. With webhooks, you don't need to keep asking, "Is there any new mail?" Instead, the data comes to you as soon as it's available, saving time and resources.
Webhooks can make your synchronization strategy less complicated since your application will receive updated data directly, eliminating the need for frequent polling and reducing the risk of missing important updates.
### Differences between polling for updates and receiving pushed updates
**Polling for updates (via APIs)**
The traditional way of polling for new data from an API is by calling a `List