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.
- 01
One origin
Discovery, OAuth and the API on one canonical origin that does not redirect. Tokens never travel to a second host.
- 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.
- 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.
- 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.
- 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.
- 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.