Warmerlydocs
API reference

Workspaces & Projects

Every account belongs to one or more workspaces — the top-level tenant that owns billing, members, and branding. Each workspace contains one or more projects, and mailboxes, campaigns, and inbox data all attach to a project (selected via the X-Project-Id header on other endpoints — see Authentication).

Workspace and project management endpoints below identify the workspace/project by ID in the URL path, so they generally do not require X-Project-Id. The one exception is listing/creating projects, which operates on your active workspace (see Projects below).

Roles

Workspace members have one of three roles:

RoleCan do
ownerEverything, including deleting the workspace and managing billing. Every workspace has exactly one owner.
adminRename the workspace, manage members/invites and branding. Cannot delete the workspace, manage billing, or modify/remove the owner.
memberRead the workspace and use its projects. Cannot manage members, rename, or delete.

Granting the owner role to another member transfers ownership — the caller must already be the owner, and their own role is downgraded to admin.

Workspaces

List your workspaces

GET/workspaces

Returns every workspace you're a member of.

JSON
{
  "workspaces": [
    {
      "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
      "name": "Acme Inc",
      "role": "owner",
      "status": "active",
      "logoUrl": "https://app.warmerly.com/api/images/...",
      "isOwner": true,
      "planName": "Agency",
      "billedThrough": null,
      "clientCount": 2,
      "deletionScheduledFor": null,
      "purgeAt": null
    }
  ]
}

status is "active", "pending" (an extra workspace waiting for its own plan) or "deleted" (in its 30-day recovery window). billedThrough is set on an Agency client workspace and names the Agency workspace whose plan it shares; planName is then null. clientCount is how many client workspaces share an Agency workspace's plan.

Create a workspace

POST/workspaces
FieldTypeRequiredDescription
namestringYes1–80 characters.
Shell
curl https://app.warmerly.com/api/v1/workspaces \
  -X POST \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Inc" }'
JSON
{
  "workspace": {
    "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "name": "Acme Inc",
    "role": "owner",
    "status": "active",
    "logoUrl": null
  },
  "billedThrough": { "id": "3c59dc04-8f37-4b8b-9c0e-7d3a1f1e2b4d", "name": "My Agency" }
}

The caller becomes the workspace's owner. How it is paid for depends on your plan:

  • On Agency, it is a client workspace: status is "active" straight away, it shares your Agency plan's allowances, and billedThrough names the Agency workspace.
  • Otherwise, status is "pending" and billedThrough is null until a paid plan is chosen for it in the app. Free is not available for extra workspaces.

If you already have a pending workspace, that one is returned (renamed to name) instead of creating a duplicate.

Get a workspace

GET/workspaces/{id}

Returns the workspace, its members, and (if you're an owner/admin) its pending invites.

JSON
{
  "workspace": {
    "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "name": "Acme Inc",
    "logoUrl": null,
    "role": "owner",
    "ownerUserId": "3c2b1a09-..."
  },
  "members": [
    {
      "userId": "3c2b1a09-...",
      "email": "you@acme.com",
      "name": "Jordan",
      "image": null,
      "role": "owner",
      "createdAt": "2026-01-10T12:00:00.000Z"
    }
  ],
  "invites": [
    {
      "id": "b2f9...",
      "email": "new.hire@acme.com",
      "role": "member",
      "status": "pending",
      "createdAt": "2026-06-01T09:00:00.000Z",
      "expiresAt": "2026-06-08T09:00:00.000Z"
    }
  ]
}

invites is empty for members without members:manage permission (i.e. plain member role).

Rename a workspace

PATCH/workspaces/{id}

Requires owner or admin.

FieldTypeRequiredDescription
namestringYes1–80 characters.
Shell
curl https://app.warmerly.com/api/v1/workspaces/8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a \
  -X PATCH \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Incorporated" }'
JSON
{ "workspace": { "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a", "name": "Acme Incorporated" } }

Delete a workspace

DELETE/workspaces/{id}

Requires owner. What happens depends on the workspace, and phase says which:

phaseWhenWhat happens
removedA pending workspace that was never paid forDeleted immediately.
scheduledIt has its own paid subscriptionThe subscription stops renewing; the workspace works until effectiveAt, then enters recovery. No refund.
retentionAnything else (Free, or an Agency client)Locked now and kept until purgeAt (30 days), then permanently deleted with everything under it.
JSON
{
  "ok": true,
  "phase": "retention",
  "effectiveAt": "2026-09-23T10:00:00.000Z",
  "purgeAt": "2026-10-23T10:00:00.000Z",
  "nextWorkspaceId": "3c59dc04-8f37-4b8b-9c0e-7d3a1f1e2b4d"
}

nextWorkspaceId is the workspace you were switched to, when the deleted one stopped being usable.

Restore a workspace

POST/workspaces/{id}/restore

Requires owner. Undoes a deletion without a card: a scheduled deletion is called off and the subscription carries on, and a workspace in recovery comes back into its Agency plan, keeps its own subscription if it still has one, or otherwise returns on the Free plan.

JSON
{ "outcome": "free" }

outcome is deletion_cancelled, agency_pool, subscription_kept, free or not_deleted.

Members

Change a member's role

PATCH/workspaces/{id}/members/{userId}

Requires owner or admin.

FieldTypeRequiredDescription
role"owner" | "admin" | "member"YesNew role for the member.
Shell
curl https://app.warmerly.com/api/v1/workspaces/8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a/members/3c2b1a09-... \
  -X PATCH \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "role": "admin" }'
JSON
{ "ok": true }

Notes:

  • Only an owner may set role: "owner" — doing so transfers ownership; the caller's own role becomes admin.
  • An admin cannot change an owner's role (403 forbidden).

