curl --request POST \
--url https://www.closient.com/account/api/v1/organizations \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"country": "US",
"is_brand": true,
"name": "Acme Foods Inc."
}
'import requests
url = "https://www.closient.com/account/api/v1/organizations"
payload = {
"country": "US",
"is_brand": True,
"name": "Acme Foods Inc."
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({country: 'US', is_brand: true, name: 'Acme Foods Inc.'})
};
fetch('https://www.closient.com/account/api/v1/organizations', 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://www.closient.com/account/api/v1/organizations",
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([
'country' => 'US',
'is_brand' => true,
'name' => 'Acme Foods Inc.'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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://www.closient.com/account/api/v1/organizations"
payload := strings.NewReader("{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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://www.closient.com/account/api/v1/organizations")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/account/api/v1/organizations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}"
response = http.request(request)
puts response.read_body{
"country": "US",
"id": "b2c3d4e5-f678-9012-abcd-ef2345678901",
"is_brand": true,
"metadata": {
"order_id": "6735"
},
"name": "Acme Foods Inc."
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Create organization
Create a new organization for the authenticated user.
A signed-in caller becomes its OWNER; an API-key caller must send issue_api_key: true and receives the new organization’s own manager key, because a key acts only on the organization it belongs to. A data-retention policy is created for it (FSMA 204 requires one before any traceability record can be written), Stripe provisioning for the FREE tier is queued, and an analytics lifecycle event is emitted. No catalog onboarding happens — no product, brand or GTIN claim is created — so the organization is ready to invite members immediately.
A staff account or the catalog-import service account may set provision_billing: false to skip the Stripe task when seeding organizations in bulk; see that field. Any other caller sending it is rejected with 403, and a service account that omits it entirely is rejected with 422 — a script has to state its billing intent rather than inherit a default meant for interactive callers.
An organization created with the importer credential is marked as importer-provisioned: that credential may keep acting inside it, naming it in X-Closient-Organization, until a human owns it.
An organization is created once per name: a name that matches a live organization once case, surrounding space and repeated spaces are ignored is refused with 409. The one exception is the importer credential, which gets back the importer-provisioned organization it already made under that name, until a human owns it.
curl --request POST \
--url https://www.closient.com/account/api/v1/organizations \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"country": "US",
"is_brand": true,
"name": "Acme Foods Inc."
}
'import requests
url = "https://www.closient.com/account/api/v1/organizations"
payload = {
"country": "US",
"is_brand": True,
"name": "Acme Foods Inc."
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({country: 'US', is_brand: true, name: 'Acme Foods Inc.'})
};
fetch('https://www.closient.com/account/api/v1/organizations', 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://www.closient.com/account/api/v1/organizations",
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([
'country' => 'US',
'is_brand' => true,
'name' => 'Acme Foods Inc.'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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://www.closient.com/account/api/v1/organizations"
payload := strings.NewReader("{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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://www.closient.com/account/api/v1/organizations")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/account/api/v1/organizations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"country\": \"US\",\n \"is_brand\": true,\n \"name\": \"Acme Foods Inc.\"\n}"
response = http.request(request)
puts response.read_body{
"country": "US",
"id": "b2c3d4e5-f678-9012-abcd-ef2345678901",
"is_brand": true,
"metadata": {
"order_id": "6735"
},
"name": "Acme Foods Inc."
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Body
Return a manager-role API key for the new organization in this response.
An API key acts only on the organization it belongs to, and keys cannot mint keys, so this is how an integrator that provisions organizations obtains a credential for each one. The key is scoped to the new organization only, is shown exactly once, and is never retrievable again — store it before doing anything else. Further keys must be created by a signed-in member.
Required when the caller is an API key, which must hold the manager role on its own organization: without it the caller would create an organization it can never read, change or remove.
Whether to queue Stripe provisioning for the new organization.
Left true (the default), creating an organization queues a task that creates a live Stripe Customer and Subscription on the FREE tier. That is right for a human signup and wrong for a bulk import: seeding one organization per imported retailer and brand would request thousands of Stripe objects for organizations with no human in them. Sending false skips only that task and records the omission on the organization, so the missing subscription is a visible state rather than a side effect nobody can account for. Everything else is unchanged: the caller still becomes OWNER, the retention policy is still created, and the organization is immediately invite-ready.
false requires a staff account or the catalog-import service account; any other caller sending it is rejected with 403.
A service account must send this field explicitly — true or false — and is rejected with 422 if it omits it. The default is for interactive callers and is not inherited by a script: an importer that simply forgot the flag would silently request thousands of live Stripe customers, which is the outcome the option exists to prevent.
Response
OK
URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$