---
title: "Connectors and apps"
url: /data/connectors
---

# Connectors and apps

> How a product talks to outside systems and lets outside software in: connectors with sync, conflicts and health; apps with scopes, consent and review.

A connector says how a product talks to one outside system. An app is outside software a Customer lets in, under scopes it consents to.

**A Customer connects its own system.** A Customer's admin.

1. Connector: from the catalog. Or your own: or a builder's.
2. Connect (consent): its sign-in.
3. Map (per Customer): objects, fields.
4. Backfill (first sync): from a cursor.
5. Syncing (each change): both ways. Or conflict: an Event.

The connection is a grant the admin revokes at any time.

## Connectors, one per outside system
A connector is the definition. An [Integration](/patterns/headless-saas.md#integrations) is one Customer's connection through it.

Connectors come from the platform's catalog, from your own file, or from another builder as an installed [extension](/identity/tenancy.md#families), checked like one.

| Family | What a connector declares, and the platform does |
| --- | --- |
| Auth | The system's OAuth or key flow; tokens kept as secrets in the Customer's own [Namespace](/reference/platform.md#namespace), refreshed before they expire, never shown |
| Objects | Each of the system's objects mapped to a Noun of the file, such as `accounts` to a Noun of customers, and each field by `map` |
| Sync modes | `read` in, `write` back, `both`, or `events` the system sends, verified and written as Events before anything runs |
| Cursor and backfill | Where the last sync stopped; a first sync backfills from a point in time, then reads changes only |
| Conflicts | Who wins a field both sides changed: `system`, `product`, `newest`, or `ask`, which opens a review; every value keeps its [Provenance](/patterns/dataset-api.md#how-a-field-is-filled) |
| Deletes | What a delete on the other side does here: `archive`, `delete` or `ignore` |
| Rate limits | The system's own limits respected per Customer, with backoff, so one Customer's sync never slows another's |
| Field maps per Customer | A Customer's admin maps its own custom fields to fields its extension adds, without a deploy of yours |
| Health and resync | Each connection's state, lag, errors and counts, and `Integration.resync` for one object or everything since a time |

## Apps, outside software inside a Customer's org
An app acts as its own [principal](/identity.md#families), always for one Customer org, through the same gate as every call.

| Family | What it does |
| --- | --- |
| Registration | A developer registers an app on the product: its name, redirect addresses, Watches and the scopes it asks for |
| Scopes from Verbs | Each scope is a set of Verbs and the Role they act under, such as `deals:read` or `Deal.win`; an app never gets more than the admin who installs it holds |
| Install consent | A Customer's admin installs an app for its org, sees each scope in words, and can narrow them |
| Review | `apps.review` says whether you review an app before any Customer but its maker's may install it |
| Caps per app | A call limit per app and per install, apart from the Customer's own, so one app cannot spend the Customer's limits |
| What an app sees | Its installs, its Watches' deliveries, its own calls, and refusals with cures like any caller |

### Example
Each Customer's own CRM, synced both ways for accounts and opportunities, with the newest change winning; and apps you review first.

```ts title="definition.ts"
import { define } from 'apis.dev'

/** Deals for each Customer, kicked off when won. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  sources: {
    crm: {
      system: 'crm', scope: 'customer', sync: 'both',
      objects: { accounts: 'Company', opportunities: 'Deal' },
      conflict: 'newest', deletes: 'archive',
    },
  },
  Company: {
    name:   { type: 'string', from: 'crm', map: 'name' },
    domain: { type: 'url!#',  from: 'crm', map: 'website' },
    deals: '<-Deal.company',
  },
  Deal: {
    title: 'string', amount: 'decimal(12,2)', company: '->Company.deals',
    stage: { open: [], qualify: 'opened', win: 'qualified', lose: ['opened', 'qualified'] },
  },
  apps: { review: 'you', calls: { day: 10000 } },
  offer: { Team: { month: 49, seats: 5, seat: 9 }, 'Deal.open': 0.001 },
})
```

A Customer's admin reads its connection's health.

```http title="Response"
HTTP/1.1 200 OK
Content-Type: application/json
X-Cost: price=0, currency="USD"
X-Request-Id: req_c41e7b02d98a3f65e0b9

{
  "id": "int_crm",
  "state": "syncing",
  "sync": "both",
  "cursor": "2026-10-27T08:55:00Z",
  "objects": {
    "accounts": { "read": 1240, "written": 18, "conflicts": 2 },
    "opportunities": { "read": 310, "written": 44, "conflicts": 0 }
  },
  "lag": "4m",
  "limit": { "used": 0.31, "resets": "2026-10-27T09:00:00Z" }
}
```

That is `GET /integrations/crm`, `getIntegration` over MCP and `pipeline integrations get crm` in the CLI.

## Connections and apps, state by state
Every move is a Verb a Customer's admin or its agent makes, and each writes its Event.

| Thing | States | What moves it |
| --- | --- | --- |
| A connection | `offered`, `connecting`, `backfilling`, `syncing`; or `degraded`, `paused`, `disconnected`, `revoked` | The admin's consent, the first sync, token refresh, the system's limits, `Integration.pause` and the admin's revoke |
| A sync run | `reading`, `writing`, `done`; or `partial`, `retrying`, `failed` | The cursor and the system's answers; a partial run says which objects it reached |
| A conflict | `found`, then `resolved` by the rule, or `asked` and then `resolved` by a person | The connector's `conflict` rule, or a reviewer's choice |
| A published connector | `draft`, `checked`, `published`, `deprecated` | `Connector.publish`; a new version is checked before it reaches any install |
| An app | `registered`, `in_review`, `approved`, `suspended`, `retired` | Its developer, your review on the [Operator plane](/trust/planes.md), and `App.suspend` |
| An install | `consented`, `active`, `narrowed`, `revoked` | A Customer's admin; a revoke ends every token the app holds for that org at once |

## What admins, developers and agents meet
- **A Customer's admin.** Connect its own system from the portal or the API, map its fields, watch its health, and revoke it.
- **A member.** Records that stay in step with the system it works in, and a conflict it can see and settle.
- **A developer.** Register an app, ask for scopes in Verbs, pass review, and reach every Customer who installs it.
- **An agent.** Connect, map, resync and revoke by Verb, and read the same health a person reads.

---

Every outside system meets the product through a connector, and every outside app through scopes made of Verbs, so both pass the one gate.