> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # Projects > Projects group the phone numbers, Resources, and credentials in your Space, and Subprojects nest one level beneath a root Project with their own Project ID and Resources. A Project is the container for what you build in a SignalWire Space. Phone numbers, [Resources](/docs/platform/resources), and [API credentials](/docs/platform/your-signalwire-api-space) all belong to exactly one Project, which is how you group work by customer, by environment, or by any other classification you like. Your Space starts with one Project, created alongside the Space itself, and you can add more at any time. ## What a Project scopes Each Project carries its own Project ID and its own API tokens, and every request you make is authenticated as one Project and acts on that Project alone. Security settings belong to the Project too. Each Project has its own [media URL protection](/docs/platform/media-protection) settings for recordings, message media, and fax media, and its own choice of whether webhooks and callbacks must use HTTPS. Not everything divides along Project lines. Voice and messaging [rate limits](/docs/platform/rate-limits) are account-level, counted across every Project in your Space. ## Work with Projects in the Dashboard The Project name at the top of the Dashboard opens the Project menu, where every Project-level action starts: * **Project Configuration** opens the active Project's settings, including its [media URL protection](/docs/platform/media-protection) toggles. * **Switch Project** changes which Project the Dashboard shows. * **Create New Project** adds a Project to your Space. A Project's Project ID, Space URL, API tokens, and signing key are on its [API Credentials](/docs/platform/your-signalwire-api-space) page, which always shows the active Project — switch Projects to reach another one's credentials. ## Subprojects A Subproject is a Project nested one level beneath a root Project. It is a full Project with its own Project ID and Resources, but a Subproject cannot contain other Subprojects. Subprojects can be created and deleted through the API with a Project's API token, so an application can open and retire a Project per customer, per tenant, or per environment on its own. Root Projects are created in the Dashboard, or through the same API with a [Personal access token](/docs/apis/authorization#personal-access-tokens). That token carries your owner or admin role in the Space rather than a Project's scopes, so it can create and delete root Projects too. ### Create a Subproject Authenticate as the root Project and send the Subproject's name, along with any security settings you want it to start with. A Subproject cannot create another Subproject, so a request authenticated as a Subproject fails with `422 nested_subprojects_not_allowed`. ### Request POST https\://%7BYour\_Space\_Name%7D.signalwire.com/api/projects ```curl curl -X POST https://{your_space_name}.signalwire.com/api/projects \ -H "Content-Type: application/json" \ -u ":" \ -d '{ "name": "Acme Staging" }' ``` ```python import requests url = "https://{your_space_name}.signalwire.com/api/projects" payload = { "name": "Acme Staging" } headers = { "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers, auth=("", "")) print(response.json()) ``` ```javascript const url = 'https://{your_space_name}.signalwire.com/api/projects'; const credentials = btoa(":"); const options = { method: 'POST', headers: { Authorization: `Basic ${credentials}`, 'Content-Type': 'application/json' }, body: '{"name":"Acme Staging"}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://{your_space_name}.signalwire.com/api/projects" payload := strings.NewReader("{\n \"name\": \"Acme Staging\"\n}") req, _ := http.NewRequest("POST", url, payload) req.SetBasicAuth("", "") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://{your_space_name}.signalwire.com/api/projects") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request.basic_auth("", "") request["Content-Type"] = 'application/json' request.body = "{\n \"name\": \"Acme Staging\"\n}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://{your_space_name}.signalwire.com/api/projects") .basicAuth("", "") .header("Content-Type", "application/json") .body("{\n \"name\": \"Acme Staging\"\n}") .asString(); ``` ```php request('POST', 'https://{your_space_name}.signalwire.com/api/projects', [ 'body' => '{ "name": "Acme Staging" }', 'headers' => [ 'Content-Type' => 'application/json', ], 'auth' => ['', ''], ]); echo $response->getBody(); ``` ```csharp using RestSharp; using RestSharp.Authenticators; var client = new RestClient("https://{your_space_name}.signalwire.com/api/projects"); client.Authenticator = new HttpBasicAuthenticator("", ""); var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"name\": \"Acme Staging\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let credentials = Data(":".utf8).base64EncodedString() let headers = [ "Authorization": "Basic \(credentials)", "Content-Type": "application/json" ] let parameters = ["name": "Acme Staging"] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://{your_space_name}.signalwire.com/api/projects")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` SignalWire signs every request it makes to your webhooks with the Project's `signing_key`, so your server can [verify that a request really came from SignalWire](/docs/swml/guides/webhook-security). The create response is the only place the API returns that key, so capture it here. Afterward you can read it from the new Project's **API Credentials** page in the Dashboard, or replace it with [Rotate a project's signing key](/docs/apis/rest/projects/rotate-signing-key). ### Response (201) ```json { "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": true, "created_at": "2024-05-06T12:20:00Z", "updated_at": "2024-05-06T12:20:00Z", "signing_key": "PSK_4d8c2b1a9f3e7c6d5b4a3e2f1d0c9b8a" } ``` To get credentials for the new Subproject, call [Create API token](/docs/apis/rest/project-tokens/create-token) from the root Project with the Subproject's ID in `subproject_id`. ### Delete a Subproject With a Project's API token, only Subprojects can be deleted; targeting the root Project returns `422 only_subprojects_can_be_deleted`. A Personal access token can delete a root Project as well, once its Subprojects are gone (`422 subprojects_must_be_removed`), and the last Project in a Space can't be deleted (`422 last_project_cannot_be_deleted`). > **Release phone numbers first** > > A Project must have no phone numbers before it can be deleted, or the request returns > `422 phone_numbers_must_be_removed`. > Deleting a Subproject also migrates its registry brands and campaigns up to the parent Project. ### Request DELETE https\://%7BYour\_Space\_Name%7D.signalwire.com/api/projects/\{id} ```curl curl -X DELETE https://{your_space_name}.signalwire.com/api/projects/id \ -u ":" ``` ```python import requests url = "https://{your_space_name}.signalwire.com/api/projects/id" response = requests.delete(url, auth=("", "")) print(response.json()) ``` ```javascript const url = 'https://{your_space_name}.signalwire.com/api/projects/id'; const credentials = btoa(":"); const options = {method: 'DELETE', headers: {Authorization: `Basic ${credentials}`}}; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "net/http" "io" ) func main() { url := "https://{your_space_name}.signalwire.com/api/projects/id" req, _ := http.NewRequest("DELETE", url, nil) req.SetBasicAuth("", "") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://{your_space_name}.signalwire.com/api/projects/id") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Delete.new(url) request.basic_auth("", "") response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.delete("https://{your_space_name}.signalwire.com/api/projects/id") .basicAuth("", "") .asString(); ``` ```php request('DELETE', 'https://{your_space_name}.signalwire.com/api/projects/id', [ 'headers' => [ ], 'auth' => ['', ''], ]); echo $response->getBody(); ``` ```csharp using RestSharp; using RestSharp.Authenticators; var client = new RestClient("https://{your_space_name}.signalwire.com/api/projects/id"); client.Authenticator = new HttpBasicAuthenticator("", ""); var request = new RestRequest(Method.DELETE); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let credentials = Data(":".utf8).base64EncodedString() let headers = ["Authorization": "Basic \(credentials)"] let request = NSMutableURLRequest(url: NSURL(string: "https://{your_space_name}.signalwire.com/api/projects/id")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "DELETE" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` ## Manage Projects through the API Every Projects API request reaches only the authenticated Project and the Subprojects beneath it. [List projects](/docs/apis/rest/projects/list-projects) returns the authenticated Project alongside its Subprojects, and each entry reports whether it is a Subproject and which Project it belongs to. A Project ID outside that tree returns `404 Not Found`. With a Personal access token the tree is wider: every root Project you are enabled for, plus their Subprojects, and you can pick which members reach which root Projects with the [Space Administration API](/docs/apis/rest/space/members/list-member-projects). [Update a project](/docs/apis/rest/projects/update-project) changes the name and security settings of any Project in that tree, root Project included. [Rotate a project's signing key](/docs/apis/rest/projects/rotate-signing-key) issues a new webhook-signing key for one; the old key keeps working for a minute or two. Integrations built around Compatibility API Account SIDs see the same tree as Accounts: [Create Subprojects](/docs/compatibility-api/rest/accounts/create-subprojects) and [List accounts](/docs/compatibility-api/rest/accounts/list-accounts) cover the same ground with form-encoded requests. ## Next steps #### [Projects API reference](/docs/apis/rest/projects/list-projects) Every operation for listing, creating, updating, and deleting Projects. #### [API credentials](/docs/platform/your-signalwire-api-space) Find a Project's Project ID and Space URL, and issue API tokens. > Projects group the phone numbers, Resources, and credentials in your Space, and Subprojects nest one level beneath a root Project with their own Project ID and Resources.