Skip to content

Developers

Build an app that works with company context.

Build a native app whose records live in Company OS, or connect an existing product through Connect v1. Native apps define tables, metrics and UI in this repository. Partner connectors keep their own data and import supported records. Use the 7 native manifests and mappings for 10 shared tables as implementation examples.

Two ways in

Choose where your app’s records live.

Both appear in the App Store with their publisher and permissions. Native apps can have a separate price; partner products can require their own subscription.

Native app

A manifest, compiled in.

Write a manifest and, if it needs one, a home surface on the app chassis: the frame, record table, record page, activity feed, metrics and settings tabs every native app already uses. Open a pull request against the open source web app. Once it lands, the app is in the store for every company.

Best for an app whose records live in Company OS: a help desk, an inventory, a hiring pipeline.

Partner app

Your product, speaking Connect v1.

Your product keeps its own accounts and data. It exposes a discovery document, OAuth with PKCE and a snapshot endpoint; Company OS installs it with a permissions screen, syncs it with a receipt for every run, and a provides spec turns your objects into rows in the company’s tables.

Best for a product a company already runs: a CRM, a task tool, a form builder, a deployment platform.

The manifest

Everything an app is, as data.

The React half lives beside it, keyed by id, so a manifest can be read on the server and by agents without pulling in UI. The test suite runs every rule below over the registry; a manifest that breaks one fails the build, not a company's install.

id
Kebab-case, stable. Also the namespace for capabilities and events.
title, summary
The name and the one line on the store card. No dashes, no slogans.
category
product, customer, operations, sales, finance, people, marketing or company.
version
A positive integer. Raising it offers an update to every company that installed an earlier one; an update only adds, never removes.
depends
Apps installed first and uninstalled together.
view
The department shelf the app's tables and pages sit on.
datasets
The tables it keeps: slug, title, description and typed columns. Column ids are kebab-case.
metrics
Readings seeded on install, each bound to a column with an aggregate, an optional window and filter, and a target.
capabilities, events
Namespaced id:verb and id.event, granted to roles and agent keys like every other capability.
kinds
Typed pages the app owns, registered in the same registry as the company's own.
export const helpDesk: AppManifest = {
  id: "help-desk",
  title: "Help desk",
  category: "customer",
  summary: "Tickets from customers, with the response time held to a target.",
  version: 1,
  depends: [],
  view: "customer",
  kinds: [],
  datasets: [{ slug: "tickets", title: "Tickets", columns: [ /* … */ ] }],
  metrics: [{
    slug: "open-tickets", title: "Open tickets", unit: "tickets",
    direction: "lower-is-better", cadence: "daily",
    binding: { dataset: "tickets", column: "subject", aggregate: "count",
               filterColumn: "status", filterValue: "open" },
  }],
  capabilities: ["help-desk:resolve"],
  events: ["help-desk.ticket_opened"],
};

What a partner app provides

Map source records to company tables.

A sync preserves source objects as versioned snapshots. A provides spec declares, per object type, the table those objects become rows of and how each column is read out of your payload: candidate paths in order of preference, and a map from your words to the table's options. A row is written only when the required columns could be read. Nothing is guessed.

{
  provider: "operate",
  objectType: "task",
  dataset: { slug: "tasks", title: "Tasks", view: "operations", columns: [ /* … */ ] },
  columns: [
    { column: "title",  from: ["title", "name"] },
    { column: "status", from: ["status.name", "status"],
      map: { "in_progress": "in progress", "closed": "done" } },
    { column: "due",    from: ["dueDate", "due_date", "dueAt"] },
  ],
  metrics: [ /* seeded over the table on first sync */ ],
}

After every successful sync the materializer walks your current sources and writes the rows, authored by your product and referenced to the source object. A source that disappears tombstones its row; an unchanged one writes nothing; the table is created on first use and its metrics seeded beside it.

A synced row is a row like any other: on a department shelf, counted by metrics, citable in a decision, searched by ⌘K, and carrying its history. That is what makes yours an app rather than an import.

The specs that ship today are in lib/apps/provides.ts. Yours is filed with your submission.

Connect v1

Five endpoints and a discovery document.

Authorization code with PKCE for the person installing, and a small read-only API for the sync. The full contract, including the crash-safe grant recovery every provider implements, is in the repository under docs.

