# OrgOrg API v1 — CRM — instructions for AI agents

This document describes the CRM endpoints of the OrgOrg v1 REST API. Fetch it from https://orgorg.com/docs/api/v1/crm/agents.md. The full API is documented at https://orgorg.com/docs/api/v1/agents.md.

## Basics

- Base URL: `https://orgorg.com`. All endpoint paths below are relative to it.
- Every request requires an `Authorization: Bearer <token>` header.
- `GET` endpoints take parameters in the URL query string. `POST` endpoints take a JSON body with a `Content-Type: application/json` header.
- All responses are JSON. A non-2xx status means the request failed; the body describes the problem.
- Endpoints are stable, but new response fields may be added without notice — ignore fields you do not recognize.

## Getting a token

Tokens are created by a signed-in user at https://orgorg.com/docs/credentials and inherit that user's permissions in their organization. If you do not have a token, ask your user to create one there. Verify a token works with:

```sh
curl "https://orgorg.com/api/v1/auth/test" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

A valid token returns `{ "ok": true, ... }`.

## CRM

Manage contacts, companies, deals, and customers, and read your deal and customer lifecycle stages. Human-readable docs: https://orgorg.com/docs/api/v1/crm

### List deal stages

`GET /api/v1/crm/deal-stages/list`

List your sales pipeline stages (keys, labels, and types) so integrations know which stage values exist. Requires the crm.stages:read scope.

Parameters: none.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/deal-stages/list" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    key: string;
    label: string;
    type: 'Open' | 'Won' | 'Lost';
    sortOrder: number;
    colorHex: string;
    description: string;
    probability: number;
  }[]
}
```

### List contacts

`GET /api/v1/crm/contacts/list`

Paginated contacts, newest first. Requires crm.contacts:read (or :write).

Parameters:

- `page` (number, optional) — Page number, starting at 1.
- `pageSize` (number, optional) — Rows per page, up to 200. Defaults to 50.
- `search` (string, optional) — Match against name, email, and company.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/contacts/list?page=1&pageSize=10" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: Contact[];
  page: number;
  pageSize: number;
  totalRows: number;
}
```

### Get contact

`GET /api/v1/crm/contacts/get`

One contact by id or email (one required; email is matched case-insensitively). Requires crm.contacts:read (or :write).

Parameters:

- `id` (string, optional) — Contact id.
- `email` (string, optional) — Contact email.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/contacts/get?email=jordan%40acme.com" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    id: string;
    firstName: string;
    lastName: string | null;
    email: string | null;
    phone: string | null;
    title: string | null;
    companyId: string | null;
    notes: string | null;
    tags: string[];
    createdAt: number;
    updatedAt: number;
  }
}
```

### Create contact

`POST /api/v1/crm/contacts/create`

Create a contact. A strict create: an email that already belongs to a contact returns 409 with that contact, so use contacts/update or contacts/upsert to change one. companyDomain links (creating if needed) the employer. Requires crm.contacts:write.

Parameters:

- `firstName` (string, required) — First name.
- `lastName` (string, optional) — Last name.
- `email` (string, optional) — Email. Case-insensitive. Must not belong to an existing contact.
- `title` (string, optional) — Job title.
- `phone` (string, optional) — Phone number.
- `notes` (string, optional) — Free-form notes.
- `companyDomain` (string, optional) — Company domain to link, for example acme.com. Pass companyId instead when the domain matches more than one company.
- `companyId` (string, optional) — Company id to link. Wins over companyDomain.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/contacts/create" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jordan",
    "lastName": "Lee",
    "email": "jordan@acme.com",
    "title": "Head of Ops",
    "companyDomain": "acme.com"
  }'
```

Response shape:

```ts
{
  data: Contact;
}
```

### Upsert contact

`POST /api/v1/crm/contacts/upsert`

Create or update a contact keyed by email. Idempotent: only the fields you send are written, so repeated calls enrich the contact as you learn more. companyDomain links (creating if needed) the employer. Requires crm.contacts:write.

Parameters:

- `email` (string, required) — The natural key. Case-insensitive.
- `firstName` (string, optional) — First name. Defaults from the email on create.
- `lastName` (string, optional) — Last name.
- `title` (string, optional) — Job title.
- `phone` (string, optional) — Phone number.
- `notes` (string, optional) — Free-form notes.
- `companyDomain` (string, optional) — Company domain to link, for example acme.com.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/contacts/upsert" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jordan@acme.com",
    "firstName": "Jordan",
    "title": "Head of Ops",
    "companyDomain": "acme.com"
  }'
```

