curl --request POST \
--url https://api.avina.io/v1/workspaces \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Acme West",
"domain": "https://www.acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"admin_emails": [
"owner@acmewest.com"
]
}
'import requests
url = "https://api.avina.io/v1/workspaces"
payload = {
"name": "Acme West",
"domain": "https://www.acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"admin_emails": ["owner@acmewest.com"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Acme West',
domain: 'https://www.acmewest.com',
external_id: 'crm-account-4821',
billed_to: 'parent',
admin_emails: ['owner@acmewest.com']
})
};
fetch('https://api.avina.io/v1/workspaces', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.avina.io/v1/workspaces",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Acme West',
'domain' => 'https://www.acmewest.com',
'external_id' => 'crm-account-4821',
'billed_to' => 'parent',
'admin_emails' => [
'owner@acmewest.com'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.avina.io/v1/workspaces"
payload := strings.NewReader("{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.avina.io/v1/workspaces")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.avina.io/v1/workspaces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "workspace",
"id": "3f2b8a52-7c1e-4d7a-9a51-2d8f0e6c4b10",
"name": "Acme West",
"domain": "acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"parent_workspace": {
"name": "Acme"
},
"created_at": "2026-10-13T17:02:11Z",
"provisioning": {
"status": "in_progress"
},
"invited_admins": [
"owner@acmewest.com"
]
}{
"object": "workspace",
"id": "3f2b8a52-7c1e-4d7a-9a51-2d8f0e6c4b10",
"name": "Acme West",
"domain": "acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"parent_workspace": {
"name": "Acme"
},
"created_at": "2026-10-13T17:02:11Z",
"provisioning": {
"status": "in_progress"
},
"invited_admins": [
"owner@acmewest.com"
]
}{
"error": {
"code": "invalid_request",
"message": "foo is not a recognized field",
"request_id": "req_8f2a",
"details": [
{
"field": "foo",
"message": "is not a recognized field"
}
]
}
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is invalid or revoked.",
"request_id": "req_8f2a"
}
}{
"error": {
"code": "permission_denied",
"message": "Only Enterprise workspaces can create sub-workspaces.",
"request_id": "req_8f2a"
}
}{
"error": {
"code": "workspace_exists",
"message": "A workspace with this external_id already exists with a different name, domain or billed_to.",
"request_id": "req_8f2a",
"details": [
{
"field": "external_id",
"message": "is used by a workspace with different fields"
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "This workspace has reached its hourly limit for creating workspaces. Retry after Retry-After seconds.",
"request_id": "req_8f2a"
}
}Create a sub-workspace
Creates a workspace under this API key’s workspace (its parent). Every workspace created through the API is a sub-workspace of the key’s workspace; there is no way to create a workspace without a parent, and the parent is not a request field.
- Only Enterprise workspaces can create sub-workspaces, and a sub-workspace’s key cannot
(
403 permission_denied). - A new sub-workspace is on the Starter plan and starts setting itself up right away
(
provisioning.status). - Every admin of the parent workspace is an admin of the sub-workspace.
admin_emailsalso get an email invite to join it as admins. billed_to: parentmeans the sub-workspace is paid while the parent’s subscription is;selfmeans it is billed on its own.- Send
external_id(your own reference for the workspace) to make retries safe: a create with anexternal_idalready used under this parent returns that workspace with200whenname,domainandbilled_tomatch, and409 workspace_existswhen they do not. - Each parent can create a limited number of sub-workspaces per hour
(
429 rate_limitedwithRetry-After).
curl --request POST \
--url https://api.avina.io/v1/workspaces \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Acme West",
"domain": "https://www.acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"admin_emails": [
"owner@acmewest.com"
]
}
'import requests
url = "https://api.avina.io/v1/workspaces"
payload = {
"name": "Acme West",
"domain": "https://www.acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"admin_emails": ["owner@acmewest.com"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Acme West',
domain: 'https://www.acmewest.com',
external_id: 'crm-account-4821',
billed_to: 'parent',
admin_emails: ['owner@acmewest.com']
})
};
fetch('https://api.avina.io/v1/workspaces', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.avina.io/v1/workspaces",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Acme West',
'domain' => 'https://www.acmewest.com',
'external_id' => 'crm-account-4821',
'billed_to' => 'parent',
'admin_emails' => [
'owner@acmewest.com'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.avina.io/v1/workspaces"
payload := strings.NewReader("{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.avina.io/v1/workspaces")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.avina.io/v1/workspaces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Acme West\",\n \"domain\": \"https://www.acmewest.com\",\n \"external_id\": \"crm-account-4821\",\n \"billed_to\": \"parent\",\n \"admin_emails\": [\n \"owner@acmewest.com\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "workspace",
"id": "3f2b8a52-7c1e-4d7a-9a51-2d8f0e6c4b10",
"name": "Acme West",
"domain": "acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"parent_workspace": {
"name": "Acme"
},
"created_at": "2026-10-13T17:02:11Z",
"provisioning": {
"status": "in_progress"
},
"invited_admins": [
"owner@acmewest.com"
]
}{
"object": "workspace",
"id": "3f2b8a52-7c1e-4d7a-9a51-2d8f0e6c4b10",
"name": "Acme West",
"domain": "acmewest.com",
"external_id": "crm-account-4821",
"billed_to": "parent",
"parent_workspace": {
"name": "Acme"
},
"created_at": "2026-10-13T17:02:11Z",
"provisioning": {
"status": "in_progress"
},
"invited_admins": [
"owner@acmewest.com"
]
}{
"error": {
"code": "invalid_request",
"message": "foo is not a recognized field",
"request_id": "req_8f2a",
"details": [
{
"field": "foo",
"message": "is not a recognized field"
}
]
}
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is invalid or revoked.",
"request_id": "req_8f2a"
}
}{
"error": {
"code": "permission_denied",
"message": "Only Enterprise workspaces can create sub-workspaces.",
"request_id": "req_8f2a"
}
}{
"error": {
"code": "workspace_exists",
"message": "A workspace with this external_id already exists with a different name, domain or billed_to.",
"request_id": "req_8f2a",
"details": [
{
"field": "external_id",
"message": "is used by a workspace with different fields"
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "This workspace has reached its hourly limit for creating workspaces. Retry after Retry-After seconds.",
"request_id": "req_8f2a"
}
}Authorizations
Workspace API key from the Avina dashboard (Settings → API keys).
Headers
Any unique string. Retrying with the same key returns the first response instead of repeating the action.
255Body
1 - 100The company's website or domain (https://www.acme.com/about or acme.com). Stored and returned as the bare domain (acme.com).
Your own reference for this workspace, unique among this workspace's sub-workspaces. Repeating a create with it returns the existing workspace. Matching ignores case (CRM-42 and crm-42 are the same external_id).
1 - 191parent: the sub-workspace is paid while the parent's subscription is. self: it is billed on its own.
parent, self Each gets an email invite to join the sub-workspace as an admin. Duplicates (ignoring case) are invited once. The parent's admins are admins already and need no invite.
10191Response
The sub-workspace already created with this external_id; nothing new was created and no invites were sent (invited_admins is empty).
"workspace"parent, self Show child attributes
Show child attributes
Show child attributes
Show child attributes
Only in the POST /workspaces response. The admin_emails invited by that request (empty when the request returned an existing workspace). An email that is already a member of the workspace is not invited.

