You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We are shipping a native REST API for managing projects and subprojects under /api/projects. Previously the only way to list/show/update projects and create subprojects programmatically was through the LaML (Accounts) API. This is expected to be live in production with the June 29th afternoon deploy.
Note
The existing /api/project (singular) token-management endpoints are unchanged and unrelated. That path was not a fit for project management: it takes no project ID and only operates on the project the request is authenticated as, so there is no way to act on a specific subproject.
The new /api/projects endpoints accept an{id} and let you manage any subproject under the authenticated root project (as well as the root project itself), which is exactly what project management requires.
Authentication & scope
Auth: HTTP Basic auth using your Project ID (username) and an API token (password), the same as other SignalWire REST APIs.
Required scope: the API token must have the Management scope. A token without it receives 403 Forbidden.
Base URL:https://<your-space>.signalwire.com
All endpoints operate only within the caller's project tree - the authenticated root project and the subprojects beneath it. Any project ID outside that tree returns 404 Not Found.
Endpoints
Method
Path
Description
GET
/api/projects
List the authenticated root project and its subprojects.
GET
/api/projects/{id}
Retrieve a single project or subproject.
POST
/api/projects
Create a subproject under the authenticated root project. Returns the signing key.
PATCH
/api/projects/{id}
Update a project's name and settings.
DELETE
/api/projects/{id}
Delete a subproject.
POST
/api/projects/{id}/signing-key/rotate
Rotate a project's signing key. Returns the new signing key.
Limitations
Create is only allowed for subprojects. A subproject cannot itself contain subprojects, so creating a project while authenticated as a subproject fails with 422 nested_subprojects_not_allowed. Subprojects can only be created under a top-level (root) project.
Delete is only allowed for subprojects. The root/parent project cannot be deleted through this API; attempting it returns 422 only_subprojects_can_be_deleted. Root-project lifecycle is intentionally managed in the UI as it must be tied to an individual user, not a shared project token. We do have a planned follow up to implement APIs with PAT authentication for performing company admin actions - this may include extending the project create/delete API to support root projects if a PAT is supplied.
A project must have no phone numbers before it can be deleted. All phone numbers must be removed first, otherwise the request returns 422 phone_numbers_must_be_removed. (On a successful delete, the subproject' registry brands/campaigns are migrated up to the parent project.)
The signing_key is only returned on create and rotate. All other responses omit it. Users must capture it from the create/rotate response - it is not retrievable via API later.
Fields
Project object (returned by list / show / update / create / rotate)
Field
Type
Notes
id
string (UUID)
Project identifier.
name
string
Project name.
parent_project_id
string (UUID) | null
The root project's ID; null for a root project.
subproject
boolean
true if this is a subproject.
region_preference
string
Effective region preference. Returned in all responses; not currently settable via this API.
protect_recordings
boolean
Setting toggle.
protect_message_media
boolean
Setting toggle.
protect_fax_media
boolean
Setting toggle.
force_https_requests
boolean
Setting toggle.
created_at
string (ISO 8601)
updated_at
string (ISO 8601)
signing_key
string
Only present on POST /api/projects and the signing-key rotate response.
Writable fields (create / update request body)
Field
Type
Notes
name
string
Required. Max 250 characters.
protect_recordings
boolean
Optional.
protect_message_media
boolean
Optional.
protect_fax_media
boolean
Optional.
force_https_requests
boolean
Optional.
Request / response examples
List projects — GET /api/projects
Optional query parameters:
name — filter projects by name.
page_size — number of results per page (1–1000, default 50).
page_token — cursor token for the next/previous page.
page_number — page index (used together with page_token).
Validation/business-rule failures return 422 Unprocessable Entity with a structured error body. Each entry has a type (category), code, human-readable message, the offending attribute (or null for project-level errors), and a url pointing at the error-code reference.
{
"errors": [
{
"type": "validation_error",
"code": "nested_subprojects_not_allowed",
"message": "Subprojects can only be created under a top-level project.",
"attribute": null,
"url": "https://developer.signalwire.com/rest/error-codes#nested_subprojects_not_allowed"
}
]
}
Other status codes:
401 Unauthorized — missing/invalid credentials.
403 Forbidden — the API token lacks the Management scope.
404 Not Found — the project ID does not exist within the caller's project tree.
Error codes to document
HTTP
Code
When it happens
422
nested_subprojects_not_allowed
POST /api/projects while authenticated as a subproject — subprojects can only be created under a top-level project.
422
only_subprojects_can_be_deleted
DELETE targeting a root/parent project — only subprojects can be deleted through this API.
422
phone_numbers_must_be_removed
DELETE of a project that still has phone numbers assigned — remove all numbers first.
422
(validation) name blank or > 250 chars
name is required and limited to 250 characters.
The three new codes (nested_subprojects_not_allowed, only_subprojects_can_be_deleted, phone_numbers_must_be_removed) must be added to the public REST error-code reference.
OpenAPI Spec
openapi: 3.0.3
info:
title: SignalWire Project Management API
description: >
Native REST API for managing projects and subprojects under `/api/projects`.
All endpoints require an API token with the `Management` scope and operate only
within the caller's project tree (the authenticated root project and its
subprojects). Create and delete are only allowed for subprojects.
version: "1.0.0"
servers:
- url: https://{space}.signalwire.com
description: Your SignalWire Space
variables:
space:
default: example-space
description: Your SignalWire Space subdomain
security:
- basicAuth: []
tags:
- name: Projects
description: Manage projects and subprojects.
paths:
/api/projects:
get:
tags: [Projects]
operationId: listProjects
summary: List projects
description: Lists the authenticated root project and its subprojects.
parameters:
- name: name
in: query
description: Filter projects by name.
required: false
schema:
type: string
- $ref: '#/components/parameters/PageSize'
- $ref: '#/components/parameters/PageToken'
- $ref: '#/components/parameters/PageNumber'
responses:
'200':
description: A page of projects.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
post:
tags: [Projects]
operationId: createSubproject
summary: Create a subproject
description: >
Creates a subproject under the authenticated root project. Only allowed
when authenticated as a top-level project. The response includes the
`signing_key`, which is not retrievable afterward.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectCreate'
responses:
'201':
description: The created subproject, including its signing key.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectWithSigningKey'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
description: >
Validation error. Possible codes include `nested_subprojects_not_allowed`
(authenticated as a subproject) and standard `name` validation errors.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/projects/{id}:
parameters:
- $ref: '#/components/parameters/ProjectId'
get:
tags: [Projects]
operationId: getProject
summary: Retrieve a project
responses:
'200':
description: The requested project.
content:
application/json:
schema:
$ref: '#/components/schemas/Project'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Projects]
operationId: updateProject
summary: Update a project
description: Updates the project's name and settings.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdate'
responses:
'200':
description: The updated project.
content:
application/json:
schema:
$ref: '#/components/schemas/Project'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
description: Validation error (e.g. blank or too-long `name`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
tags: [Projects]
operationId: deleteSubproject
summary: Delete a subproject
description: >
Deletes a subproject. Only subprojects can be deleted, and all phone
numbers must be removed first.
responses:
'204':
description: The subproject was deleted.
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
description: >
Business-rule error. Possible codes: `only_subprojects_can_be_deleted`
(target is a root project) and `phone_numbers_must_be_removed`.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/projects/{id}/signing-key/rotate:
parameters:
- $ref: '#/components/parameters/ProjectId'
post:
tags: [Projects]
operationId: rotateSigningKey
summary: Rotate a project's signing key
description: >
Rotates the project's signing key and returns the project with the new
`signing_key`. The previous key may take 1-2 minutes to stop working.
responses:
'200':
description: The project with its new signing key.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectWithSigningKey'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
description: >
HTTP Basic auth. Username is your Project ID; password is an API token
with the `Management` scope.
parameters:
ProjectId:
name: id
in: path
required: true
description: The project or subproject ID.
schema:
type: string
format: uuid
PageSize:
name: page_size
in: query
required: false
description: Number of results per page.
schema:
type: integer
minimum: 1
maximum: 1000
default: 50
PageToken:
name: page_token
in: query
required: false
description: Cursor token for paging (from the `links` in a previous response).
schema:
type: string
PageNumber:
name: page_number
in: query
required: false
description: Page index, used together with `page_token`.
schema:
type: integer
minimum: 0
default: 0
responses:
Unauthorized:
description: Missing or invalid credentials.
Forbidden:
description: The API token lacks the required `Management` scope.
NotFound:
description: The project ID does not exist within the caller's project tree.
schemas:
Project:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
parent_project_id:
type: string
format: uuid
nullable: true
description: The root project's ID; null for a root project.
subproject:
type: boolean
description: True if this project is a subproject.
region_preference:
type: string
readOnly: true
description: >
Effective region preference. Returned in all responses; not currently
settable via this API.
protect_recordings:
type: boolean
protect_message_media:
type: boolean
protect_fax_media:
type: boolean
force_https_requests:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
required:
- id
- name
- parent_project_id
- subproject
- region_preference
- protect_recordings
- protect_message_media
- protect_fax_media
- force_https_requests
- created_at
- updated_at
ProjectWithSigningKey:
allOf:
- $ref: '#/components/schemas/Project'
- type: object
properties:
signing_key:
type: string
description: >
The project's signing key. Only returned on create and rotate;
not retrievable afterward.
required:
- signing_key
ProjectCreate:
type: object
properties:
name:
type: string
maxLength: 250
description: Project name (required).
protect_recordings:
type: boolean
protect_message_media:
type: boolean
protect_fax_media:
type: boolean
force_https_requests:
type: boolean
required:
- name
ProjectUpdate:
allOf:
- $ref: '#/components/schemas/ProjectCreate'
ProjectList:
type: object
properties:
links:
type: object
properties:
self:
type: string
format: uri
first:
type: string
format: uri
next:
type: string
format: uri
description: Present only when more results exist.
prev:
type: string
format: uri
description: Present only when a previous page exists.
required:
- self
- first
data:
type: array
items:
$ref: '#/components/schemas/Project'
required:
- links
- data
Error:
type: object
properties:
type:
type: string
description: Error category (e.g. "validation_error").
code:
type: string
description: Machine-readable error code.
message:
type: string
description: Human-readable error message.
attribute:
type: string
nullable: true
description: The offending field, or null for project-level errors.
url:
type: string
format: uri
description: Link to the error-code reference.
required:
- type
- code
- message
ErrorResponse:
type: object
properties:
errors:
type: array
items:
$ref: '#/components/schemas/Error'
required:
- errors
references https://github.com/signalwire/cloud-product/issues/19427
Summary
We are shipping a native REST API for managing projects and subprojects under
/api/projects. Previously the only way to list/show/update projects and create subprojects programmatically was through the LaML (Accounts) API. This is expected to be live in production with the June 29th afternoon deploy.Note
The existing
/api/project(singular) token-management endpoints are unchanged and unrelated. That path was not a fit for project management: it takes no project ID and only operates on the project the request is authenticated as, so there is no way to act on a specific subproject.The new
/api/projectsendpoints accept an{id}and let you manage any subproject under the authenticated root project (as well as the root project itself), which is exactly what project management requires.Authentication & scope
Managementscope. A token without it receives403 Forbidden.https://<your-space>.signalwire.comAll endpoints operate only within the caller's project tree - the authenticated root project and the subprojects beneath it. Any project ID outside that tree returns
404 Not Found.Endpoints
GET/api/projectsGET/api/projects/{id}POST/api/projectsPATCH/api/projects/{id}DELETE/api/projects/{id}POST/api/projects/{id}/signing-key/rotateLimitations
422 nested_subprojects_not_allowed. Subprojects can only be created under a top-level (root) project.422 only_subprojects_can_be_deleted. Root-project lifecycle is intentionally managed in the UI as it must be tied to an individual user, not a shared project token. We do have a planned follow up to implement APIs with PAT authentication for performing company admin actions - this may include extending the project create/delete API to support root projects if a PAT is supplied.422 phone_numbers_must_be_removed. (On a successful delete, the subproject' registry brands/campaigns are migrated up to the parent project.)signing_keyis only returned on create and rotate. All other responses omit it. Users must capture it from the create/rotate response - it is not retrievable via API later.Fields
Project object (returned by list / show / update / create / rotate)
idnameparent_project_idnullfor a root project.subprojecttrueif this is a subproject.region_preferenceprotect_recordingsprotect_message_mediaprotect_fax_mediaforce_https_requestscreated_atupdated_atsigning_keyPOST /api/projectsand the signing-key rotate response.Writable fields (create / update request body)
nameprotect_recordingsprotect_message_mediaprotect_fax_mediaforce_https_requestsRequest / response examples
List projects —
GET /api/projectsOptional query parameters:
name— filter projects by name.page_size— number of results per page (1–1000, default 50).page_token— cursor token for the next/previous page.page_number— page index (used together withpage_token).Response —
200 OK{ "links": { "self": "https://example-space.signalwire.com/api/projects?page_size=50", "first": "https://example-space.signalwire.com/api/projects?page_size=50", "next": "https://example-space.signalwire.com/api/projects?page_size=50&page_number=1&page_token=PA8f14e45f" }, "data": [ { "id": "b3877739-5c7e-4d4f-9d1a-2f0c8c2f1a11", "name": "Acme Production", "parent_project_id": null, "subproject": false, "region_preference": "us-west", "protect_recordings": false, "protect_message_media": false, "protect_fax_media": false, "force_https_requests": true, "created_at": "2026-06-24T19:00:00Z", "updated_at": "2026-06-24T19:00:00Z" }, { "id": "8f14e45f-ceea-467d-9c2b-7a1d3a9b2c34", "name": "Acme Staging", "parent_project_id": "b3877739-5c7e-4d4f-9d1a-2f0c8c2f1a11", "subproject": true, "region_preference": "us-west", "protect_recordings": false, "protect_message_media": false, "protect_fax_media": false, "force_https_requests": false, "created_at": "2026-06-24T19:05:00Z", "updated_at": "2026-06-24T19:05:00Z" } ] }Show a project —
GET /api/projects/{id}Returns a single project object (
200 OK). An ID outside the caller's tree returns404 Not Found.Create a subproject —
POST /api/projectsRequest
{ "name": "Acme Staging", "protect_recordings": true, "force_https_requests": true }Response —
201 Created(includessigning_key){ "id": "8f14e45f-ceea-467d-9c2b-7a1d3a9b2c34", "name": "Acme Staging", "parent_project_id": "b3877739-5c7e-4d4f-9d1a-2f0c8c2f1a11", "subproject": true, "region_preference": "us-west", "protect_recordings": true, "protect_message_media": false, "protect_fax_media": false, "force_https_requests": true, "created_at": "2026-06-24T19:05:00Z", "updated_at": "2026-06-24T19:05:00Z", "signing_key": "PSK_4d8c2b1a9f3e7c6d5b4a3e2f1d0c9b8a" }Update a project —
PATCH /api/projects/{id}Request
{ "name": "Acme Staging (EU)", "protect_message_media": true }Response —
200 OK— returns the updated project object (nosigning_key).Delete a subproject —
DELETE /api/projects/{id}Response —
204 No Content(empty body).Rotate signing key —
POST /api/projects/{id}/signing-key/rotateResponse —
200 OK— returns the project object with the newsigning_key. The previous key may take about 1-2 minutes to stop working.{ "id": "8f14e45f-ceea-467d-9c2b-7a1d3a9b2c34", "name": "Acme Staging", "parent_project_id": "b3877739-5c7e-4d4f-9d1a-2f0c8c2f1a11", "subproject": true, "region_preference": "us-west", "protect_recordings": true, "protect_message_media": false, "protect_fax_media": false, "force_https_requests": true, "created_at": "2026-06-24T19:05:00Z", "updated_at": "2026-06-24T20:10:00Z", "signing_key": "PSK_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d" }Error responses
Validation/business-rule failures return
422 Unprocessable Entitywith a structured error body. Each entry has atype(category),code, human-readablemessage, the offendingattribute(ornullfor project-level errors), and aurlpointing at the error-code reference.{ "errors": [ { "type": "validation_error", "code": "nested_subprojects_not_allowed", "message": "Subprojects can only be created under a top-level project.", "attribute": null, "url": "https://developer.signalwire.com/rest/error-codes#nested_subprojects_not_allowed" } ] }Other status codes:
401 Unauthorized— missing/invalid credentials.403 Forbidden— the API token lacks theManagementscope.404 Not Found— the project ID does not exist within the caller's project tree.Error codes to document
nested_subprojects_not_allowedPOST /api/projectswhile authenticated as a subproject — subprojects can only be created under a top-level project.only_subprojects_can_be_deletedDELETEtargeting a root/parent project — only subprojects can be deleted through this API.phone_numbers_must_be_removedDELETEof a project that still has phone numbers assigned — remove all numbers first.nameblank or > 250 charsnameis required and limited to 250 characters.The three new codes (
nested_subprojects_not_allowed,only_subprojects_can_be_deleted,phone_numbers_must_be_removed) must be added to the public REST error-code reference.OpenAPI Spec