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.

{
  "workspaces": [
    {
      "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
      "name": "Acme Inc",
      "role": "owner",
      "status": "active",
      "logoUrl": "https://app.warmerly.com/api/images/..."
    }
  ]
}

status is "active" or "pending" (new workspaces beyond your first may require approval before becoming active).

Create a workspace

POST /workspaces
FieldTypeRequiredDescription
namestringYes1–80 characters.
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" }'
{
  "workspace": {
    "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "name": "Acme Inc",
    "role": "owner",
    "status": "active",
    "logoUrl": null
  }
}

The caller becomes the workspace's owner. If you already have a pending workspace awaiting approval, that same workspace is returned 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.

{
  "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.
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" }'
{ "workspace": { "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a", "name": "Acme Incorporated" } }

Delete a workspace

DELETE /workspaces/{id}

Requires owner. Permanently deletes the workspace and everything under it (members, projects, campaigns, accounts).

{ "ok": true }

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.
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" }'
{ "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).

{ "ok": true }

Invites

List pending invites

GET /workspaces/{id}/invites

Requires owner or admin.

{
  "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.
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" }'
{
  "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
{
  "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.
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" }'
{
  "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.

{
  "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"
  }
}

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.