# CommsHarbor — full API reference > Generated from the catalog at https://commsharbor.com · build `074a2a11` > 151 endpoints · 74 structures > Short index: https://commsharbor.com/llms.txt · Spec: https://commsharbor.com/openapi.json · MCP: https://commsharbor.com/mcp > Transactional email and permission-based marketing with tenant isolation. > English is the default product language; the human UI is also available in pt-BR. ## How to read - Every endpoint lists path, auth, parameters, body, response structure, errors and a call that runs. - `Pagina` is a reference: the fields are under **Structures**, at the end, once. - `(optional)` on a field means it may be absent; `(may be null)` means it comes with a null value. - Slice what you need: `https://commsharbor.com/llms-full.txt?prefix=/api/` returns only that branch. ## Authentication - `credito` — Prepaid credit token in `Authorization: Bearer cred_…` (or the `X-Credito` header). Not an account: it is a bearer of balance. - `none` — Public endpoint. No tenant data is returned. - `session` — Bearer session or secure session cookie. No organization is implied. - `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. - `organization_template_write` — Active organization identity with template:write permission. - `organization_messages_send` — Active organization identity with messages:send permission. Idempotency-Key is mandatory. - `organization_campaign_write` — Active organization identity with campaign:write permission. Campaign launches require Idempotency-Key. - `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. - `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. - `aws_sns` — Amazon SNS signature, regional certificate URL and the exact configured TopicArn. This callback never accepts a user credential. - `preference_capability` — Signed, expiring capability scoped to one organization and contact. No login is required and no email address is embedded in the token. ## Endpoints ## Discovery ### `GET /okf/:arquivo` OKF bundle (Open Knowledge Format v0.1): markdown with frontmatter so an agent reads the whole product without parsing HTML. - **URL:** `https://commsharbor.com/okf/:arquivo` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Path parameters** - `arquivo` (string, required) — `index.md`, `sobre.md`, `api.md` or `faq.md`. e.g.: `index.md`. **Response `200`** `text/markdown`. Start at `/okf/index.md`, which lists the bundle. **Errors** - `404` — File outside the bundle. **Example** ```sh curl -s https://commsharbor.com/okf/index.md ``` ### `GET /.well-known/:arquivo` Machine discovery before the home page: `api-catalog` (RFC 9727, a linkset with the API and the MCP), `security.txt` (RFC 9116) and `mcp-registry-auth` (the official MCP registry key). - **URL:** `https://commsharbor.com/.well-known/:arquivo` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Path parameters** - `arquivo` (string, required) — `api-catalog`, `security.txt`, `mcp-registry-auth` or `apis.json`. e.g.: `api-catalog`. **Response `200`** `application/linkset+json` for the api-catalog; `text/plain` for the other two. **Errors** - `404` — Name outside the four published. **Example** ```sh curl -s https://commsharbor.com/.well-known/api-catalog ``` ### `GET /apis.json` APIs.json (apisjson.org, 0.19): the index APIs.io harvests — the API, the MCP, OpenAPI, guide and OKF bundle in one file. Also at `/.well-known/apis.json`. - **URL:** `https://commsharbor.com/apis.json` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Response `200`** `application/json` in the APIs.json 0.19 format: `apis[]` with `baseURL`, `humanURL` and `properties[]`. **Example** ```sh curl -s https://commsharbor.com/apis.json ``` ### `GET /api/` Discover the whole API: every endpoint, every auth mode and the MCP endpoint. - **URL:** `https://commsharbor.com/api/` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Response `200`** Structure: `ApiIndex`. - `name` (string) — Product name. - `description` (string) — What the product does, in one sentence. - `build` (string) — Deployed commit. - `environment` (string) — Which environment answered: `production`, `staging` or `local`. - `base_url` (string) — Origin this API is served from. - `docs` (object) — Links to llms.txt, llms-full.txt, openapi.json, the MCP endpoint and the human UI. - `auth` (object) — Every authentication mode and how to obtain it. - `endpoints` (object[]) — Every endpoint with method, path, auth, absolute URL and what it returns. - `mcp` (object) — Remote MCP endpoint and transport. - `mcp_tools` (string[]) — Name of every MCP tool. ### `GET /api/health` Read liveness, environment and the commit currently deployed. Smoke tests wait for their own commit to appear here instead of racing edge propagation. - **URL:** `https://commsharbor.com/api/health` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Response `200`** Structure: `Health`. - `ok` (bool) — Always true when the Worker answers. - `app` (string) — Product name. - `build` (string) — Deployed commit; CI passes the short SHA at deploy time. - `environment` (string) — Which environment answered. - `timestamp` (string) — Response time (UTC, ISO-8601). ### `GET /mcp` Inspect the remote MCP server card: transport, protocol and tool count. `POST /mcp` speaks Streamable HTTP with JSON-RPC 2.0. The tools are the operations in this catalog; the MCP has no backend of its own. - **URL:** `https://commsharbor.com/mcp` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Response `200`** - `transport` (string) — How the server speaks — `streamable-http`. - `protocol_version` (string) — MCP protocol version the server implements. - `endpoint` (string) — Absolute URL to POST JSON-RPC to. - `server` (object) — `name` and `version` of this server; the version is the deployed commit. - `tools` (int) — How many tools the server exposes. - `usage` (string) — One line saying how to call it. - `api_index` (string) — The self-describing API index, for the operations behind the tools. ## Account ### `POST /api/auth/start` Send a one-time login code to an email address. - **URL:** `https://commsharbor.com/api/auth/start` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Body** (`application/json`) - `email` (string, required) — Where to send the code. **Body example** ```json { "email": "owner@example.com" } ``` **Response `200`** - `sent` (bool) — Whether the message was accepted for delivery. - `expires_in` (int) — Seconds until the code stops working. **Errors** - `400` — Missing or malformed email. - `429` — Too many codes requested for this address. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/auth/start -H 'content-type: application/json' -d '{"email":"owner@example.com"}' ``` ### `POST /api/auth/verify` Exchange a one-time code for a session. - **URL:** `https://commsharbor.com/api/auth/verify` - **Auth:** `none` — Public endpoint. No tenant data is returned. **Body** (`application/json`) - `email` (string, required) — The address the code was sent to. - `code` (string, required) — The six digits from the email. **Body example** ```json { "email": "owner@example.com", "code": "123456" } ``` **Response `200`** - `user` (User) — The person who just signed in. → see `User` under **Structures**. - `session_token` (string) — Bearer token for `Authorization`. It implies no organization on its own. **Errors** - `400` — Wrong or expired code. - `429` — Too many attempts. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/auth/verify -H 'content-type: application/json' -d '{"email":"owner@example.com","code":"123456"}' ``` ### `POST /api/auth/logout` Revoke the session in use right now. - **URL:** `https://commsharbor.com/api/auth/logout` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Response `200`** Structure: `Ok`. - `ok` (bool) — Always true — failures arrive as a 4xx/5xx status, never as `ok:false`. **Errors** - `401` — No session, or the session expired. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/auth/logout -H "Authorization: Bearer $TOKEN" ``` ### `GET /api/me` Read the profile, every membership and the platform role, if any. This is how a client discovers which organizations it may pass in `X-Organization-Id`. - **URL:** `https://commsharbor.com/api/me` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Response `200`** - `user` (User) — The person behind the session. → see `User` under **Structures**. - `profile` (Profile) — Personal preferences — they belong to the person, not to a tenant. → see `Profile` under **Structures**. - `organizations` (Organization[]) — Every organization this person belongs to. → see `Organization` under **Structures**. - `platform_role` (string, may be null) — Platform role, when one was explicitly granted. Owning a tenant grants nothing here. **Errors** - `401` — No session, or the session expired. **Example** ```sh curl -s https://commsharbor.com/api/me -H "Authorization: Bearer $TOKEN" ``` ### `PATCH /api/me` Update the display name or language of the current profile. - **URL:** `https://commsharbor.com/api/me` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Body** (`application/json`) - `display_name` (string) — Name shown in the interface. - `locale` (string) — Preferred language, e.g. `en` or `pt-br`. **Body example** ```json { "display_name": "A. User", "locale": "en" } ``` **Response `200`** Structure: `Profile`. - `display_name` (string, may be null) — Name shown in the interface. - `locale` (string, may be null) — Preferred language, e.g. `en` or `pt-br`. **Errors** - `400` — No updatable field in the body. - `401` — No session, or the session expired. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/me -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"locale":"pt-br"}' ``` ## Organizations ### `GET /api/organizations` List the organizations the current person belongs to. - **URL:** `https://commsharbor.com/api/organizations` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Response `200`** - `organizations` (Organization[]) — Every organization with a membership for this person. → see `Organization` under **Structures**. **Errors** - `401` — No session, or the session expired. **Example** ```sh curl -s https://commsharbor.com/api/organizations -H "Authorization: Bearer $TOKEN" ``` ### `POST /api/organizations` Create one organization trial for a verified owner and a sending domain. One trial per owner. The domain is registered here but not verified — DNS state is only ever observed, in the domains resource. - **URL:** `https://commsharbor.com/api/organizations` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Body** (`application/json`) - `name` (string, required) — Display name of the organization. - `sending_domain` (string, required) — Domain the organization will send from. **Body example** ```json { "name": "Acme", "sending_domain": "example.com" } ``` **Response `200`** Structure: `Organization`. - `id` (string) — Organization ID, used in every tenant route. - `name` (string) — Display name. - `sending_domain` (string, may be null) — Domain the organization sends from. - `created_at` (string) — Creation time (UTC, ISO-8601). **Errors** - `400` — Missing name or domain. - `401` — No session, or the session expired. - `409` — This owner already has a trial organization. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"name":"Acme","sending_domain":"example.com"}' ``` ### `GET /api/context` Resolve which organization and role the current credential is acting as. Read this before a write when you are not certain which tenant is active. Guessing is how data lands in the wrong organization. - **URL:** `https://commsharbor.com/api/context` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Headers** - `X-Organization-Id` (string, required) — Which organization to act in. Required with a human session; an API key determines its own and rejects a conflicting value. **Response `200`** Structure: `Identity`. - `kind` (string) — How the identity was resolved. - `organizationId` (string) — The active organization. - `userId` (string, optional) — The person, when a session is in play. - `email` (string, optional) — The person's e-mail, on a session. - `sessionId` (string, optional) — Session identifier, on a session. - `role` (string, optional) — Role inside that organization; sessions only. - `organization` (object, optional) — `{ id, name, slug }` of the active organization; sessions only. - `apiKeyId` (string, optional) — Which key answered, on an API key. - `scopes` (string[], optional) — Granted scopes; present on an API key, which carries its own narrower set. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/context -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id` Read one organization. - **URL:** `https://commsharbor.com/api/organizations/:organization_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Response `200`** Structure: `Organization`. - `id` (string) — Organization ID, used in every tenant route. - `name` (string) — Display name. - `sending_domain` (string, may be null) — Domain the organization sends from. - `created_at` (string) — Creation time (UTC, ISO-8601). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id` Update an organization's name. - **URL:** `https://commsharbor.com/api/organizations/:organization_id` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `name` (string, required) — New display name. **Body example** ```json { "name": "Acme Global" } ``` **Response `200`** Structure: `Organization`. - `id` (string) — Organization ID, used in every tenant route. - `name` (string) — Display name. - `sending_domain` (string, may be null) — Domain the organization sends from. - `created_at` (string) — Creation time (UTC, ISO-8601). **Errors** - `400` — Empty name. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/$ORG -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"name":"Acme Global"}' ``` ### `GET /api/organizations/:organization_id/members` List the members of an organization and their roles. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/members` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Response `200`** - `members` (Membership[]) — Every member and the role they hold. → see `Membership` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/members -H "Authorization: Bearer $TOKEN" ``` ### `PATCH /api/organizations/:organization_id/members/:user_id` Change the role of a member who is not the owner. The owner's role cannot be changed through this route — an organization that can lose its last owner is an organization nobody can administer. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/members/:user_id` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `user_id` (string, required) — Member user ID, as returned by the members listing. **Body** (`application/json`) - `role` (string, required) — Role to give the member from now on. Values: `admin`, `marketer`, `viewer`. **Body example** ```json { "role": "marketer" } ``` **Response `200`** Structure: `Membership`. - `organization_id` (string) — The organization. - `user_id` (string) — The member. - `role` (string) — What the member may do. - `created_at` (string) — When the membership started (UTC). **Errors** - `400` — Unknown role. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/$ORG/members/$USER -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"role":"marketer"}' ``` ### `GET /api/organizations/:organization_id/invitations` List invitations without their tokens. The token is shown once, when the invitation is created. Listing never shows it again. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/invitations` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Response `200`** - `invitations` (Invitation[]) — Invitations for this organization, without tokens. → see `Invitation` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/invitations -H "Authorization: Bearer $TOKEN" ``` ### `POST /api/organizations/:organization_id/invitations` Create an invitation and reveal its token exactly once. Store the token now: this is the only response that carries it. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/invitations` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `email` (string, required) — Who to invite. - `role` (string, required) — Role the invitation grants. Values: `admin`, `marketer`, `viewer`. **Body example** ```json { "email": "member@example.com", "role": "viewer" } ``` **Response `200`** Structure: `Invitation`. - `id` (string) — Invitation ID. - `email` (string) — Who is being invited. - `role` (string) — Role the invitation grants. - `expires_at` (string) — When the invitation stops working (UTC). - `invitation_token` (string, optional) — The invitation token. Present only on the response that created it. - `token_notice` (string, optional) — Reminder that the token is shown once. **Errors** - `400` — Missing email or unknown role. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/invitations -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"email":"member@example.com","role":"viewer"}' ``` ### `POST /api/invitations/accept` Accept an invitation and join the organization it points at. Accepting joins an EXISTING organization trial. It never creates one, so an invitation cannot be used to mint tenants. - **URL:** `https://commsharbor.com/api/invitations/accept` - **Auth:** `session` — Bearer session or secure session cookie. No organization is implied. **Body** (`application/json`) - `token` (string, required) — The invitation token, from the response that created it. **Body example** ```json { "token": "invite_…" } ``` **Response `200`** Structure: `Membership`. - `organization_id` (string) — The organization. - `user_id` (string) — The member. - `role` (string) — What the member may do. - `created_at` (string) — When the membership started (UTC). **Errors** - `400` — Missing token. - `401` — No session, or the session expired. - `404` — Unknown, expired or already used invitation. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/invitations/accept -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"token":"invite_…"}' ``` ## Credentials ### `GET /api/organizations/:organization_id/api-keys` List API keys without token hashes or secrets. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/api-keys` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Response `200`** - `api_keys` (ApiKey[]) — Keys of this organization, without the tokens. → see `ApiKey` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/api-keys -H "Authorization: Bearer $TOKEN" ``` ### `POST /api/organizations/:organization_id/api-keys` Create a scoped API key and reveal it exactly once. The key determines its own organization. It cannot manage credentials or membership — those stay with human admins. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/api-keys` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `name` (string, required) — Label so the key can be recognised later. - `scopes` (string[], required) — What the key may do, e.g. `organization:read`, `crm:write`, `messages:send`. **Body example** ```json { "name": "Automation", "scopes": [ "organization:read", "crm:read", "crm:write" ] } ``` **Response `200`** Structure: `ApiKey`. - `id` (string) — API key ID, used to revoke it. - `name` (string) — Label chosen at creation. - `scopes` (string[]) — What this key may do, e.g. `crm:read`, `messages:send`. - `prefix` (string) — Token prefix, so a key can be recognised in a list. - `last_used_at` (string, may be null) — When the key last answered; null if never. - `revoked_at` (string, may be null) — When it was revoked; null while it is still valid. - `created_at` (string) — Creation time (UTC). - `token` (string, optional) — The key itself. Present only on the response that created it — it is never shown again. **Errors** - `400` — Missing name or unknown scope. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/api-keys -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"name":"Automation","scopes":["crm:read"]}' ``` ### `DELETE /api/organizations/:organization_id/api-keys/:api_key_id` Revoke an API key. It stops working immediately, not at the next cache expiry. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/api-keys/:api_key_id` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `api_key_id` (string, required) — API key ID from the key listing. The token itself is never returned again. **Response `200`** Structure: `ApiKey`. - `id` (string) — API key ID, used to revoke it. - `name` (string) — Label chosen at creation. - `scopes` (string[]) — What this key may do, e.g. `crm:read`, `messages:send`. - `prefix` (string) — Token prefix, so a key can be recognised in a list. - `last_used_at` (string, may be null) — When the key last answered; null if never. - `revoked_at` (string, may be null) — When it was revoked; null while it is still valid. - `created_at` (string) — Creation time (UTC). - `token` (string, optional) — The key itself. Present only on the response that created it — it is never shown again. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/$ORG/api-keys/$KEY -H "Authorization: Bearer $TOKEN" ``` ### `GET /api/organizations/:organization_id/audit` List the append-only audit trail of the organization. Audit records are never rewritten and never carry recipient PII. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audit` - **Auth:** `organization_admin` — Human organization member with the required role; API keys cannot manage credentials or membership. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `PageCamel`. - `items` (AuditEvent[]) — The records on this page. → see `AuditEvent` under **Structures**. - `nextCursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s 'https://commsharbor.com/api/organizations/$ORG/audit?limit=20' -H "Authorization: Bearer $TOKEN" ``` ### `GET /api/platform/context` Confirm an explicitly granted platform administrator. Tenant ownership grants nothing here: platform access is a separate, explicit grant. - **URL:** `https://commsharbor.com/api/platform/context` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Response `200`** - `role` (string) — The platform role that was granted. - `user_id` (string) — Who holds it. **Errors** - `401` — No session. - `403` — This person has no platform grant. **Example** ```sh curl -s https://commsharbor.com/api/platform/context -H "Authorization: Bearer $TOKEN" ``` ## Domains ### `GET /api/organizations/:organization_id/domains` List the sending domains of the organization and their last observed state. `status` is what was last OBSERVED at SES and DNS. It does not become `active` because provisioning was requested. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Response `200`** Structure: `Lista`. - `items` (Domain[]) — Every record in the collection. → see `Domain` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/domains -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/domains` Register the organization's sending domain and queue idempotent SES provisioning. Asking twice does not provision twice. Publish the returned DKIM records, then call `verify` to have the state observed. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `domain` (string, required) — The domain to send from, e.g. `example.com`. **Body example** ```json { "domain": "example.com" } ``` **Response `200`** - `domain` (Domain) — The registered domain, with the DKIM records to publish. → see `Domain` under **Structures**. - `provisioning` (Provisioning) — Whether this call enqueued provisioning work. → see `Provisioning` under **Structures**. **Errors** - `400` — Missing or malformed domain. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/domains -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"domain":"example.com"}' ``` ### `GET /api/organizations/:organization_id/domains/:domain_id` Read one sending domain, without inferring current DNS state. This returns what was stored at the last observation. To look again, call `verify`. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains/:domain_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `domain_id` (string, required) — Sending domain ID, from the domains listing. **Response `200`** Structure: `Domain`. - `id` (string) — Domain ID. - `domain` (string) — The domain itself, e.g. `example.com`. - `status` (string) — Last observed state. - `dkim_tokens` (string[]) — CNAME records to publish for DKIM. - `dmarc` (object, may be null) — DMARC policy observed at the domain. - `mail_from` (object, may be null) — Custom MAIL FROM subdomain and its observed state. - `last_observed_at` (string, may be null) — When SES and DNS were last actually checked (UTC). - `created_at` (string) — Registration time (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/domains/$DOMAIN -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/domains/:domain_id/verify` Observe SES, DKIM, DMARC and custom MAIL FROM state right now, and store what was seen. This is the ONLY operation that can move a domain to `active`, and only because it actually looked. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains/:domain_id/verify` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `domain_id` (string, required) — Sending domain ID, from the domains listing. **Body example** ```json {} ``` **Response `200`** Structure: `Domain`. - `id` (string) — Domain ID. - `domain` (string) — The domain itself, e.g. `example.com`. - `status` (string) — Last observed state. - `dkim_tokens` (string[]) — CNAME records to publish for DKIM. - `dmarc` (object, may be null) — DMARC policy observed at the domain. - `mail_from` (object, may be null) — Custom MAIL FROM subdomain and its observed state. - `last_observed_at` (string, may be null) — When SES and DNS were last actually checked (UTC). - `created_at` (string) — Registration time (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/domains/$DOMAIN/verify -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{}' ``` ### `POST /api/organizations/:organization_id/domains/:domain_id/smoke` Queue one controlled smoke message to the server-side QA recipient. The request never accepts a recipient: the destination is a server-side secret. That is what keeps this from becoming a way to send mail to arbitrary addresses through someone else's verified domain. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains/:domain_id/smoke` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `domain_id` (string, required) — Sending domain ID, from the domains listing. **Body example** ```json {} ``` **Response `200`** - `delivery` (Delivery) — The delivery record for the smoke. → see `Delivery` under **Structures**. - `capacity` (Capacity) — What is left to send after it. → see `Capacity` under **Structures**. - `dispatch` (Dispatch) — The queue work behind it. → see `Dispatch` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `409` — The domain is not active yet — verify it first. - `429` — No capacity left. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/domains/$DOMAIN/smoke -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{}' ``` ## Deliveries ### `GET /api/organizations/:organization_id/domains/:domain_id/deliveries` List deliveries sent from one domain, without recipient addresses. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains/:domain_id/deliveries` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `domain_id` (string, required) — Sending domain ID, from the domains listing. **Response `200`** Structure: `Lista`. - `items` (Delivery[]) — Every record in the collection. → see `Delivery` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/domains/$DOMAIN/deliveries -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/deliveries/:delivery_id` Read one delivery and its SES MessageId, without recipient data. `ses_message_id` is what correlates this record with AWS when you need to chase a message there. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/deliveries/:delivery_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `delivery_id` (string, required) — Delivery ID returned when the message was queued. **Response `200`** Structure: `Delivery`. - `id` (string) — Delivery ID. - `status` (string) — Delivery state, authoritative in D1. - `domain_id` (string) — Domain it was sent from. - `template_id` (string, may be null) — Template used. - `template_version` (int, may be null) — Immutable template version used. - `ses_message_id` (string, may be null) — SES MessageId, for correlating with AWS. - `created_at` (string) — When it was queued (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/deliveries/$DELIVERY -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/deliveries/:delivery_id/events` List the normalized SES feedback events for one delivery. Open and Click are ADDITIVE: they are recorded alongside delivery state and never overwrite it. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/deliveries/:delivery_id/events` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `delivery_id` (string, required) — Delivery ID returned when the message was queued. **Response `200`** Structure: `Lista`. - `items` (DeliveryEvent[]) — Every record in the collection. → see `DeliveryEvent` under **Structures**. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/deliveries/$DELIVERY/events -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/aws/sns` Receive a signed Amazon SNS callback for the configured SES topic. This callback never accepts a user credential. It requires a valid SNS signature, a certificate URL in the expected AWS region, and the exact configured TopicArn — anything else is rejected before the body is read. - **URL:** `https://commsharbor.com/api/aws/sns` - **Auth:** `aws_sns` — Amazon SNS signature, regional certificate URL and the exact configured TopicArn. This callback never accepts a user credential. **Response `200`** `204 No Content` when the notification was accepted. **Errors** - `400` — Malformed notification, or a certificate URL outside the expected region. - `403` — Bad signature, or a TopicArn other than the configured one. **Example** ```sh # Amazon SNS posts this; a hand-made call is rejected at the signature check. curl -s -XPOST https://commsharbor.com/api/aws/sns -H 'x-amz-sns-message-type: Notification' -H 'content-type: text/plain; charset=UTF-8' -d '{"Type":"Notification","TopicArn":"arn:aws:sns:…","Signature":"…","SigningCertURL":"https://sns..amazonaws.com/…","Message":"{…SES event…}"}' ``` ## CRM ### `GET /api/organizations/:organization_id/crm/contacts` List contacts in the tenant CRM — a person in the tenant CRM. Being in the CRM is not permission to email: consent lives in the `marketing` sub-resource. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `company_id` (string) — Restrict to one CRM company. **Response `200`** Structure: `Page`. - `items` (Contact[]) — The records on this page. → see `Contact` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/contacts -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/contacts` Create a contact in the tenant CRM. Being in the CRM is not permission to email: consent lives in the `marketing` sub-resource. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `email` (string, required) — Email address. Normalized and never truncated. - `first_name` (string, required) — Given name of the contact. - `last_name` (string) — Family name. - `company_id` (string) — Company this contact belongs to. **Body example** ```json { "email": "buyer@example.com", "first_name": "Buyer", "company_id": "co_…" } ``` **Response `200`** Structure: `Contact`. - `id` (string) — Contact ID. - `email` (string) — Email address, normalized and never truncated. - `first_name` (string, may be null) — Given name. - `last_name` (string, may be null) — Family name. - `company_id` (string, may be null) — Company this contact belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this contact — agents never build the path by hand. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/contacts -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"email":"buyer@example.com","first_name":"Buyer","company_id":"co_…"}' ``` ### `GET /api/organizations/:organization_id/crm/contacts/:contact_id` Read one contact from the tenant CRM. Being in the CRM is not permission to email: consent lives in the `marketing` sub-resource. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Response `200`** Structure: `Contact`. - `id` (string) — Contact ID. - `email` (string) — Email address, normalized and never truncated. - `first_name` (string, may be null) — Given name. - `last_name` (string, may be null) — Family name. - `company_id` (string, may be null) — Company this contact belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this contact — agents never build the path by hand. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/contacts/contact_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/contacts/:contact_id` Update one contact in the tenant CRM. Only the fields you send change. Being in the CRM is not permission to email: consent lives in the `marketing` sub-resource. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Body** (`application/json`) - `email` (string, required) — Email address. Normalized and never truncated. - `first_name` (string, required) — Given name of the contact. - `last_name` (string) — Family name. - `company_id` (string) — Company this contact belongs to. **Body example** ```json { "email": "buyer@example.com", "first_name": "Buyer", "company_id": "co_…" } ``` **Response `200`** Structure: `Contact`. - `id` (string) — Contact ID. - `email` (string) — Email address, normalized and never truncated. - `first_name` (string, may be null) — Given name. - `last_name` (string, may be null) — Family name. - `company_id` (string, may be null) — Company this contact belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this contact — agents never build the path by hand. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/contacts/contact_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"email":"buyer@example.com","first_name":"Buyer","company_id":"co_…"}' ``` ### `DELETE /api/organizations/:organization_id/crm/contacts/:contact_id` Delete one contact from the tenant CRM. The response carries the record as it was. Being in the CRM is not permission to email: consent lives in the `marketing` sub-resource. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Response `200`** Structure: `Contact`. - `id` (string) — Contact ID. - `email` (string) — Email address, normalized and never truncated. - `first_name` (string, may be null) — Given name. - `last_name` (string, may be null) — Family name. - `company_id` (string, may be null) — Company this contact belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this contact — agents never build the path by hand. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/contacts/contact_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/companies` List companies in the tenant CRM — an organization in the tenant CRM — a customer of the customer. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/companies` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. **Response `200`** Structure: `Page`. - `items` (Company[]) — The records on this page. → see `Company` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/companies -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/companies` Create a company in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/companies` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `name` (string, required) — Company name. - `domain` (string) — Primary domain, used to group contacts. - `website` (string) — Website URL. **Body example** ```json { "name": "Acme", "domain": "example.com", "website": "https://example.com" } ``` **Response `200`** Structure: `Company`. - `id` (string) — Company ID. - `name` (string) — Company name. - `domain` (string, may be null) — Primary domain. - `website` (string, may be null) — Website URL. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this company. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/companies -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Acme","domain":"example.com","website":"https://example.com"}' ``` ### `GET /api/organizations/:organization_id/crm/companies/:company_id` Read one company from the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/companies/:company_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `company_id` (string, required) — CRM company ID inside the active organization. **Response `200`** Structure: `Company`. - `id` (string) — Company ID. - `name` (string) — Company name. - `domain` (string, may be null) — Primary domain. - `website` (string, may be null) — Website URL. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this company. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/companies/company_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/companies/:company_id` Update one company in the tenant CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/companies/:company_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `company_id` (string, required) — CRM company ID inside the active organization. **Body** (`application/json`) - `name` (string, required) — Company name. - `domain` (string) — Primary domain, used to group contacts. - `website` (string) — Website URL. **Body example** ```json { "name": "Acme", "domain": "example.com", "website": "https://example.com" } ``` **Response `200`** Structure: `Company`. - `id` (string) — Company ID. - `name` (string) — Company name. - `domain` (string, may be null) — Primary domain. - `website` (string, may be null) — Website URL. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this company. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/companies/company_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Acme","domain":"example.com","website":"https://example.com"}' ``` ### `DELETE /api/organizations/:organization_id/crm/companies/:company_id` Delete one company from the tenant CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/companies/:company_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `company_id` (string, required) — CRM company ID inside the active organization. **Response `200`** Structure: `Company`. - `id` (string) — Company ID. - `name` (string) — Company name. - `domain` (string, may be null) — Primary domain. - `website` (string, may be null) — Website URL. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this company. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/companies/company_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/pipelines` List pipelines in the tenant CRM — a named sequence of stages that deals move through. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. **Response `200`** Structure: `Page`. - `items` (Pipeline[]) — The records on this page. → see `Pipeline` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/pipelines -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/pipelines` Create a pipeline in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `name` (string, required) — Pipeline name. - `stages` (string[]) — Stage names to create with the pipeline, in order. **Body example** ```json { "name": "Sales", "stages": [ "New", "Proposal", "Won" ] } ``` **Response `200`** Structure: `Pipeline`. - `id` (string) — Pipeline ID. - `name` (string) — Pipeline name. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this pipeline. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/pipelines -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Sales","stages":["New","Proposal","Won"]}' ``` ### `GET /api/organizations/:organization_id/crm/pipelines/:pipeline_id` Read one pipeline from the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. **Response `200`** Structure: `Pipeline`. - `id` (string) — Pipeline ID. - `name` (string) — Pipeline name. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this pipeline. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/pipelines/:pipeline_id` Update one pipeline in the tenant CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. **Body** (`application/json`) - `name` (string, required) — Pipeline name. - `stages` (string[]) — Stage names to create with the pipeline, in order. **Body example** ```json { "name": "Sales", "stages": [ "New", "Proposal", "Won" ] } ``` **Response `200`** Structure: `Pipeline`. - `id` (string) — Pipeline ID. - `name` (string) — Pipeline name. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this pipeline. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Sales","stages":["New","Proposal","Won"]}' ``` ### `DELETE /api/organizations/:organization_id/crm/pipelines/:pipeline_id` Delete one pipeline from the tenant CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. **Response `200`** Structure: `Pipeline`. - `id` (string) — Pipeline ID. - `name` (string) — Pipeline name. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this pipeline. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/deals` List deals in the tenant CRM — an opportunity moving through a pipeline. Money is in minor units: `value_minor: 10000` with `currency: "USD"` is $100.00, not $10,000. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/deals` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `pipeline_id` (string) — Restrict to one pipeline. - `stage_id` (string) — Restrict to one pipeline stage. - `status` (string) — Restrict to one status value. **Response `200`** Structure: `Page`. - `items` (Deal[]) — The records on this page. → see `Deal` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/deals -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/deals` Create a deal in the tenant CRM. Money is in minor units: `value_minor: 10000` with `currency: "USD"` is $100.00, not $10,000. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/deals` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `title` (string, required) — What the deal is. - `pipeline_id` (string, required) — Pipeline the deal lives in. - `stage_id` (string, required) — Stage the deal is at. - `value_minor` (int) — Value in the currency's MINOR unit — cents, not dollars. - `currency` (string) — ISO 4217 code for `value_minor`. **Body example** ```json { "title": "Renewal", "pipeline_id": "pl_…", "stage_id": "st_…", "value_minor": 10000, "currency": "USD" } ``` **Response `200`** Structure: `Deal`. - `id` (string) — Deal ID. - `title` (string) — What the deal is. - `pipeline_id` (string) — Pipeline the deal lives in. - `stage_id` (string) — Current stage. - `status` (string) — Where it stands. - `value_minor` (int, may be null) — Value in the currency's minor unit — cents, not dollars. - `currency` (string, may be null) — ISO 4217 code for `value_minor`. - `contact_id` (string, may be null) — Main contact on the deal. - `company_id` (string, may be null) — Company on the deal. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this deal. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/deals -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Renewal","pipeline_id":"pl_…","stage_id":"st_…","value_minor":10000,"currency":"USD"}' ``` ### `GET /api/organizations/:organization_id/crm/deals/:deal_id` Read one deal from the tenant CRM. Money is in minor units: `value_minor: 10000` with `currency: "USD"` is $100.00, not $10,000. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/deals/:deal_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `deal_id` (string, required) — CRM deal ID inside the active organization. **Response `200`** Structure: `Deal`. - `id` (string) — Deal ID. - `title` (string) — What the deal is. - `pipeline_id` (string) — Pipeline the deal lives in. - `stage_id` (string) — Current stage. - `status` (string) — Where it stands. - `value_minor` (int, may be null) — Value in the currency's minor unit — cents, not dollars. - `currency` (string, may be null) — ISO 4217 code for `value_minor`. - `contact_id` (string, may be null) — Main contact on the deal. - `company_id` (string, may be null) — Company on the deal. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this deal. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/deals/deal_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/deals/:deal_id` Update one deal in the tenant CRM. Only the fields you send change. Money is in minor units: `value_minor: 10000` with `currency: "USD"` is $100.00, not $10,000. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/deals/:deal_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `deal_id` (string, required) — CRM deal ID inside the active organization. **Body** (`application/json`) - `title` (string, required) — What the deal is. - `pipeline_id` (string, required) — Pipeline the deal lives in. - `stage_id` (string, required) — Stage the deal is at. - `value_minor` (int) — Value in the currency's MINOR unit — cents, not dollars. - `currency` (string) — ISO 4217 code for `value_minor`. **Body example** ```json { "title": "Renewal", "pipeline_id": "pl_…", "stage_id": "st_…", "value_minor": 10000, "currency": "USD" } ``` **Response `200`** Structure: `Deal`. - `id` (string) — Deal ID. - `title` (string) — What the deal is. - `pipeline_id` (string) — Pipeline the deal lives in. - `stage_id` (string) — Current stage. - `status` (string) — Where it stands. - `value_minor` (int, may be null) — Value in the currency's minor unit — cents, not dollars. - `currency` (string, may be null) — ISO 4217 code for `value_minor`. - `contact_id` (string, may be null) — Main contact on the deal. - `company_id` (string, may be null) — Company on the deal. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this deal. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/deals/deal_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Renewal","pipeline_id":"pl_…","stage_id":"st_…","value_minor":10000,"currency":"USD"}' ``` ### `DELETE /api/organizations/:organization_id/crm/deals/:deal_id` Delete one deal from the tenant CRM. The response carries the record as it was. Money is in minor units: `value_minor: 10000` with `currency: "USD"` is $100.00, not $10,000. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/deals/:deal_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `deal_id` (string, required) — CRM deal ID inside the active organization. **Response `200`** Structure: `Deal`. - `id` (string) — Deal ID. - `title` (string) — What the deal is. - `pipeline_id` (string) — Pipeline the deal lives in. - `stage_id` (string) — Current stage. - `status` (string) — Where it stands. - `value_minor` (int, may be null) — Value in the currency's minor unit — cents, not dollars. - `currency` (string, may be null) — ISO 4217 code for `value_minor`. - `contact_id` (string, may be null) — Main contact on the deal. - `company_id` (string, may be null) — Company on the deal. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this deal. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/deals/deal_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/activities` List activities in the tenant CRM — something that happened with a contact, company or deal. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/activities` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `deal_id` (string) — Restrict to one deal. - `contact_id` (string) — Restrict to one CRM contact. - `company_id` (string) — Restrict to one CRM company. - `activity_type` (string) — Restrict to one activity type, e.g. `note` or `call`. **Response `200`** Structure: `Page`. - `items` (Activity[]) — The records on this page. → see `Activity` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/activities -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/activities` Create a activity in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/activities` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `note` (string, required) — The text of the activity. - `activity_type` (string) — What kind it was, e.g. `note`, `call`, `meeting`. - `deal_id` (string) — Deal it refers to. - `contact_id` (string) — Contact it refers to. - `company_id` (string) — Company it refers to. **Body example** ```json { "activity_type": "note", "note": "Followed up", "deal_id": "de_…" } ``` **Response `200`** Structure: `Activity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`, `call`, `meeting`. - `note` (string) — The text of the activity. - `deal_id` (string, may be null) — Deal it refers to. - `contact_id` (string, may be null) — Contact it refers to. - `company_id` (string, may be null) — Company it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/activities -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"activity_type":"note","note":"Followed up","deal_id":"de_…"}' ``` ### `GET /api/organizations/:organization_id/crm/activities/:activity_id` Read one activity from the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/activities/:activity_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Response `200`** Structure: `Activity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`, `call`, `meeting`. - `note` (string) — The text of the activity. - `deal_id` (string, may be null) — Deal it refers to. - `contact_id` (string, may be null) — Contact it refers to. - `company_id` (string, may be null) — Company it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/activities/:activity_id` Update one activity in the tenant CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/activities/:activity_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Body** (`application/json`) - `note` (string, required) — The text of the activity. - `activity_type` (string) — What kind it was, e.g. `note`, `call`, `meeting`. - `deal_id` (string) — Deal it refers to. - `contact_id` (string) — Contact it refers to. - `company_id` (string) — Company it refers to. **Body example** ```json { "activity_type": "note", "note": "Followed up", "deal_id": "de_…" } ``` **Response `200`** Structure: `Activity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`, `call`, `meeting`. - `note` (string) — The text of the activity. - `deal_id` (string, may be null) — Deal it refers to. - `contact_id` (string, may be null) — Contact it refers to. - `company_id` (string, may be null) — Company it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"activity_type":"note","note":"Followed up","deal_id":"de_…"}' ``` ### `DELETE /api/organizations/:organization_id/crm/activities/:activity_id` Delete one activity from the tenant CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/activities/:activity_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Response `200`** Structure: `Activity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`, `call`, `meeting`. - `note` (string) — The text of the activity. - `deal_id` (string, may be null) — Deal it refers to. - `contact_id` (string, may be null) — Contact it refers to. - `company_id` (string, may be null) — Company it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/tasks` List tasks in the tenant CRM — work someone still has to do in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/tasks` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `status` (string) — Restrict to one status value. - `deal_id` (string) — Restrict to one deal. - `contact_id` (string) — Restrict to one CRM contact. - `assignee_user_id` (string) — Restrict to the member the work is assigned to. **Response `200`** Structure: `Page`. - `items` (Task[]) — The records on this page. → see `Task` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/tasks -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/tasks` Create a task in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/tasks` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `title` (string, required) — What has to be done. - `status` (string) — Current state. Default: `open`. Values: `open`, `done`, `cancelled`. - `due_at` (string) — When it is due, ISO-8601. - `assignee_user_id` (string) — Member responsible for it. - `deal_id` (string) — Deal the task belongs to. - `contact_id` (string) — Contact the task belongs to. **Body example** ```json { "title": "Follow up", "status": "open", "deal_id": "de_…" } ``` **Response `200`** Structure: `Task`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Member responsible for it. - `deal_id` (string, may be null) — Deal the task belongs to. - `contact_id` (string, may be null) — Contact the task belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/tasks -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Follow up","status":"open","deal_id":"de_…"}' ``` ### `GET /api/organizations/:organization_id/crm/tasks/:task_id` Read one task from the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/tasks/:task_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `task_id` (string, required) — CRM task ID inside the active organization. **Response `200`** Structure: `Task`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Member responsible for it. - `deal_id` (string, may be null) — Deal the task belongs to. - `contact_id` (string, may be null) — Contact the task belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/tasks/:task_id` Update one task in the tenant CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/tasks/:task_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `task_id` (string, required) — CRM task ID inside the active organization. **Body** (`application/json`) - `title` (string, required) — What has to be done. - `status` (string) — Current state. Default: `open`. Values: `open`, `done`, `cancelled`. - `due_at` (string) — When it is due, ISO-8601. - `assignee_user_id` (string) — Member responsible for it. - `deal_id` (string) — Deal the task belongs to. - `contact_id` (string) — Contact the task belongs to. **Body example** ```json { "title": "Follow up", "status": "open", "deal_id": "de_…" } ``` **Response `200`** Structure: `Task`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Member responsible for it. - `deal_id` (string, may be null) — Deal the task belongs to. - `contact_id` (string, may be null) — Contact the task belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Follow up","status":"open","deal_id":"de_…"}' ``` ### `DELETE /api/organizations/:organization_id/crm/tasks/:task_id` Delete one task from the tenant CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/tasks/:task_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `task_id` (string, required) — CRM task ID inside the active organization. **Response `200`** Structure: `Task`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Member responsible for it. - `deal_id` (string, may be null) — Deal the task belongs to. - `contact_id` (string, may be null) — Contact the task belongs to. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages` List stages in the tenant CRM — one step of a pipeline, addressed under the pipeline it belongs to. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (Stage[]) — The records on this page. → see `Stage` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id/stages -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages` Create a stage in the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. **Body** (`application/json`) - `name` (string, required) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int) — Chance of winning at this stage, 0 to 100. **Body example** ```json { "name": "Proposal", "position": 2, "probability": 60 } ``` **Response `200`** Structure: `Stage`. - `id` (string) — Stage ID. - `pipeline_id` (string) — Pipeline this stage belongs to. - `name` (string) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int, may be null) — Chance of winning at this stage, 0 to 100. - `url` (string) — Absolute URL of this stage. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id/stages -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Proposal","position":2,"probability":60}' ``` ### `GET /api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` Read one stage from the tenant CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. - `stage_id` (string, required) — Pipeline stage ID. Stages live under one pipeline. **Response `200`** Structure: `Stage`. - `id` (string) — Stage ID. - `pipeline_id` (string) — Pipeline this stage belongs to. - `name` (string) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int, may be null) — Chance of winning at this stage, 0 to 100. - `url` (string) — Absolute URL of this stage. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id/stages/stage_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` Update one stage in the tenant CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. - `stage_id` (string, required) — Pipeline stage ID. Stages live under one pipeline. **Body** (`application/json`) - `name` (string, required) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int) — Chance of winning at this stage, 0 to 100. **Body example** ```json { "name": "Proposal", "position": 2, "probability": 60 } ``` **Response `200`** Structure: `Stage`. - `id` (string) — Stage ID. - `pipeline_id` (string) — Pipeline this stage belongs to. - `name` (string) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int, may be null) — Chance of winning at this stage, 0 to 100. - `url` (string) — Absolute URL of this stage. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id/stages/stage_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Proposal","position":2,"probability":60}' ``` ### `DELETE /api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` Delete one stage from the tenant CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/pipelines/:pipeline_id/stages/:stage_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `pipeline_id` (string, required) — CRM pipeline ID inside the active organization. - `stage_id` (string, required) — Pipeline stage ID. Stages live under one pipeline. **Response `200`** Structure: `Stage`. - `id` (string) — Stage ID. - `pipeline_id` (string) — Pipeline this stage belongs to. - `name` (string) — Stage name, e.g. `Proposal`. - `position` (int) — Order within the pipeline, lowest first. - `probability` (int, may be null) — Chance of winning at this stage, 0 to 100. - `url` (string) — Absolute URL of this stage. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/organization_id/crm/pipelines/pipeline_id/stages/stage_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ## Platform CRM ### `GET /api/platform/crm/leads` List leads in the platform CRM — a prospective ORGANIZATION in our own funnel — platform CRM is not tenant data. - **URL:** `https://commsharbor.com/api/platform/crm/leads` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `stage` (string) — Restrict to one funnel stage of the platform lead. **Response `200`** Structure: `Page`. - `items` (Lead[]) — The records on this page. → see `Lead` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/leads -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/platform/crm/leads` Create a lead in the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/leads` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Body** (`application/json`) - `email` (string, required) — Contact email for the lead. - `name` (string, required) — Who to talk to. - `company_name` (string) — Name of the prospective organization. - `source` (string) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where it sits in our funnel, e.g. `new`. - `organization_id` (string) — The organization it became, once converted. **Body example** ```json { "email": "lead@example.com", "name": "Lead", "company_name": "Acme", "source": "manual", "stage": "new", "organization_id": "org_…" } ``` **Response `200`** Structure: `Lead`. - `id` (string) — Lead ID. - `email` (string) — Contact email for the lead. - `name` (string) — Who to talk to. - `company_name` (string, may be null) — Name of the prospective organization. - `source` (string, may be null) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where the lead is in our own funnel, e.g. `new`. - `organization_id` (string, may be null) — The organization this lead became, once it converted. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this lead. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/platform/crm/leads -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"email":"lead@example.com","name":"Lead","company_name":"Acme","source":"manual","stage":"new","organization_id":"org_…"}' ``` ### `GET /api/platform/crm/leads/:lead_id` Read one lead from the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. **Response `200`** Structure: `Lead`. - `id` (string) — Lead ID. - `email` (string) — Contact email for the lead. - `name` (string) — Who to talk to. - `company_name` (string, may be null) — Name of the prospective organization. - `source` (string, may be null) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where the lead is in our own funnel, e.g. `new`. - `organization_id` (string, may be null) — The organization this lead became, once it converted. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this lead. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/leads/lead_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/platform/crm/leads/:lead_id` Update one lead in the platform CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. **Body** (`application/json`) - `email` (string, required) — Contact email for the lead. - `name` (string, required) — Who to talk to. - `company_name` (string) — Name of the prospective organization. - `source` (string) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where it sits in our funnel, e.g. `new`. - `organization_id` (string) — The organization it became, once converted. **Body example** ```json { "email": "lead@example.com", "name": "Lead", "company_name": "Acme", "source": "manual", "stage": "new", "organization_id": "org_…" } ``` **Response `200`** Structure: `Lead`. - `id` (string) — Lead ID. - `email` (string) — Contact email for the lead. - `name` (string) — Who to talk to. - `company_name` (string, may be null) — Name of the prospective organization. - `source` (string, may be null) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where the lead is in our own funnel, e.g. `new`. - `organization_id` (string, may be null) — The organization this lead became, once it converted. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this lead. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/platform/crm/leads/lead_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"email":"lead@example.com","name":"Lead","company_name":"Acme","source":"manual","stage":"new","organization_id":"org_…"}' ``` ### `DELETE /api/platform/crm/leads/:lead_id` Delete one lead from the platform CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. **Response `200`** Structure: `Lead`. - `id` (string) — Lead ID. - `email` (string) — Contact email for the lead. - `name` (string) — Who to talk to. - `company_name` (string, may be null) — Name of the prospective organization. - `source` (string, may be null) — Where the lead came from, e.g. `manual`. - `stage` (string) — Where the lead is in our own funnel, e.g. `new`. - `organization_id` (string, may be null) — The organization this lead became, once it converted. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this lead. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/platform/crm/leads/lead_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/platform/crm/tasks` List tasks in the platform CRM — work on a platform lead — about a prospective tenant, not about a tenant's customer. - **URL:** `https://commsharbor.com/api/platform/crm/tasks` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. - `status` (string) — Restrict to one status value. - `lead_id` (string) — Restrict to one platform lead. - `assignee_user_id` (string) — Restrict to the member the work is assigned to. **Response `200`** Structure: `Page`. - `items` (PlatformTask[]) — The records on this page. → see `PlatformTask` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/tasks -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/platform/crm/tasks` Create a task in the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/tasks` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Body** (`application/json`) - `title` (string, required) — What has to be done. - `lead_id` (string) — Lead the task belongs to. - `due_at` (string) — When it is due, ISO-8601. - `assignee_user_id` (string) — Who is responsible. **Body example** ```json { "title": "Follow up", "lead_id": "ld_…", "due_at": "2026-09-01T12:00:00Z" } ``` **Response `200`** Structure: `PlatformTask`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `lead_id` (string, may be null) — Lead the task belongs to. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Who is responsible. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/platform/crm/tasks -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Follow up","lead_id":"ld_…","due_at":"2026-09-01T12:00:00Z"}' ``` ### `GET /api/platform/crm/tasks/:task_id` Read one task from the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/tasks/:task_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `task_id` (string, required) — CRM task ID inside the active organization. **Response `200`** Structure: `PlatformTask`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `lead_id` (string, may be null) — Lead the task belongs to. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Who is responsible. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/platform/crm/tasks/:task_id` Update one task in the platform CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/platform/crm/tasks/:task_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `task_id` (string, required) — CRM task ID inside the active organization. **Body** (`application/json`) - `title` (string, required) — What has to be done. - `lead_id` (string) — Lead the task belongs to. - `due_at` (string) — When it is due, ISO-8601. - `assignee_user_id` (string) — Who is responsible. **Body example** ```json { "title": "Follow up", "lead_id": "ld_…", "due_at": "2026-09-01T12:00:00Z" } ``` **Response `200`** Structure: `PlatformTask`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `lead_id` (string, may be null) — Lead the task belongs to. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Who is responsible. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/platform/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"title":"Follow up","lead_id":"ld_…","due_at":"2026-09-01T12:00:00Z"}' ``` ### `DELETE /api/platform/crm/tasks/:task_id` Delete one task from the platform CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/platform/crm/tasks/:task_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `task_id` (string, required) — CRM task ID inside the active organization. **Response `200`** Structure: `PlatformTask`. - `id` (string) — Task ID. - `title` (string) — What has to be done. - `status` (string) — Current state. - `lead_id` (string, may be null) — Lead the task belongs to. - `due_at` (string, may be null) — When it is due (UTC). - `assignee_user_id` (string, may be null) — Who is responsible. - `created_at` (string) — Creation time (UTC). - `updated_at` (string, may be null) — Last change (UTC). - `url` (string) — Absolute URL of this task. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/platform/crm/tasks/task_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/platform/crm/leads/:lead_id/activities` List activities in the platform CRM — our note about a prospective tenant. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id/activities` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. - `q` (string) — Free-text search over the record's main fields. **Response `200`** Structure: `Page`. - `items` (PlatformActivity[]) — The records on this page. → see `PlatformActivity` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/leads/lead_id/activities -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/platform/crm/leads/:lead_id/activities` Create a activity in the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id/activities` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. **Body** (`application/json`) - `note` (string, required) — The text of the activity. - `activity_type` (string) — What kind it was, e.g. `note`. **Body example** ```json { "activity_type": "note", "note": "Followed up" } ``` **Response `200`** Structure: `PlatformActivity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`. - `note` (string) — The text of the activity. - `lead_id` (string) — Lead it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `400` — A required field is missing, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/platform/crm/leads/lead_id/activities -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"activity_type":"note","note":"Followed up"}' ``` ### `GET /api/platform/crm/leads/:lead_id/activities/:activity_id` Read one activity from the platform CRM. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id/activities/:activity_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Response `200`** Structure: `PlatformActivity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`. - `note` (string) — The text of the activity. - `lead_id` (string) — Lead it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/platform/crm/leads/lead_id/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/platform/crm/leads/:lead_id/activities/:activity_id` Update one activity in the platform CRM. Only the fields you send change. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id/activities/:activity_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Body** (`application/json`) - `note` (string, required) — The text of the activity. - `activity_type` (string) — What kind it was, e.g. `note`. **Body example** ```json { "activity_type": "note", "note": "Followed up" } ``` **Response `200`** Structure: `PlatformActivity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`. - `note` (string) — The text of the activity. - `lead_id` (string) — Lead it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `400` — A field is invalid, or a referenced record does not exist here. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/platform/crm/leads/lead_id/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"activity_type":"note","note":"Followed up"}' ``` ### `DELETE /api/platform/crm/leads/:lead_id/activities/:activity_id` Delete one activity from the platform CRM. The response carries the record as it was. - **URL:** `https://commsharbor.com/api/platform/crm/leads/:lead_id/activities/:activity_id` - **Auth:** `platform_admin` — Session with an explicit platform_roles grant. Tenant ownership does not grant platform access. **Path parameters** - `lead_id` (string, required) — Platform CRM lead ID. Platform CRM is not tenant data. - `activity_id` (string, required) — CRM activity ID inside the active organization. **Response `200`** Structure: `PlatformActivity`. - `id` (string) — Activity ID. - `activity_type` (string) — What kind of activity it was, e.g. `note`. - `note` (string) — The text of the activity. - `lead_id` (string) — Lead it refers to. - `created_at` (string) — Creation time (UTC). - `url` (string) — Absolute URL of this activity. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/platform/crm/leads/lead_id/activities/activity_id -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ## Consent ### `GET /api/organizations/:organization_id/crm/contacts/:contact_id/marketing` Read consent and marketing preference for one CRM contact. Being in the CRM is not permission to email. This resource is where permission actually lives. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id/marketing` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Response `200`** Structure: `Marketing`. - `marketing_enabled` (bool) — Whether marketing may be sent to this contact right now. - `basis` (string, may be null) — Why sending is allowed. - `source` (string, may be null) — Where the consent came from, e.g. a signup form. - `captured_at` (string, may be null) — When consent was captured (UTC). - `unsubscribed_at` (string, may be null) — When the contact unsubscribed; null when they never did. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/crm/contacts/$CONTACT/marketing -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PUT /api/organizations/:organization_id/crm/contacts/:contact_id/marketing` Record permission-based marketing consent for one contact. This NEVER restores a previous unsubscribe. If the contact opted out, they stay out and `marketing_enabled` remains false — recording consent after the fact does not undo their decision. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id/marketing` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Body** (`application/json`) - `basis` (string, required) — Why sending is allowed. Values: `explicit`, `documented_relationship`. - `source` (string, required) — Where the consent came from, e.g. the signup form that captured it. - `captured_at` (string, required) — When consent was captured, ISO-8601. **Body example** ```json { "basis": "explicit", "source": "website signup form", "captured_at": "2026-08-29T12:00:00.000Z" } ``` **Response `200`** Structure: `Marketing`. - `marketing_enabled` (bool) — Whether marketing may be sent to this contact right now. - `basis` (string, may be null) — Why sending is allowed. - `source` (string, may be null) — Where the consent came from, e.g. a signup form. - `captured_at` (string, may be null) — When consent was captured (UTC). - `unsubscribed_at` (string, may be null) — When the contact unsubscribed; null when they never did. **Errors** - `400` — Missing basis, source or capture time, or an unknown basis. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPUT https://commsharbor.com/api/organizations/$ORG/crm/contacts/$CONTACT/marketing -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"basis":"explicit","source":"website signup form","captured_at":"2026-08-29T12:00:00.000Z"}' ``` ### `POST /api/organizations/:organization_id/crm/contacts/:contact_id/preference-token` Create a signed preference and one-click unsubscribe capability for a contact. The capability is scoped to one organization and one contact, it expires, and it embeds no email address. Put `marketing_headers` in the message and the unsubscribe works without a login. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/crm/contacts/:contact_id/preference-token` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Body example** ```json {} ``` **Response `200`** Structure: `PreferenceToken`. - `api_url` (string) — Machine-readable preference endpoint. - `preferences_url` (string) — Human preference page. - `unsubscribe_url` (string) — RFC 8058 one-click unsubscribe endpoint. - `marketing_headers` (object) — `List-Unsubscribe` and `List-Unsubscribe-Post`, ready to add to the message. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/crm/contacts/$CONTACT/preference-token -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{}' ``` ### `GET /api/preferences/:token` Read a contact's preference through a signed capability, with no login. The token IS the credential. It reveals preference state and nothing else — no email address, no CRM record. - **URL:** `https://commsharbor.com/api/preferences/:token` - **Auth:** `preference_capability` — Signed, expiring capability scoped to one organization and contact. No login is required and no email address is embedded in the token. **Path parameters** - `token` (string, required) — Signed preference capability. It is scoped to one organization and one contact, it expires, and it never embeds an email address. **Response `200`** Structure: `PreferenceState`. - `marketing_enabled` (bool) — Whether marketing may be sent. - `unsubscribed_at` (string, may be null) — When the contact unsubscribed. - `expires_at` (string) — When this capability stops working (UTC). **Errors** - `401` — Bad signature. - `404` — Unknown or expired capability. **Example** ```sh curl -s https://commsharbor.com/api/preferences/$TOKEN ``` ### `POST /api/preferences/:token/unsubscribe` Apply an RFC 8058 one-click unsubscribe, idempotently. This is the endpoint mail clients call from the `List-Unsubscribe-Post` header, which is why the body is form-encoded and fixed. Calling it twice is the same as calling it once. - **URL:** `https://commsharbor.com/api/preferences/:token/unsubscribe` - **Auth:** `preference_capability` — Signed, expiring capability scoped to one organization and contact. No login is required and no email address is embedded in the token. **Path parameters** - `token` (string, required) — Signed preference capability. It is scoped to one organization and one contact, it expires, and it never embeds an email address. **Body example** ```json List-Unsubscribe=One-Click ``` **Response `200`** - `unsubscribed` (bool) — Always true once the contact is unsubscribed, whether or not this call was the one that did it. **Errors** - `401` — Bad signature. - `404` — Unknown or expired capability. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/preferences/$TOKEN/unsubscribe -H 'content-type: application/x-www-form-urlencoded' -d 'List-Unsubscribe=One-Click' ``` ### `POST /api/organizations/:organization_id/domains/:domain_id/marketing-smoke` Queue one controlled permission-based marketing message to the server-side recipient. Like the transactional smoke, the destination is a server-side secret. `contact_id` must be the CRM contact that matches it — you cannot point this at an arbitrary person. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/domains/:domain_id/marketing-smoke` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `domain_id` (string, required) — Sending domain ID, from the domains listing. **Headers** - `Idempotency-Key` (string, required) — Unique key for this controlled send. Replaying the same key returns the same result and produces NO second Queue message; a different payload under the same key conflicts. **Body** (`application/json`) - `contact_id` (string, required) — CRM contact matching the server-side QA recipient. **Body example** ```json { "contact_id": "ct_controlled" } ``` **Response `200`** Structure: `Send`. - `delivery` (Delivery) — The delivery record. → see `Delivery` under **Structures**. - `capacity` (Capacity) — What is left after this send. → see `Capacity` under **Structures**. - `dispatch` (Dispatch) — The queue work behind it. → see `Dispatch` under **Structures**. - `replayed` (bool) — True when this was a replay of an earlier identical request. **Errors** - `400` — Missing contact. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `409` — The contact does not match the controlled recipient, or the domain is not active. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/domains/$DOMAIN/marketing-smoke -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H "Idempotency-Key: $KEY" -H 'content-type: application/json' -d '{"contact_id":"ct_controlled"}' ``` ## Imports ### `GET /api/organizations/:organization_id/contact-imports` List the contact imports of the organization. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-imports` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (ContactImport[]) — The records on this page. → see `ContactImport` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/contact-imports -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/contact-imports` Upload a consent-declared CSV and get back a safe preview before anything is imported. Nothing is created by this call. The consent declaration is mandatory: an import that cannot say why these people may be emailed is an import that does not happen. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-imports` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `csv` (string, required) — The CSV itself, up to 5 MB and 10,000 rows. - `mapping` (object, required) — Which CSV column feeds which contact field, e.g. `{ "email": "Email" }`. - `basis` (string, required) — Why sending is allowed. Values: `explicit`, `documented_relationship`. - `source` (string, required) — Where the consent came from, e.g. the signup form that captured it. - `captured_at` (string, required) — When consent was captured, ISO-8601. **Body example** ```json { "csv": "Email,First name\\nalice@example.com,Alice", "mapping": { "email": "Email", "first_name": "First name" }, "basis": "explicit", "source": "website signup form", "captured_at": "2026-08-29T12:00:00.000Z" } ``` **Response `200`** - `import` (ContactImport) — The import in `preview` state. → see `ContactImport` under **Structures**. - `preview` (object[]) — The first parsed rows, so you can check the mapping before confirming. - `errors` (ImportError[]) — Rows that would be rejected, addressed by line number. → see `ImportError` under **Structures**. **Errors** - `400` — CSV too large, unparseable, or missing the consent declaration. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `413` — Above the size limit. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/contact-imports -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"csv":"Email\\nalice@example.com","mapping":{"email":"Email"},"basis":"explicit","source":"website signup form","captured_at":"2026-08-29T12:00:00.000Z"}' ``` ### `GET /api/organizations/:organization_id/contact-imports/:import_id` Read one durable contact import and its current counts. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-imports/:import_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `import_id` (string, required) — Contact import ID returned by the CSV preview. **Response `200`** Structure: `ContactImport`. - `id` (string) — Import ID. - `status` (string) — Where the import stands. - `rows` (int) — Rows found in the CSV. - `accepted` (int) — Rows that will be imported. - `rejected` (int) — Rows rejected, with reasons in the errors listing. - `basis` (string) — Declared consent basis for every row. - `file_id` (string, may be null) — Stored file, downloadable for seven days. - `created_at` (string) — Upload time (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/contact-imports/$IMPORT -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/contact-imports/:import_id/confirm` Confirm a previewed import and enqueue it, exactly once. The idempotency key is mandatory here. Reusing it returns the same import and never creates a second Queue message — which is what keeps a retry from importing everyone twice. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-imports/:import_id/confirm` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `import_id` (string, required) — Contact import ID returned by the CSV preview. **Headers** - `Idempotency-Key` (string, required) — Unique key for this import confirmation. Replaying the same key returns the same result and produces NO second Queue message; a different payload under the same key conflicts. **Body example** ```json {} ``` **Response `200`** Structure: `ContactImport`. - `id` (string) — Import ID. - `status` (string) — Where the import stands. - `rows` (int) — Rows found in the CSV. - `accepted` (int) — Rows that will be imported. - `rejected` (int) — Rows rejected, with reasons in the errors listing. - `basis` (string) — Declared consent basis for every row. - `file_id` (string, may be null) — Stored file, downloadable for seven days. - `created_at` (string) — Upload time (UTC). **Errors** - `400` — Missing Idempotency-Key. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `409` — The same key was used with a different payload, or the import was already confirmed. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/contact-imports/$IMPORT/confirm -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H "Idempotency-Key: $KEY" -H 'content-type: application/json' -d '{}' ``` ### `GET /api/organizations/:organization_id/contact-imports/:import_id/errors` List the row-numbered errors of one import, so the source file can be fixed. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-imports/:import_id/errors` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `import_id` (string, required) — Contact import ID returned by the CSV preview. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (ImportError[]) — The records on this page. → see `ImportError` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/contact-imports/$IMPORT/errors -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/contact-files/:file_id` Download a tenant-owned CSV before its seven-day expiry. Files expire seven days after creation. Durable audit and row-level results survive the file. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contact-files/:file_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `file_id` (string, required) — Contact file ID. Files expire seven days after they are created. **Response `200`** `text/csv` as an attachment, with the tenant's file name. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `410` — The file expired. Its audit record still exists. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/contact-files/$FILE -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/contacts/export` Create a CSV export of the organization's contacts, retained for seven days. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/contacts/export` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body example** ```json {} ``` **Response `200`** - `export` (ContactImport) — The export record, with its file and expiry. → see `ContactImport` under **Structures**. - `download_url` (string) — Where to fetch the CSV while it lasts. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/contacts/export -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{}' ``` ## Suppressions ### `GET /api/organizations/:organization_id/suppressions` List the organization's suppressions, without exposing email hashes. Global, organization and SES tenant suppressions are all checked BEFORE quota and before any queue work. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/suppressions` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (Suppression[]) — The records on this page. → see `Suppression` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/suppressions -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/suppressions` Suppress one recipient inside the active organization. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/suppressions` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `email` (string, required) — Address to suppress. - `contact_id` (string) — CRM contact it corresponds to, when you know it. **Body example** ```json { "email": "alice@example.com", "contact_id": "ct_example" } ``` **Response `200`** - `suppressed` (bool) — Always true once the address is suppressed. **Errors** - `400` — Missing or malformed email. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/suppressions -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"email":"alice@example.com"}' ``` ## Audiences ### `GET /api/organizations/:organization_id/audiences` List static audiences and saved segments. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (Audience[]) — The records on this page. → see `Audience` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/audiences -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/audiences` Create a static audience or a saved segment with an allowlisted filter. Only allowlisted filter fields are accepted — a saved segment cannot be turned into an arbitrary query over the CRM. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Body** (`application/json`) - `name` (string, required) — Audience name. - `kind` (string, required) — How membership is decided. Values: `static`, `saved_segment`. - `filter` (object) — The saved filter, for `saved_segment`. Allowlisted fields only. **Body example** ```json { "name": "Newsletter", "kind": "static" } ``` **Response `200`** Structure: `Audience`. - `id` (string) — Audience ID. - `name` (string) — Audience name. - `kind` (string) — How membership is decided. - `filter` (object, may be null) — The saved filter, for `saved_segment`. Only allowlisted fields are accepted. - `member_count` (int, may be null) — Contacts currently in the audience. - `created_at` (string) — Creation time (UTC). **Errors** - `400` — Missing name or kind, or a filter field outside the allowlist. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/audiences -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Newsletter","kind":"static"}' ``` ### `GET /api/organizations/:organization_id/audiences/:audience_id` Read one audience of this organization. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. **Response `200`** Structure: `Audience`. - `id` (string) — Audience ID. - `name` (string) — Audience name. - `kind` (string) — How membership is decided. - `filter` (object, may be null) — The saved filter, for `saved_segment`. Only allowlisted fields are accepted. - `member_count` (int, may be null) — Contacts currently in the audience. - `created_at` (string) — Creation time (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `PATCH /api/organizations/:organization_id/audiences/:audience_id` Rename an audience or change its saved filter. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. **Body** (`application/json`) - `name` (string) — New audience name. - `filter` (object) — New saved filter. Allowlisted fields only. **Body example** ```json { "name": "Customers", "filter": { "consent_basis": "explicit" } } ``` **Response `200`** Structure: `Audience`. - `id` (string) — Audience ID. - `name` (string) — Audience name. - `kind` (string) — How membership is decided. - `filter` (object, may be null) — The saved filter, for `saved_segment`. Only allowlisted fields are accepted. - `member_count` (int, may be null) — Contacts currently in the audience. - `created_at` (string) — Creation time (UTC). **Errors** - `400` — A filter field outside the allowlist. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XPATCH https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"name":"Customers"}' ``` ### `DELETE /api/organizations/:organization_id/audiences/:audience_id` Delete an audience and its memberships. Contacts themselves are untouched. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. **Response `200`** Structure: `Audience`. - `id` (string) — Audience ID. - `name` (string) — Audience name. - `kind` (string) — How membership is decided. - `filter` (object, may be null) — The saved filter, for `saved_segment`. Only allowlisted fields are accepted. - `member_count` (int, may be null) — Contacts currently in the audience. - `created_at` (string) — Creation time (UTC). **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `GET /api/organizations/:organization_id/audiences/:audience_id/members` List the contacts currently in one audience. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id/members` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `Page`. - `items` (Contact[]) — The records on this page. → see `Contact` under **Structures**. - `next_cursor` (string, may be null) — Cursor for the next page; null when there are no more. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE/members -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ### `POST /api/organizations/:organization_id/audiences/:audience_id/members` Add a CRM contact to a static audience. Only for `static` audiences: a saved segment's membership comes from its filter, not from this route. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id/members` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. **Body** (`application/json`) - `contact_id` (string, required) — CRM contact to add. **Body example** ```json { "contact_id": "ct_example" } ``` **Response `200`** - `membership` (object) — The audience membership that was created. **Errors** - `400` — Missing contact. - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. - `409` — The audience is a saved segment and has no manual membership. **Example** ```sh curl -s -XPOST https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE/members -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" -H 'content-type: application/json' -d '{"contact_id":"ct_example"}' ``` ### `DELETE /api/organizations/:organization_id/audiences/:audience_id/members/:contact_id` Remove a contact from a static audience. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/audiences/:audience_id/members/:contact_id` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. - `audience_id` (string, required) — Audience ID inside the active organization. - `contact_id` (string, required) — CRM contact ID inside the active organization. **Response `200`** - `membership` (object) — The membership as it was before removal. **Errors** - `401` — No session, no API key, or the credential does not resolve to this organization. - `403` — The identity is valid but lacks the required role or scope for this operation. - `404` — The resource does not exist in this organization. Another tenant's ID answers the same 404 — the API never confirms that it exists elsewhere. **Example** ```sh curl -s -XDELETE https://commsharbor.com/api/organizations/$ORG/audiences/$AUDIENCE/members/$CONTACT -H "Authorization: Bearer $TOKEN" -H "X-Organization-Id: $ORG" ``` ## Templates ### `GET /api/organizations/:organization_id/templates` List the versioned email templates of the organization. - **URL:** `https://commsharbor.com/api/organizations/:organization_id/templates` - **Auth:** `organization` — Session plus X-Organization-Id membership, or a scoped API key that determines the organization. **Path parameters** - `organization_id` (string, required) — Active organization ID. It must match the organization your session header or API key resolves to. **Query** - `cursor` (string) — Opaque cursor from the previous page. Do not build or parse it. - `limit` (int) — Page size, from 1 to 100. Default: `50`. **Response `200`** Structure: `PageCamel