/.well-known/companyos-connector
The discovery document. Every endpoint on it lives on the same canonical, non-redirecting origin.
/oauth/authorize, /oauth/token, /oauth/revoke
Authorization code with PKCE (S256 only), exact redirect URI, one-use state, access tokens of an hour or less, rotating single-use refresh tokens with reuse detection.
/api/companyos/v1/account
Who approved, and which tenant. The tenant comes from the token, never from a header.
/api/companyos/v1/installation
PUT binds Company OS's installation id to the grant; DELETE disconnects it and revokes the grant, with a receipt that says so.
/api/companyos/v1/snapshot
A complete, paged, tenant-bound export with a stable checkpoint per traversal. Every object carries an id, a version, an updatedAt and its data.

Discovery

GET /.well-known/companyos-connector

{
  "protocolVersion": "1",
  "provider": "your-product",
  "issuer": "https://www.your-product.com",
  "authorizationEndpoint": "https://www.your-product.com/oauth/authorize",
  "tokenEndpoint": "https://www.your-product.com/oauth/token",
  "revocationEndpoint": "https://www.your-product.com/oauth/revoke",
  "accountEndpoint": "https://www.your-product.com/api/companyos/v1/account",
  "installationEndpoint": "https://www.your-product.com/api/companyos/v1/installation",
  "snapshotEndpoint": "https://www.your-product.com/api/companyos/v1/snapshot",
  "scopes": ["companyos:account:read", "companyos:data:read"],
  "objectTypes": ["project", "task"]
}

Snapshot

GET /api/companyos/v1/snapshot?cursor=<opaque>&limit=200
Authorization: Bearer <access token>

{
  "objects": [{
    "type": "task", "id": "task_123", "version": "2026-08-30T15:00:00.000Z",
    "updatedAt": "2026-08-30T15:00:00.000Z",
    "sourceUrl": "https://www.your-product.com/t/123",
    "data": { "title": "Sign the landlord", "status": "in_progress" }
  }],
  "nextCursor": null,
  "checkpoint": "opaque_sync_checkpoint"
}

Check it before we do

The reviewer's checklist, as a command.

The checker fetches your discovery document and, with a token, walks your snapshot endpoint the way Company OS does. Every rule it holds is one the review holds, worded the same way, so passing it locally is most of passing review. It exits non-zero on any failure and belongs in your CI.

$ npm run connector:check -- https://www.your-product.com --token cos_pilot_…

PASS  https://www.your-product.com/.well-known/companyos-connector
PASS  snapshot page 1 (143 objects)
PASS  snapshot page 2 (57 objects)

It is in the open source web app under scripts/check-connector.mjs, with no dependencies beyond Node. Clone the repository and run it against a preview deployment while you build.

Review

Six things a listing has to be.

Review is done by a person at Company OS, against your production origin. You hear back with a state and a note on your submission page: in review, changes requested, approved, or declined with the reason.

  1. 01

    One origin

    Discovery, OAuth and the API on one canonical origin that does not redirect. Tokens never travel to a second host.

  2. 02

    The grant is safe to lose

    PKCE, rotating refresh tokens, reuse detection, and the replay receipts Connect v1 specifies, so a crash between your token endpoint and our store never leaves a live token nobody holds.

  3. 03

    Explicit permissions

    Describe each permission and its purpose before approval. Snapshot sync reads source data; integrations that create tasks or send events must explain those actions separately.

  4. 04

    The snapshot holds

    The checker passes on your production origin: stable checkpoints, cursors that do not loop, timestamps that advance, and data that can be reprocessed without a second fetch.

  5. 05

    Something changes in the company

    A provides spec maps at least one object type to a table, with every required column readable from your payload. An app that only lands evidence is not yet an app.

  6. 06

    The listing reads like a person wrote it

    A name, a line under ninety characters, a description that says what arrives and where, and a logo that survives a light tile. The copy rules the product holds itself to apply to your listing.

Submit

Submit your app for review.

Sign in, name the app, give the discovery URL or the pull request, and say in a paragraph what arrives and where. You will see the state of the review on the same page. Include any separate account, subscription or setup requirements in the listing.

Give your team a shared starting point.

Start free with three members and two agent keys. Add your first company document today.