Response shape:

```ts
{
  data: Contact;
  created: boolean;
}
```

### Update contact

`POST /api/v1/crm/contacts/update`

Update a contact by id. Only the fields you send change. Requires crm.contacts:write.

Parameters:

- `id` (string, required) — Contact id.
- `firstName` (string, optional) — First name.
- `lastName` (string, optional) — Last name.
- `email` (string, optional) — Email address.
- `title` (string, optional) — Job title.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/contacts/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "CONTACT_ID",
    "title": "VP of Ops"
  }'
```

Response shape:

```ts
{
  data: Contact;
  updated: true;
}
```

### Delete contact

`POST /api/v1/crm/contacts/delete`

Delete a contact by id. Requires crm.contacts:write.

Parameters:

- `id` (string, required) — Contact id.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/contacts/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "CONTACT_ID"
  }'
```

Response shape:

```ts
{
  data: { id: string };
  deleted: true;
}
```

### List companies

`GET /api/v1/crm/companies/list`

Paginated companies, newest first. Requires crm.companies:read (or :write).

Parameters:

- `page` (number, optional) — Page number, starting at 1.
- `pageSize` (number, optional) — Rows per page, up to 200. Defaults to 50.
- `search` (string, optional) — Match against the company name.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/companies/list?page=1&pageSize=10" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: Company[];
  page: number;
  pageSize: number;
  totalRows: number;
}
```

### Get company

`GET /api/v1/crm/companies/get`

One company by id or domain (one required; subdomains reduce to the registrable domain). Requires crm.companies:read (or :write).

Parameters:

- `id` (string, optional) — Company id.
- `domain` (string, optional) — Company domain, for example acme.com.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/companies/get?domain=acme.com" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    domain: string | null;
    industry: string | null;
    notes: string | null;
    logoUrl: string | null;
    createdAt: number;
    updatedAt: number;
  }
}
```

### Create company

`POST /api/v1/crm/companies/create`

Create a company. A strict create: a domain that already belongs to a company returns 409 with that company, so use companies/update or companies/upsert to change one. A personal-mail domain such as gmail.com is rejected. Requires crm.companies:write.

Parameters:

- `name` (string, required) — Company name.
- `domain` (string, optional) — Website domain, for example acme.com. Must not belong to an existing company.
- `industry` (string, optional) — Industry.
- `notes` (string, optional) — Free-form notes.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/companies/create" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme",
    "domain": "acme.com",
    "industry": "Software"
  }'
```

Response shape:

```ts
{
  data: Company;
}
```

### Upsert company

`POST /api/v1/crm/companies/upsert`

Create or update a company keyed by domain. Idempotent: only the fields you send are written, so repeated calls enrich the company. The name defaults to the bare domain on create. Requires crm.companies:write.

Parameters:

- `domain` (string, required) — The natural key, for example acme.com.
- `name` (string, optional) — Company name.
- `industry` (string, optional) — Industry.
- `notes` (string, optional) — Free-form notes.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/companies/upsert" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "acme.com",
    "name": "Acme",
    "industry": "Software"
  }'
```

Response shape:

```ts
{
  data: Company;
  created: boolean;
}
```

### Update company

`POST /api/v1/crm/companies/update`

Update a company by id. Only the fields you send change. Requires crm.companies:write.

Parameters:

- `id` (string, required) — Company id.
- `name` (string, optional) — Company name.
- `industry` (string, optional) — Industry.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/companies/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "COMPANY_ID",
    "industry": "Fintech"
  }'
```

Response shape:

```ts
{
  data: Company;
  updated: true;
}
```

### Delete company

`POST /api/v1/crm/companies/delete`

Delete a company by id. Requires crm.companies:write.

Parameters:

- `id` (string, required) — Company id.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/companies/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "COMPANY_ID"
  }'
```

Response shape:

```ts
{
  data: { id: string };
  deleted: true;
}
```

### List deals

`GET /api/v1/crm/deals/list`

Paginated deals, newest first. Filter by companyId or stage (key or label, case-insensitive). Requires crm.deals:read (or :write).

Parameters:

- `page` (number, optional) — Page number, starting at 1.
- `pageSize` (number, optional) — Rows per page, up to 200. Defaults to 50.
- `companyId` (string, optional) — Only deals linked to this company.
- `stage` (string, optional) — Only deals in this stage (key or label).

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/deals/list?page=1&pageSize=10" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: Deal[];
  page: number;
  pageSize: number;
  totalRows: number;
}
```

### Get deal

`GET /api/v1/crm/deals/get`

One deal by id. Requires crm.deals:read (or :write).

Parameters:

- `id` (string, required) — Deal id.

Example request:

```sh
curl -X GET "https://orgorg.com/api/v1/crm/deals/get?id=DEAL_ID" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN"
```

Response shape:

```ts
{
  data: {
    id: string;
    name: string;
    stage: string;
    stageLabel: string | null;
    amountCents: number | null;
    companyId: string | null;
    contactId: string | null;
    ownerEmail: string | null;
    expectedCloseDate: number | null;
    notes: string | null;
    externalId: string | null;
    createdAt: number;
    updatedAt: number;
  }
}
```

### Create deal

`POST /api/v1/crm/deals/create`

Create a deal. companyDomain and contactEmail resolve (creating if needed) the linked records, stage accepts a key or label case-insensitively (defaulting to your first open stage), and an externalId makes the call idempotent so event-driven callers can retry safely. Requires crm.deals:write.

Parameters:

- `name` (string, required) — Deal name.
- `stage` (string, optional) — Stage key or label, for example 'qualified'.
- `amountCents` (number, optional) — Deal value in cents.
- `companyDomain` (string, optional) — Company to link by domain, creating it if needed.
- `contactEmail` (string, optional) — Contact to link by email, creating it if needed.
- `ownerEmail` (string, optional) — Deal owner, by member email. Defaults to the token owner. Must be an active member of your org.
- `expectedCloseDate` (number, optional) — Expected close date, epoch milliseconds.
- `externalId` (string, optional) — Your idempotency key. Re-sending updates the same deal instead of duplicating it.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/deals/create" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme expansion",
    "stage": "qualified",
    "amountCents": 500000,
    "companyDomain": "acme.com",
    "contactEmail": "jordan@acme.com",
    "externalId": "crm-docs-example-1"
  }'
```

Response shape:

```ts
{
  data: Deal;
  created: boolean;
}
```

### Update deal

`POST /api/v1/crm/deals/update`

Update a deal by id, or by the externalId you created it with. Setting stage moves it through your pipeline (key or label, case-insensitive). Requires crm.deals:write.

Parameters:

- `id` (string, optional) — Deal id. Send id or externalId.
- `externalId` (string, optional) — The externalId you created the deal with. Send id or externalId.
- `stage` (string, optional) — Stage key or label to move to, for example 'Won'.
- `amountCents` (number, optional) — Deal value in cents.
- `name` (string, optional) — Deal name.
- `ownerEmail` (string, optional) — Reassign the deal owner, by member email. Must be an active member of your org.
- `reason` (string, optional) — Optional note recorded with a stage change, shown in the pipeline activity history.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/deals/update" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "DEAL_ID",
    "stage": "Won"
  }'
```

Response shape:

```ts
{
  data: Deal;
  updated: true;
}
```

### Record a deal signal

`POST /api/v1/crm/deals/signal`

Record something your product saw happen to a deal, such as a signup, a usage milestone, or a signed order, without moving its stage. The signal shows in the deal history and on the pipeline activity page with the token that sent it, counts as activity for auto-close, and is evidence the Pipe Manager weighs when it decides a move. Name the deal by id or by externalId. Requires crm.deals:write.

Parameters:

- `id` (string, optional) — Deal id. Send id or externalId.
- `externalId` (string, optional) — The externalId you created the deal with. Send id or externalId.
- `title` (string, required) — What happened, in a sentence of up to 200 characters, for example 'Invited 5 teammates'.
- `detail` (string, optional) — More context, up to 2,000 characters.
- `occurredAt` (number, optional) — When it happened, in milliseconds since 1970. Defaults to now.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/deals/signal" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "DEAL_ID",
    "title": "Invited 5 teammates"
  }'
```

Response shape:

```ts
{
  data: { id: string; dealId: string; title: string; occurredAt: number };
  created: true;
}
```

### Delete deal

`POST /api/v1/crm/deals/delete`

Delete a deal by id. Requires crm.deals:write.

Parameters:

- `id` (string, required) — Deal id.

Example request:

```sh
curl -X POST "https://orgorg.com/api/v1/crm/deals/delete" \
  -H "Authorization: Bearer $ORGORG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "DEAL_ID"
  }'
```

Response shape:

```ts
{
  data: { id: string };
  deleted: true;
}
```
