Create a company
Creates a company and its first admin user in one request. This is the supported way for a script or AI agent to sign a merchant up. It takes no credentials.
On success, Fluid emails a signup confirmation link to user.email.
Opening it and clicking Sign in confirms the signup and signs the
merchant in. The link works once and expires after 30 minutes. An admin
signed in to the company with the companies.update permission can ask
for another with POST /api/company/signup-confirmation, which limits
how often it sends. The merchant then signs the
agent in to the Fluid CLI, or creates an API token under Settings → API
Tokens in Fluid admin and gives it to the agent. Authenticated calls are
possible only after that. The response is the same whether or not
user.email already has a Fluid account, and whether or not the mail
was sent.
The response always lists email_confirmation under
requirements.currently_due: a public signup starts unconfirmed. Until an
admin redeems, at Fluid admin, a sign-in code or link emailed to them for
this company (the link above does), the company’s storefront, portal and
account hostnames on the Fluid platform are not served. Signing in to
Fluid admin, and API calls that carry the company’s token, do not wait
for it.
A 422 means no company was created, apart from a rare case where the
database connection is lost mid-request. A 5xx, or no response at all,
can follow a company that was created: retry once with the same
subdomain. What the retry tells you depends on what you sent. When the
subdomain was sent in its normalized form (see company.subdomain),
errors.company.subdomain is taken on that retry usually means the
first request created it, but it does not prove it, because another
signup for the same name gives the same answer. The is taken check
ignores letter case but no other normalization. So when the value sent
differs from its normalized form in more than case, a refused retry
tells you nothing: a held name is refused with a different error, and
that error is the same whether your first request created the company
or another signup holds the name. Either way, the merchant can sign in
to Fluid admin with user.email to see whether the company exists
before you choose another subdomain.
curl --request POST \
--url https://api.fluid.app/api/company \
--header 'Content-Type: application/json' \
--data '
{
"user": {
"first_name": "Michael",
"last_name": "Scott",
"email": "michael.scott@dundermifflin.com"
},
"company": {
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"color": "1F6FEB",
"onboarding_info": {
"usage_type": "business",
"website_setup": "unsure"
}
}
}
'import requests
url = "https://api.fluid.app/api/company"
payload = {
"user": {
"first_name": "Michael",
"last_name": "Scott",
"email": "michael.scott@dundermifflin.com"
},
"company": {
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"color": "1F6FEB",
"onboarding_info": {
"usage_type": "business",
"website_setup": "unsure"
}
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
user: {
first_name: 'Michael',
last_name: 'Scott',
email: 'michael.scott@dundermifflin.com'
},
company: {
name: 'Dunder Mifflin Paper Company',
subdomain: 'dunder-mifflin',
color: '1F6FEB',
onboarding_info: {usage_type: 'business', website_setup: 'unsure'}
}
})
};
fetch('https://api.fluid.app/api/company', 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.fluid.app/api/company",
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([
'user' => [
'first_name' => 'Michael',
'last_name' => 'Scott',
'email' => 'michael.scott@dundermifflin.com'
],
'company' => [
'name' => 'Dunder Mifflin Paper Company',
'subdomain' => 'dunder-mifflin',
'color' => '1F6FEB',
'onboarding_info' => [
'usage_type' => 'business',
'website_setup' => 'unsure'
]
]
]),
CURLOPT_HTTPHEADER => [
"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.fluid.app/api/company"
payload := strings.NewReader("{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
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.fluid.app/api/company")
.header("Content-Type", "application/json")
.body("{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/company")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"company": {
"id": 1405,
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"fluid_shop": "dunder-mifflin.fluid.app"
},
"user_company": {
"id": 902771,
"email": "michael.scott@dundermifflin.com"
},
"requirements": {
"currently_due": [
"email_confirmation"
]
},
"meta": {
"request_id": "3f9c1a7e-5b2d-4c8e-9a61-2d7f0b4e8c13",
"timestamp": "2026-09-21T09:12:45Z"
}
}{
"error": "Forbidden"
}{
"error_message": "Invalid parameters",
"errors": {},
"meta": {
"request_id": "<string>",
"timestamp": "<string>"
}
}{
"error": "Rate limit exceeded",
"message": "<string>",
"retry_after": 123
}Body
Request payload
Response
The company and its admin user were created.
curl --request POST \
--url https://api.fluid.app/api/company \
--header 'Content-Type: application/json' \
--data '
{
"user": {
"first_name": "Michael",
"last_name": "Scott",
"email": "michael.scott@dundermifflin.com"
},
"company": {
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"color": "1F6FEB",
"onboarding_info": {
"usage_type": "business",
"website_setup": "unsure"
}
}
}
'import requests
url = "https://api.fluid.app/api/company"
payload = {
"user": {
"first_name": "Michael",
"last_name": "Scott",
"email": "michael.scott@dundermifflin.com"
},
"company": {
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"color": "1F6FEB",
"onboarding_info": {
"usage_type": "business",
"website_setup": "unsure"
}
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
user: {
first_name: 'Michael',
last_name: 'Scott',
email: 'michael.scott@dundermifflin.com'
},
company: {
name: 'Dunder Mifflin Paper Company',
subdomain: 'dunder-mifflin',
color: '1F6FEB',
onboarding_info: {usage_type: 'business', website_setup: 'unsure'}
}
})
};
fetch('https://api.fluid.app/api/company', 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.fluid.app/api/company",
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([
'user' => [
'first_name' => 'Michael',
'last_name' => 'Scott',
'email' => 'michael.scott@dundermifflin.com'
],
'company' => [
'name' => 'Dunder Mifflin Paper Company',
'subdomain' => 'dunder-mifflin',
'color' => '1F6FEB',
'onboarding_info' => [
'usage_type' => 'business',
'website_setup' => 'unsure'
]
]
]),
CURLOPT_HTTPHEADER => [
"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.fluid.app/api/company"
payload := strings.NewReader("{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
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.fluid.app/api/company")
.header("Content-Type", "application/json")
.body("{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/company")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"user\": {\n \"first_name\": \"Michael\",\n \"last_name\": \"Scott\",\n \"email\": \"michael.scott@dundermifflin.com\"\n },\n \"company\": {\n \"name\": \"Dunder Mifflin Paper Company\",\n \"subdomain\": \"dunder-mifflin\",\n \"color\": \"1F6FEB\",\n \"onboarding_info\": {\n \"usage_type\": \"business\",\n \"website_setup\": \"unsure\"\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"company": {
"id": 1405,
"name": "Dunder Mifflin Paper Company",
"subdomain": "dunder-mifflin",
"fluid_shop": "dunder-mifflin.fluid.app"
},
"user_company": {
"id": 902771,
"email": "michael.scott@dundermifflin.com"
},
"requirements": {
"currently_due": [
"email_confirmation"
]
},
"meta": {
"request_id": "3f9c1a7e-5b2d-4c8e-9a61-2d7f0b4e8c13",
"timestamp": "2026-09-21T09:12:45Z"
}
}{
"error": "Forbidden"
}{
"error_message": "Invalid parameters",
"errors": {},
"meta": {
"request_id": "<string>",
"timestamp": "<string>"
}
}{
"error": "Rate limit exceeded",
"message": "<string>",
"retry_after": 123
}