---
title: "How to Advertise ATS Jobs on LinkedIn, Meta, Google and TikTok with"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_advertise_ats_jobs_on_linkedin_meta_google_and_tiktok_with-icon.png
date: 2026-07-10T23:56:00.000Z
updated: 2026-10-21T13:51:03.414Z
readingTime: 12
tag: Product
description: "Recruiting and talent products that display customer jobs often need to promote them too, running the same open role as a paid campaign on the platforms where..."
url: "https://unified.to/blog/how_to_advertise_ats_jobs_on_linkedin_meta_google_and_tiktok_with"
---

# How to Advertise ATS Jobs on LinkedIn, Meta, Google and TikTok with

Recruiting and talent products that display customer jobs often need to promote them too, running the same open role as a paid campaign on the platforms where candidates are: LinkedIn, Meta, Google, and TikTok. With [Unified.to](http://unified.to/) you read the job from the ATS as one enriched object and act on it in the Advertising category through the same connection model, keeping the ad campaign aligned to the job as it changes. The job stays the source of truth; the campaign follows it. [Unified.to](http://unified.to/) keeps no customer records at rest; connection credentials and operational metadata are encrypted at rest.


The hard part is not fetching a job or reading a campaign metric. It is that the job lives in one system and the campaign lives in another, each with a different schema, and keeping the two in sync means building and maintaining integrations on both sides. This guide shows how to connect the two categories so that when a role opens, changes, or closes in the ATS, the campaign follows. The examples use the Unified.to TypeScript SDK.


## Which two Unified.to categories does this use case span?


This use case spans the ATS category (the job as source) and the Advertising category (the campaign as destination). The ATS job supplies the campaign's content and lifecycle; the Advertising integrations run the campaign.


| Category    | Role in this flow                             | Key objects                                                   |
| ----------- | --------------------------------------------- | ------------------------------------------------------------- |
| ATS         | Source of truth for the role being advertised | `job`                                                         |
| Advertising | Runs the paid campaign for that role          | `organization`, `campaign`, `group`, `ad`, `target`, `report` |
The Advertising category defines ten objects (as of 20 October 2026). Six support full create, read, update and delete (Organization, Campaign, Group, Ad, Creative and Insertion Order), which is what makes creating a campaign from a job possible (Path B, below). Asset supports list, retrieve and create. Report, Target and Promoted are read-only: Report returns metrics, while Target and Promoted are lookups whose IDs you feed into writes. Target IDs go into targeting, and Promoted IDs (a company page, a lead form) on the group for Meta Ads and Google Ads, or on the ad for X Ads and Amazon Advertising (as of 19 October 2026). Connecting these two categories through one connection model lets a product promote customer jobs without building separate integrations per ATS and per ad platform.


## What does the ATS Job object give you for an ad?


The Unified.to `AtsJob` object returns in a single call with the fields an ad needs already embedded on the parent, rather than split across separate resources you assemble yourself. One read gives you the content, the targeting inputs, the destination, and the lifecycle signal.


| Job field                                                                                          | Ad use                                                                 |
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `name`                                                                                             | Ad headline / campaign name                                            |
| `description`                                                                                      | Ad body copy                                                           |
| `postings[]` (`posting_url`, `name`, `description`, `is_active`)                                   | Public-facing destination and copy for the active posting              |
| `public_job_urls`                                                                                  | Ad destination / apply URL                                             |
| `addresses` (`city`, `region_code`, `country_code`)                                                | Geographic targeting input                                             |
| `remote`                                                                                           | Targeting or copy variant for remote roles                             |
| `groups[]` (`type`: `DEPARTMENT`, `TEAM`, `DIVISION`, `BUSINESS_UNIT`, `BRANCH`, `SUB_DEPARTMENT`) | Campaign segmentation by org unit                                      |
| `employment_type`                                                                                  | Audience or copy variant (full-time, contractor, intern, and the rest) |
| `compensation[]` (`type`, `min`, `max`, `currency`, `frequency`)                                   | Pay-range copy where the ad platform allows it                         |
| `language_locale`                                                                                  | Ad locale (ISO 639-1)                                                  |
| `skills`                                                                                           | Targeting or copy inputs                                               |
| `status` (`OPEN`, `CLOSED`, `ARCHIVED`, `PENDING`, `DRAFT`) + `closed_at`                          | Campaign lifecycle trigger                                             |
Two things worth reading defensively. First, prefer the active posting over the raw job where it exists: `postings[]` carries a public `posting_url`, `name`, and `description` written for candidates, which is better ad material than the internal job fields; fall back to `public_job_urls` and the job's `name`/`description` when no active posting is present. Second, every field above is defined in the unified model, but _population_ varies by integration. As of 19 October 2026, addresses come back on 119 of 152 job integrations, compensation on 91, public_job_urls on 69, language_locale on 24, postings[] on 18 and openings[] on 7. Read each field with a fallback, and use the published per-integration field support to know what a given ATS returns before you depend on it.


## How do customers connect their ATS and ad accounts?


Customers authorize each integration through Unified.to's embedded authorization flow, and your application stores one `connection_id` per category per customer. The flow is the same on both sides:

1. Your application launches the authorization component.
2. The customer selects their ATS (Greenhouse, Lever, and the rest) and, separately, their ad platform.
3. The customer authorizes access.
4. Unified.to returns a `connection_id` for each.

Store the mapping `user_id → connection_id` for both the ATS connection and the Advertising connection, and reference the right one on each call.


```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';

const sdk = new UnifiedTo({
  security: { jwt: process.env.UNIFIED_API_KEY! },
});

const atsConnectionId = '...'; // customer's ATS connection
const adsConnectionId = '...'; // customer's Advertising connection
```


## How do you read the job from the ATS?


Read the role from the ATS job endpoint, which returns the same `AtsJob` shape on 152 ATS integrations (as of 19 October 2026). Filter to open roles, and use `updated_gte` for incremental reads where the integration supports it (63 of 152).


```typescript
// REST: GET /ats/{connection_id}/job
const jobs = await sdk.ats.listAtsJobs({
  connectionId: atsConnectionId,
  limit: 50,
  offset: 0,
});

const openJobs = jobs.filter((j) => j.status === 'OPEN');
```


Each returned job carries the fields from the table above on the parent object. You do not make a second call to assemble location, department, or the apply URL; they are already on the job.


## How do you turn a job into a running campaign?


There are two paths, and which one applies depends on whether the campaign already exists in the customer's ad account. Both are supported. This guide walks Path A end to end, aligning an existing campaign to the job, then shows Path B, creating a new ad from the job, with the job-to-ad field mapping worked in full.


**Path A: align an existing campaign to the job (covered here).** The customer's campaign already exists in the ad platform. Your product maps the ATS job to that campaign and keeps its content, targeting, and lifecycle in sync with the job. Group targeting reads, merges and updates; campaign changes use PATCH with only the fields you change, which also works where an integration has no campaign retrieve (LinkedIn, as of 19 October 2026).


**Path B: create the campaign from the job in one pass (supported, with a per-integration check).** The Campaign, Group, Ad, and Creative objects support full CRUD, so you can create a new campaign from an ATS job rather than only aligning an existing one. What varies is _which_ integrations accept creation. As of 19 October 2026, campaign create is available on 21 of the 34 advertising integrations with a campaign object, including Google Ads, LinkedIn, Meta Ads, Microsoft Advertising, Reddit Ads, Snapchat and TikTok Ads; confirm it for the ad platforms your customers run before you ship. The create hierarchy is Campaign → Group → Ad, with a Creative created before the ad that references it for image or video ads (text ads need none), mapping the job's `name` and `description` onto the ad's copy fields and `public_job_urls` onto the destination.


### Set targeting from the job's location


Apply targeting at the group level, which is where most advertising integrations accept it. First resolve the job's location to a targeting identifier using the lookup endpoint, then merge it into the group.


```typescript
// 1) Resolve the job location to a targeting ID
const address = job.addresses?.[0];
const countryCode = address?.countryCode; // e.g. 'US'

const targets = await sdk.ads.listAdsTargets({
  connectionId: adsConnectionId,
  orgId: organizationId, // ad account ID; required on 11 of the 22 integrations with a target lookup (as of 19 October 2026)
  type: 'countries',
  query: address?.country ?? countryCode ?? '',
  limit: 50,
});

// listAdsTargets returns an array; match on the job's country code
const country = targets.find((t) => t.id === countryCode);
if (!country) throw new Error('No targeting match for the job location');

// 2) Read the group, merge targeting, update
const group = await sdk.ads.getAdsGroup({ connectionId: adsConnectionId, id: groupId });

await sdk.ads.updateAdsGroup({
  connectionId: adsConnectionId,
  id: groupId,
  adsGroup: {
    ...group,
    targeting: {
      ...group.targeting,
      geographic: { countries: [{ id: country.id, name: country.name }] },
    },
  },
});
```


Targeting identifiers differ by integration, which is why you resolve them through `listAdsTargets` rather than passing raw values. Available targeting types vary by integration; check supported options before enabling a control in your UI. And because this is a job ad, keep targeting to broad geography. The employment-category restrictions below apply whether you are aligning an existing campaign or creating one.


### How do you change a campaign's budget and end date?


Send a PATCH with only the budget and schedule fields you change; there is no need to read the campaign first. Campaign updates also accept PUT.


```typescript
// PATCH sends only the fields you change; the rest of the campaign is left as it is
await sdk.ads.patchAdsCampaign({
  connectionId: adsConnectionId,
  id: campaignId,
  adsCampaign: {
    budgetAmount: 5000,
    budgetPeriod: 'DAILY',
    endAt: new Date('2026-12-31T23:59:59Z'), // the SDK takes a Date and sends ISO-8601
  },
});
```


## How do you create a new ad from the job? (Path B)


Where you are not aligning an existing campaign but building one, create the campaign, group, and ad in that order, then map the job's content onto the ad's fields. The Ad object carries the text of the ad directly (`headline`, `ad_copy`, `cta`, `final_url`), so a text or responsive job ad needs no separate creative; for an image or video ad you also create a `creative` and reference it from the ad. The create hierarchy is campaign → group → (creative, for media) → ad.


Create the campaign first, with budget, schedule, and, because this is a job ad, the employment special ad category:


```typescript
const campaign = await sdk.ads.createAdsCampaign({   // POST /ads/{connection_id}/campaign
  connectionId: adsConnectionId,
  adsCampaign: {
    name: job.name,
    organizationId,
    status: 'PAUSED',
    budgetAmount: 5000,
    budgetPeriod: 'DAILY',      // DAILY / MONTHLY / TOTAL / LIFETIME
    currency: 'USD',            // writable on LinkedIn, Adform, Equativ and The Trade Desk only
    startAt: new Date('2026-11-01T00:00:00Z'),   // the SDK takes a Date and sends ISO-8601
    category: 'EMPLOYMENT',     // Meta only
  },
});
```


Then create the group, applying geographic targeting resolved from the job's `addresses`, an optimization goal that drives applies, and, for a job ad, no demographic targeting (see below):


```typescript
const group = await sdk.ads.createAdsGroup({   // POST /ads/{connection_id}/group
  connectionId: adsConnectionId,
  adsGroup: {
    name: job.name,
    campaignId: campaign.id,
    status: 'PAUSED',
    optimizationGoal: 'LINK_CLICKS',   // writable on Meta / TikTok / X: LINK_CLICKS / LANDING_PAGE_VIEWS / CONVERSIONS
    targeting: {
      geographic: {
        countries: [{ id: country.id, name: country.name }], // resolved with listAdsTargets, as in Path A
      },
    },
  },
});
```


The group also carries bid controls (`bid_amount`, `bid_strategy`), `pacing`, and `billing_event`, and budget can sit at the group level as well as the campaign level depending on the integration; set these where you need them, or rely on the platform defaults. For a lead-generation job ad, attach a `LEAD_FORM_ID` promoted entity so candidates apply in-platform, and set `optimization_goal` to `LEAD_GENERATION` where the integration accepts it; `optimization_goal` is writable on Meta Ads, TikTok Ads, X Ads, OpenAI Ads and StackAdapt (as of 19 October 2026).


Then create the ad, mapping the job onto confirmed Ad fields. Prefer the active posting's public copy over the raw job:


```typescript
const activePosting = job.postings?.find((p) => p.isActive);

const ad = await sdk.ads.createAdsAd({   // POST /ads/{connection_id}/ad
  connectionId: adsConnectionId,
  adsAd: {
    name: job.name,
    campaignId: campaign.id,
    groupId: group.id,
    headline: activePosting?.name ?? job.name,
    adCopy: activePosting?.description ?? job.description,
    cta: 'Apply',
    finalUrl: activePosting?.postingUrl ?? job.publicJobUrls?.[0],
    // creativeIds: [creative.id],                         // media ads: create the creative first (below)
    status: 'PAUSED',                                      // create paused, activate after review
  },
});
```


Create the ad with `status: 'PAUSED'` and activate it after a review step, so a malformed mapping never spends live budget. `ad_type` is read-only on most integrations (writable on StackAdapt only, as of 19 October 2026), so the example doesn't set it.


To attach a promoted entity, look it up through the read-only Promoted endpoint and pass it in the `promoted` array as `{ id, type }`, on the group for Meta Ads and Google Ads, or on the ad for X Ads and Amazon Advertising (as of 19 October 2026). For a job ad the useful types are `PAGE_ID` (run the ad from the employer's page) and `LEAD_FORM_ID`, for a lead-gen job ad that captures candidate interest in-platform instead of sending the click to an external apply URL.


For an image or video job ad rather than a text one, create the `creative` first and reference it from the ad via `creativeIds`. The Creative object carries its own copy and asset fields, so the job maps onto it directly:


```typescript
// Create the creative before the ad, then pass creativeIds: [creative.id] on the ad
const creative = await sdk.ads.createAdsCreative({   // POST /ads/{connection_id}/creative
  connectionId: adsConnectionId,
  adsCreative: {
    name: job.name,
    groupId: group.id,                                     // not writable on Meta
    creativeType: 'STANDARD',                              // writable on LinkedIn, Meta and Microsoft Advertising, among others
    title: activePosting?.name ?? job.name,
    body: activePosting?.description ?? job.description,
    cta: 'Apply',
    linkUrl: activePosting?.postingUrl ?? job.publicJobUrls?.[0],
    assetUrls: [yourHostedImageUrl],                       // your creative asset
    status: 'PAUSED',
  },
});
```


Full CRUD is defined on the object model, but per-integration create coverage is capability-matrix data, not a schema guarantee. Confirm the integrations your customers run accept programmatic campaign creation before you ship. Where a platform needs a field the unified model doesn't carry, put it in a `raw` object on the request body (`adsCampaign.raw`, `adsGroup.raw`, `adsAd.raw`); those fields go to the platform as written.


## Which fields are writable, on which platforms?


Writability varies by integration, and the biggest variation is where the ad copy lives: some platforms keep it on the Ad, others on the Creative. On LinkedIn, Microsoft Advertising and Pinterest Ads (beta) the copy is writable on the Ad (`headline`, `ad_copy`); on Meta it is writable on the Creative (`title`, `body`); Google Ads and TikTok split across both. So the create flow is the same shape everywhere (campaign, then group, then ad, plus a creative for media), but you set the copy on whichever object the target integration marks writable, and you read that from [Unified.to](http://unified.to/)'s published field-support matrix rather than assuming. The employment `category` field is writable through [Unified.to](http://unified.to/) on Meta; on other platforms you satisfy their employment-ad requirements through their own setup and the `raw` passthrough.


The controls that stay constant across the major job-ad platforms are campaign status, budget, and group targeting, which is why Path A (aligning an existing campaign) is the portable path and Path B (full creation) is the more platform-shaped one.


| Writable field                 | LinkedIn | Meta | Google Ads | TikTok | Microsoft |
| ------------------------------ | -------- | ---- | ---------- | ------ | --------- |
| Campaign status (pause/resume) | ✓        | ✓    | ✓          | ✓      | ✓         |
| Campaign budget                | ✓        | ✓    | ✓          | ✓      | ✓         |
| Group targeting                | ✓        | ✓    | ✓          | ✓      | ✓         |
| Employment `category`          | no       | ✓    | no         | no     | no        |
| Ad headline / copy             | ✓        | no   | headline   | copy   | ✓         |
| Creative title / body          | no       | ✓    | ✓          | ✓      | ✓         |
| Group optimization goal        | no       | ✓    | no         | ✓      | no        |
Table checked against [Unified.to](http://unified.to/)'s per-integration field support on 19 October 2026; it changes as integrations change, so check each integration's Feature Support tab. As of 19 October 2026, 27 advertising integrations accept writes on at least one object; the rest are read-only.


## Do job ads have targeting restrictions?


Yes. Employment ads are a restricted category, and on Meta, [Unified.to](http://unified.to/)'s campaign `category` field is where you declare it. Meta treats employment ads as a Special Ad Category, alongside housing, financial products and services, and social issues, elections or politics (as of 21 October 2026). Google restricts targeting on employment ads in the United States and Canada (no targeting by sex, age, parental or marital status, or ZIP code, as of 21 October 2026). Through [Unified.to](http://unified.to/) the `category` field is writable on Meta; on Google and other platforms you satisfy the equivalent requirement through their own campaign setup or the `raw` passthrough. Either way, do not populate the `demographic` targeting object (`age_min`, `age_max`, `male`, `female`) or tight radius targeting for a job ad, even though the fields exist in the model for non-restricted campaigns.


The practical rule when mapping a job to an ad: use the job's `addresses` for broad geographic targeting and its `groups[]`/`skills` for role context, but never map anything that resolves to a protected demographic characteristic. The unified model includes the full targeting surface; employment law, not the API, constrains which parts of it you may use here.


## How does closing a role pause its campaign automatically?


Subscribe to the ATS job webhook, and let a job update drive the campaign's lifecycle. When a role closes in the ATS, [Unified.to](http://unified.to/) delivers an `ats_job_updated` event with the changed record on supported integrations (job update events on 71 ATS integrations as of 20 October 2026, including Greenhouse, Lever, Ashby and Workable; virtual on most of them; native or virtual on Ashby); your handler reads the new `status` and pauses the campaign. The ATS job webhook is the campaign controller: the recruiter closes the role once, in the system they already use, and the paid campaign follows.


Subscribe to job updates:


```json
{
  "hook_url": "https://your-app.com/webhooks",
  "connection_id": "<ats_connection_id>",
  "object_type": "ats_job",
  "event": "updated"
}
```


On delivery, act on the status transition:


```typescript
// Inside your webhook handler, after sig256 validation.
// body.data is an array of ats_job records in the API's snake_case form.
for (const job of body.data) {
  const mapped = await findCampaignForJob(job.id); // your job → { adsConnectionId, campaignId, pausedByClose }
  if (!mapped) continue;

  if (job.status === 'CLOSED' || job.status === 'ARCHIVED') {
    // send status only; no campaign retrieve needed (LinkedIn has none)
    await sdk.ads.patchAdsCampaign({
      connectionId: mapped.adsConnectionId,
      id: mapped.campaignId,
      adsCampaign: { status: 'PAUSED' },
    });
    await markPausedByClose(job.id, true);
  } else if (job.status === 'OPEN' && mapped.pausedByClose) {
    // resume only campaigns this handler paused, not ones paused for review
    await sdk.ads.patchAdsCampaign({
      connectionId: mapped.adsConnectionId,
      id: mapped.campaignId,
      adsCampaign: { status: 'ACTIVE' },
    });
    await markPausedByClose(job.id, false);
  }
}
```


A closed role arrives as an `ats_job_updated` event where `status` moves to `CLOSED` (or `ARCHIVED`), carrying a `closed_at` timestamp, rather than as a separate event, so treat the status transition as the trigger. Where the ATS sends no [webhooks](/blog/replacing_polling_with_event_driven_integrations_using_virtual_webhooks), [Unified.to](http://unified.to/)'s [virtual webhooks](/blog/what_are_virtual_webhooks_how_they_differ_from_sync_based_webhooks) check at an interval you set, as often as every minute on paid plans, so the campaign pauses within one interval of the role closing (half an interval on average), not the instant the recruiter clicks close. Virtual webhooks deliver `created` and `updated` events and suit cases where a short delay is acceptable. Virtual webhooks retry failed deliveries with progressive backoff for up to about two weeks (native webhooks retry three times, then stop), so make your handler idempotent: a repeated delivery should not double-pause or error. Reopen the role and the same mechanism resumes the campaign; change the job's location and you re-run the targeting lookup and update the group.


## Which operations are supported on which integrations?


Write support varies by integration, and [Unified.to](http://unified.to/) shows the difference rather than hiding it. Campaign status writes (pause and resume) are supported across most advertising integrations (25 of 34 campaign integrations, as of 19 October 2026); where a specific write is not supported, [Unified.to](http://unified.to/) returns `501 Not Implemented` rather than failing silently.


Handle the differences explicitly:

- **Targeting** is group-level on most integrations; only some accept campaign-level targeting.
- **Budget and flight-date writes** are supported where the ad platform allows post-launch edits; some do not.
- **Advertising change events** exist on a few integrations, mostly virtual (StackAdapt group events are native, as of 20 October 2026): campaign events on Meta Ads, Google Display & Video 360 and Snapchat, among others, and ad events on Meta Ads and TikTok Ads, among others. This flow doesn't depend on them: the trigger is the ATS job event, available on supported ATS integrations.
- **`501`** **responses** mean the operation is unsupported for that integration. Do not retry them.
- **`429`** **responses** mean a rate limit was hit. Retry with exponential backoff and jitter; the SDK does not retry by default (retries are opt-in through `retryConfig`).

A minimal handler for both:


```typescript
import * as errors from '@unified-api/typescript-sdk/sdk/models/errors';

// 501: not supported on this integration, don't retry. 429: back off with jitter, then retry.
async function callWithBackoff<T>(call: () => Promise<T>, attempts = 5): Promise<T | undefined> {
  for (let i = 0; ; i++) {
    try {
      return await call();
    } catch (err) {
      if (err instanceof errors.UnifiedToError && err.statusCode === 501) return undefined;
      if (err instanceof errors.UnifiedToError && err.statusCode === 429 && i < attempts - 1) {
        const delay = Math.min(30_000, 1_000 * 2 ** i) * (0.5 + Math.random() / 2);
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }
      throw err;
    }
  }
}

// e.g. await callWithBackoff(() => sdk.ads.patchAdsCampaign({ connectionId, id, adsCampaign: { status: 'PAUSED' } }));
```


## Why run this through Unified.to instead of building both integrations?


Because the alternative is maintaining integrations on both the ATS side and the ad-platform side, and a mapping layer between them, per customer. [Unified.to](http://unified.to/) gives you a standard `AtsJob` across 162 ATS integrations and a consistent campaign, group, and targeting model across 39 advertising integrations, reached through one connection model. [Unified.to](http://unified.to/) keeps no customer records at rest; connection credentials and operational metadata are encrypted at rest. You map the job to the campaign once; on supported integrations, the ATS job event keeps them aligned after that.


## Building the flow: the sequence

1. Customer authorizes an ATS connection and an Advertising connection through the embedded authorization flow.
2. Read open roles with `listAtsJobs`, filtering on `status === 'OPEN'`.
3. Either map each job to an existing campaign (Path A), or create a campaign, group, and ad from the job with `createAdsAd` mapping `headline`, `ad_copy`, `cta`, and `final_url` (Path B).
4. Resolve the job's location with `listAdsTargets` and apply it group-level via `updateAdsGroup`.
5. Keep budget and schedule aligned with `patchAdsCampaign`, sending only the fields you change.
6. Subscribe to the `ats_job` `updated` webhook and pause the campaign when `status` moves to closed.
7. For job ads on Meta, set the campaign `category` to employment; elsewhere, meet the platform's employment-ad rules in its own setup. Keep targeting to broad geography, with no demographic targeting.
8. Handle `501` (unsupported, do not retry) and `429` (rate limit, back off) explicitly.

Related: [Build Cross-Platform Campaign Controls](https://unified.to/blog/build_cross_platform_campaign_controls_status_budgets_flight_dates_with_unified_advertising_api) · [Advertising API Integration](https://unified.to/blog/advertising_api_integration_real_time_campaign_management_and_cross_platform_performance_data) · [ATS API Integration](https://unified.to/blog/ats_api_integration_real_time_recruiting_data_candidate_pipelines_and_hiring_automation)


[Start a free 30-day trial](https://app.unified.to/) or [book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified).


## Frequently asked questions


**Does** [**Unified.to**](http://unified.to/) **store the job or campaign data?**
No. [Unified.to](http://unified.to/) reads and writes the ATS and ad platforms at request time and keeps no customer records at rest; connection credentials and operational metadata are encrypted at rest.


---


_Written by Mallory Greene._


_About the author: Mallory Greene is Head of Marketing at_ [_Unified.to_](http://unified.to/)_. She writes about integration infrastructure, unified APIs, and [MCP](/mcp) for technical teams. Based in Toronto._