Remove a member

DELETE/workspaces/{id}/members/{userId}

Requires owner or admin to remove another member. A member may remove themselves (leave the workspace) regardless of role, except the owner — the owner must transfer ownership or delete the workspace first (400 bad_request).

An owner can never be removed by another member (403 forbidden).

JSON
{ "ok": true }

Invites

List pending invites

GET/workspaces/{id}/invites

Requires owner or admin.

JSON
{
  "invites": [
    {
      "id": "b2f9...",
      "email": "new.hire@acme.com",
      "role": "member",
      "status": "pending",
      "createdAt": "2026-06-01T09:00:00.000Z",
      "expiresAt": "2026-06-08T09:00:00.000Z"
    }
  ]
}

Invite a member

POST/workspaces/{id}/invites

Requires owner or admin.

FieldTypeRequiredDescription
emailstringYesInvitee's email address.
role"admin" | "member"YesRole granted on acceptance. You cannot invite someone directly as owner.
Shell
curl https://app.warmerly.com/api/v1/workspaces/8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a/invites \
  -X POST \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "email": "new.hire@acme.com", "role": "member" }'
JSON
{
  "invite": {
    "id": "b2f9...",
    "email": "new.hire@acme.com",
    "role": "member",
    "status": "pending",
    "createdAt": "2026-06-01T09:00:00.000Z",
    "expiresAt": "2026-06-08T09:00:00.000Z"
  }
}

201 Created on success. If the email already belongs to a member, returns 409 conflict. A new invite replaces any existing pending invite for the same email. Invites expire after 7 days. If transactional email is configured, an invite email is sent automatically.

Projects

A project hangs off a workspace and is what campaigns, mailboxes, and inbox data attach to via X-Project-Id. Each workspace has exactly one project. Unlike the workspace/member endpoints above, listing and creating projects operates on your active workspace (the workspace currently selected in the dashboard/session) rather than a workspace ID in the URL or header.

List projects

GET/projects
JSON
{
  "projects": [
    {
      "id": "2b6e1c3d-...",
      "name": "Default",
      "slug": "default",
      "description": null,
      "color": null,
      "icon": null,
      "createdAt": "2026-01-10T12:00:00.000Z",
      "campaignCount": 3,
      "accountCount": 5
    }
  ]
}

If your active workspace has no projects yet, a default one is created automatically before the list is returned, so this endpoint never returns an empty array.

Create a project

POST/projects
FieldTypeRequiredDescription
namestringYesProject name.
slugstringYesLowercase letters, numbers, and hyphens only (^[a-z0-9-]+$). A random suffix is appended automatically if the slug is already taken.
descriptionstringNoUp to 2000 characters.
colorstringNoUp to 32 characters.
iconstringNoUp to 64 characters.
defaultTimezonestringNoUp to 64 characters.
Shell
curl https://app.warmerly.com/api/v1/projects \
  -X POST \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q3 Outbound", "slug": "q3-outbound" }'
JSON
{
  "project": {
    "id": "9d4f2a11-...",
    "name": "Q3 Outbound",
    "slug": "q3-outbound",
    "workspaceId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "description": null,
    "color": null,
    "icon": null,
    "defaultTimezone": null,
    "createdAt": "2026-07-01T09:00:00.000Z"
  }
}

201 Created on success. Returns 409 conflict if the active workspace already has a project — a workspace can only ever have one.

Get a project

GET/projects/{id}

Returns the project. Requires membership in the project's workspace.

JSON
{
  "project": {
    "id": "9d4f2a11-...",
    "name": "Q3 Outbound",
    "slug": "q3-outbound",
    "workspaceId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "description": null,
    "color": null,
    "icon": null,
    "defaultTimezone": null,
    "createdAt": "2026-07-01T09:00:00.000Z"
  }
}

Project stats

GET/projects/{id}/stats

A rollup for one project — what the dashboard's project switcher shows.

JSON
{
  "project": { "id": "…", "name": "SaaS Outreach", "slug": "saas-outreach-05adc35c", "workspaceId": "…", "userId": "…" },
  "campaigns": { "total": 3, "active": 2 },
  "accounts": { "total": 12 },
  "leads": { "total": 145 },
  "emails": { "sent": 300, "replied": 17, "replyRate": 5.7 }
}

replyRate is a percentage of real campaign sends — not a warmup figure (see How warmup works for why the two must not be mixed).

Workspace housekeeping

 
POST   /workspaces/{id}/activate
POST   /workspaces/{id}/logo
DELETE /workspaces/{id}/logo
DELETE /workspaces/{id}/invites/{inviteId}

activate switches which workspace is "current" for session-based (dashboard) use and returns { "ok": true }. API-key callers rarely need it: pass X-Workspace-Id on the request instead, which scopes that one call without changing any stored state.

logo takes a multipart/form-data upload and returns { "logoUrl": "…" }; DELETE removes it. DELETE /workspaces/{id}/invites/{inviteId} revokes a pending invite that has not been accepted.

Errors

In addition to the standard error envelope, these endpoints return:

  • 403 forbidden — you're a member of the workspace/project but lack the required role for the action (e.g. a member trying to invite someone, or an admin trying to remove an owner).
  • 404 not_found — the workspace or project doesn't exist, or you're not a member of it (workspace/project membership checks return 404, not 403, to avoid leaking existence to non-members).
  • 409 conflict — inviting an email that's already a member.
  • 400 bad_request — invalid body (e.g. bad slug format), or an owner trying to leave their own workspace without transferring ownership first.

Summarize with AI

Markdown version for LLMsllms.txt

Something unclear or out of date? Ask Warmi in the chat, or email support@warmerly.com.