Business Entity Formation Workflow

🏛️

Form new business entities through the SingleFile API: retrieve the jurisdiction's schema, build a structured payload, preview it, then submit. Supporting documents can be attached, but the structured fields are always what the filing is built from.

How It Works

Your App                    SingleFile API               State Authority
   |                             |                             |
   |--- GET /schemas ----------->|                             |
   |<-- field requirements ------|                             |
   |                             |                             |
   |--- POST /orders ----------->|                             |
   |    (?preview=true)          |                             |
   |<-- priced order_items ------|                             |
   |                             |                             |
   |--- POST /orders ----------->|---- files with state ------>|
   |<-- id + order_id -----------|                             |
   |                             |<--- approval/rejection -----|
   |<-- webhook event -----------|                             |

All examples use the sandbox host, https://api.demo.singlefile.ai. Production is https://api.app.singlefile.io. Every resource id in the API is a UUID.

Prerequisites

  • Valid API access token
  • The UUID of the organization the new entity belongs to (Step 1)
  • Entity type and jurisdiction
  • The information the jurisdiction's schema requires (Step 2); for a Delaware LLC that is the entity name, the registered agent's name and Delaware address, the organizer's name, and the authorized person's name

Step 1: Find the Organization

A formation order must name the organization the new entity belongs to, as organization: {"id": "<uuid>"}. List your organizations to find it. The list is paginated and supports ?search=<name>.

ℹ️

Creating organizations through the API (POST /organizations/create) is available only to law-firm accounts. A company account receives 403 PERMISSION_DENIED; use the organization you already have.

ACCESS_TOKEN="your_bearer_token_here"
BASE_URL="https://api.demo.singlefile.ai/external-api/v1"

curl -s -G "$BASE_URL/organizations/list" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode "search=Acme Ventures"
import requests

BASE_URL = "https://api.demo.singlefile.ai/external-api/v1"
ACCESS_TOKEN = "your_bearer_token_here"
HEADERS = {"Authorization": f"Bearer {ACCESS_TOKEN}"}

def find_organization(org_name):
    """Return the UUID of the organization with this exact name."""
    response = requests.get(f"{BASE_URL}/organizations/list", params={"search": org_name, "page_size": 100}, headers=HEADERS)
    response.raise_for_status()
    for org in response.json()["data"]:
        if org["name"] == org_name:
            return org["id"]
    raise Exception(f"Organization '{org_name}' not found")

organization_id = find_organization("Acme Ventures LLC")
const BASE_URL = "https://api.demo.singlefile.ai/external-api/v1";
const ACCESS_TOKEN = "your_bearer_token_here";
const HEADERS = { Authorization: `Bearer ${ACCESS_TOKEN}` };

async function findOrganization(orgName) {
  const params = new URLSearchParams({ search: orgName, page_size: "100" });
  const res = await fetch(`${BASE_URL}/organizations/list?${params}`, { headers: HEADERS });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const { data } = await res.json();
  const org = data.find((o) => o.name === orgName);
  if (!org) throw new Error(`Organization '${orgName}' not found`);
  return org.id;
}

const organizationId = await findOrganization("Acme Ventures LLC");
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

const (
	baseURL     = "https://api.demo.singlefile.ai/external-api/v1"
	accessToken = "your_bearer_token_here"
)

func findOrganization(orgName string) (string, error) {
	params := url.Values{"search": {orgName}, "page_size": {"100"}}
	req, _ := http.NewRequest("GET", baseURL+"/organizations/list?"+params.Encode(), nil)
	req.Header.Set("Authorization", "Bearer "+accessToken)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	var result struct {
		Data []struct {
			ID   string `json:"id"`
			Name string `json:"name"`
		} `json:"data"`
	}
	json.NewDecoder(resp.Body).Decode(&result)
	for _, org := range result.Data {
		if org.Name == orgName {
			return org.ID, nil
		}
	}
	return "", fmt.Errorf("organization %q not found", orgName)
}
using System.Net.Http.Json;
using System.Text.Json;

const string BaseUrl = "https://api.demo.singlefile.ai/external-api/v1";
const string AccessToken = "your_bearer_token_here";

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {AccessToken}");

async Task<string> FindOrganization(string orgName)
{
    var doc = await client.GetFromJsonAsync<JsonDocument>($"{BaseUrl}/organizations/list?search={Uri.EscapeDataString(orgName)}&page_size=100");
    foreach (var org in doc!.RootElement.GetProperty("data").EnumerateArray())
        if (org.GetProperty("name").GetString() == orgName)
            return org.GetProperty("id").GetString()!;
    throw new Exception($"Organization '{orgName}' not found");
}

var organizationId = await FindOrganization("Acme Ventures LLC");

Step 2: Retrieve the Formation Schema

The schema is the contract for the order payload: its required list and properties define exactly what to send. Retrieve it for your entity type and jurisdiction, and read it from data.schema in the response (data also carries entity_type, filing_type, jurisdiction and version).

ParameterDescriptionExamples
entity_typeType of business entityllc, corporation, limited partnership
filing_typeFiling actionformation, foreign qualification
jurisdictionState for filingdelaware, california, texas

Query parameter values are case-insensitive. The payload is stricter: use the exact spellings the schema gives (for a Delaware LLC, entity_type must be LLC or Limited Liability Company; filing_type Formation and jurisdiction Delaware accept any case).

curl -s -G "$BASE_URL/schemas" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d "entity_type=llc" -d "filing_type=formation" -d "jurisdiction=delaware"
def get_formation_schema(entity_type, jurisdiction):
    response = requests.get(
        f"{BASE_URL}/schemas",
        params={"entity_type": entity_type, "filing_type": "formation", "jurisdiction": jurisdiction},
        headers=HEADERS,
    )
    if response.status_code == 400:
        raise Exception(f"Unsupported parameters: {response.json()['error']['message']}")
    response.raise_for_status()
    return response.json()["data"]["schema"]

formation_schema = get_formation_schema("llc", "delaware")
print("Required fields:", formation_schema["required"])
async function getFormationSchema(entityType, jurisdiction) {
  const params = new URLSearchParams({ entity_type: entityType, filing_type: "formation", jurisdiction });
  const res = await fetch(`${BASE_URL}/schemas?${params}`, { headers: HEADERS });
  if (res.status === 400) throw new Error(`Unsupported parameters: ${(await res.json()).error.message}`);
  if (!res.ok) throw new Error(`Failed to retrieve schema: ${res.status}`);
  return (await res.json()).data.schema;
}

const formationSchema = await getFormationSchema("llc", "delaware");
console.log("Required fields:", formationSchema.required);
func getFormationSchema(entityType, jurisdiction string) (map[string]interface{}, error) {
	params := url.Values{"entity_type": {entityType}, "filing_type": {"formation"}, "jurisdiction": {jurisdiction}}
	req, _ := http.NewRequest("GET", baseURL+"/schemas?"+params.Encode(), nil)
	req.Header.Set("Authorization", "Bearer "+accessToken)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("failed to retrieve schema: %d", resp.StatusCode)
	}
	var result struct {
		Data struct {
			Schema map[string]interface{} `json:"schema"`
		} `json:"data"`
	}
	json.NewDecoder(resp.Body).Decode(&result)
	return result.Data.Schema, nil
}
async Task<JsonElement> GetFormationSchema(string entityType, string jurisdiction)
{
    var url = $"{BaseUrl}/schemas?entity_type={Uri.EscapeDataString(entityType)}&filing_type=formation&jurisdiction={Uri.EscapeDataString(jurisdiction)}";
    var res = await client.GetAsync(url);
    if (!res.IsSuccessStatusCode) throw new Exception($"Failed to retrieve schema: {res.StatusCode}");
    var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
    return doc.RootElement.GetProperty("data").GetProperty("schema").Clone();
}

Invalid parameter values return 400 VALIDATION_ERROR (for example "Invalid jurisdiction"). A valid combination SingleFile has no jurisdiction-specific schema for returns a generic schema; check the $id in the response.

Delaware LLC schema at a glance (version 2):

FieldTypeRequiredNotes
organizationobjectYes{"id": "<organization uuid>"}
entity_typeenumYesLLC or Limited Liability Company (case-sensitive)
filing_typeconstYesFormation
jurisdictionconstYesDelaware
entity_namestringYesMust contain "LLC", "L.L.C." or "Limited Liability Company"
registered_agent_namestringYesDelaware registered agent
registered_agent_addressobjectYesstreet_address, city, state, postal_code (Delaware address)
organizer_namestringYesPerson or entity filing the Certificate of Formation
authorized_person_namestringYesPerson who signs the certificate
primary_contactobjectNofirst_name, last_name, email, optional telephone_number (must be a valid number)
primary_address, mailing_addressobjectNoUS addresses need state (two-letter) and postal_code
add_certified_copyboolNoAdds a certified copy line item
variantenumNostandard (default), next-day, same-day, 2-hour, 1-hour
comments, client_matter_number, is_draft, purpose, duration, effective_date, management_structure, authorized_person_title, billing_contactNoSee the schema for shapes
documentobjectNoSupporting document, see "Attaching documents" below

Step 3: Build the Formation Payload

A payload that passes the Delaware LLC schema:

{
  "organization": { "id": "c65ee965-4531-4176-a6a9-bbe0401e6037" },
  "entity_name": "Acme AI Solutions LLC",
  "entity_type": "LLC",
  "jurisdiction": "Delaware",
  "filing_type": "Formation",
  "registered_agent_name": "Delaware Registered Agent Services",
  "registered_agent_address": {
    "street_address": "1000 N. West Street",
    "city": "Wilmington",
    "state": "DE",
    "postal_code": "19801"
  },
  "organizer_name": "John Doe",
  "authorized_person_name": "John Doe",
  "primary_contact": {
    "first_name": "John",
    "last_name": "Doe",
    "email": "[email protected]",
    "telephone_number": "+1-202-555-0179"
  },
  "primary_address": {
    "street_address": "123 Innovation Drive",
    "city": "San Francisco",
    "state": "CA",
    "postal_code": "94105",
    "country": "US"
  },
  "add_certified_copy": true,
  "comments": "Standard Delaware LLC formation"
}
# Save the JSON above as payload.json with your organization id; Steps 4 and 5 send it with -d @payload.json
def create_formation_payload(organization_id, entity_name):
    return {
        "organization": {"id": organization_id},
        "entity_name": entity_name,
        "entity_type": "LLC",
        "jurisdiction": "Delaware",
        "filing_type": "Formation",
        "registered_agent_name": "Delaware Registered Agent Services",
        "registered_agent_address": {
            "street_address": "1000 N. West Street",
            "city": "Wilmington",
            "state": "DE",
            "postal_code": "19801",
        },
        "organizer_name": "John Doe",
        "authorized_person_name": "John Doe",
        "primary_contact": {
            "first_name": "John",
            "last_name": "Doe",
            "email": "[email protected]",
            "telephone_number": "+1-202-555-0179",
        },
        "primary_address": {
            "street_address": "123 Innovation Drive",
            "city": "San Francisco",
            "state": "CA",
            "postal_code": "94105",
            "country": "US",
        },
        "add_certified_copy": True,
        "comments": "Standard Delaware LLC formation",
    }

formation_payload = create_formation_payload(organization_id, "Acme AI Solutions LLC")
function createFormationPayload(organizationId, entityName) {
  return {
    organization: { id: organizationId },
    entity_name: entityName,
    entity_type: "LLC",
    jurisdiction: "Delaware",
    filing_type: "Formation",
    registered_agent_name: "Delaware Registered Agent Services",
    registered_agent_address: { street_address: "1000 N. West Street", city: "Wilmington", state: "DE", postal_code: "19801" },
    organizer_name: "John Doe",
    authorized_person_name: "John Doe",
    primary_contact: { first_name: "John", last_name: "Doe", email: "[email protected]", telephone_number: "+1-202-555-0179" },
    primary_address: { street_address: "123 Innovation Drive", city: "San Francisco", state: "CA", postal_code: "94105", country: "US" },
    add_certified_copy: true,
    comments: "Standard Delaware LLC formation",
  };
}

const formationPayload = createFormationPayload(organizationId, "Acme AI Solutions LLC");
func createFormationPayload(organizationID, entityName string) map[string]interface{} {
	return map[string]interface{}{
		"organization":           map[string]string{"id": organizationID},
		"entity_name":            entityName,
		"entity_type":            "LLC",
		"jurisdiction":           "Delaware",
		"filing_type":            "Formation",
		"registered_agent_name":  "Delaware Registered Agent Services",
		"registered_agent_address": map[string]string{"street_address": "1000 N. West Street", "city": "Wilmington", "state": "DE", "postal_code": "19801"},
		"organizer_name":         "John Doe",
		"authorized_person_name": "John Doe",
		"primary_contact":        map[string]string{"first_name": "John", "last_name": "Doe", "email": "[email protected]", "telephone_number": "+1-202-555-0179"},
		"primary_address":        map[string]string{"street_address": "123 Innovation Drive", "city": "San Francisco", "state": "CA", "postal_code": "94105", "country": "US"},
		"add_certified_copy":     true,
		"comments":               "Standard Delaware LLC formation",
	}
}
Dictionary<string, object> CreateFormationPayload(string organizationId, string entityName) => new()
{
    ["organization"] = new { id = organizationId },
    ["entity_name"] = entityName,
    ["entity_type"] = "LLC",
    ["jurisdiction"] = "Delaware",
    ["filing_type"] = "Formation",
    ["registered_agent_name"] = "Delaware Registered Agent Services",
    ["registered_agent_address"] = new { street_address = "1000 N. West Street", city = "Wilmington", state = "DE", postal_code = "19801" },
    ["organizer_name"] = "John Doe",
    ["authorized_person_name"] = "John Doe",
    ["primary_contact"] = new { first_name = "John", last_name = "Doe", email = "[email protected]", telephone_number = "+1-202-555-0179" },
    ["primary_address"] = new { street_address = "123 Innovation Drive", city = "San Francisco", state = "CA", postal_code = "94105", country = "US" },
    ["add_certified_copy"] = true,
    ["comments"] = "Standard Delaware LLC formation",
};

var formationPayload = CreateFormationPayload(organizationId, "Acme AI Solutions LLC");

Validation happens server-side in the preview step; you do not need a local JSON Schema validator.

Step 4: Preview the Order

POST /orders?preview=true validates the payload and prices it without creating anything. A 200 response is the order as it would be created, with preview: true, order_id: "PREVIEW", the line items in order_items and the sum in total_amount. A 400 SCHEMA_VALIDATION_ERROR lists every failing field in error.errors. Previews never contact the state, so you can call them freely.

To get a price before you have the full payload, add "single_file_data": {"is_estimate": true}; required-field checks are skipped for that preview only.

curl -s -X POST "$BASE_URL/orders?preview=true" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json
def preview_formation_order(payload):
    response = requests.post(f"{BASE_URL}/orders", params={"preview": "true"}, json=payload, headers=HEADERS)
    body = response.json()
    if response.status_code == 200:
        order = body["data"]
        print(f"Preview accepted. Total: ${order['total_amount']}")
        for item in order["order_items"]:
            print(f"  {item['product_name']}: ${item['total_cost']}")
        return True
    print(f"Preview failed: {body['error']['message']}")
    for err in body["error"].get("errors", []):
        print(f"  {err['field']}: {err['message']}")
    return False

if not preview_formation_order(formation_payload):
    raise Exception("Formation order preview failed")
async function previewFormationOrder(payload) {
  const res = await fetch(`${BASE_URL}/orders?preview=true`, {
    method: "POST",
    headers: { ...HEADERS, "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const body = await res.json();
  if (res.status === 200) {
    console.log(`Preview accepted. Total: $${body.data.total_amount}`);
    body.data.order_items.forEach((i) => console.log(`  ${i.product_name}: $${i.total_cost}`));
    return true;
  }
  console.error(`Preview failed: ${body.error.message}`);
  (body.error.errors ?? []).forEach((e) => console.error(`  ${e.field}: ${e.message}`));
  return false;
}

if (!(await previewFormationOrder(formationPayload))) throw new Error("Formation order preview failed");
func previewFormationOrder(payload map[string]interface{}) bool {
	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", baseURL+"/orders?preview=true", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+accessToken)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return false
	}
	defer resp.Body.Close()
	var result struct {
		Data  struct{ TotalAmount string `json:"total_amount"` } `json:"data"`
		Error struct {
			Message string `json:"message"`
			Errors  []struct{ Field, Message string } `json:"errors"`
		} `json:"error"`
	}
	json.NewDecoder(resp.Body).Decode(&result)
	if resp.StatusCode == 200 {
		fmt.Println("Preview accepted. Total:", result.Data.TotalAmount)
		return true
	}
	fmt.Println("Preview failed:", result.Error.Message)
	for _, e := range result.Error.Errors {
		fmt.Printf("  %s: %s\n", e.Field, e.Message)
	}
	return false
}
async Task<bool> PreviewFormationOrder(object payload)
{
    var res = await client.PostAsJsonAsync($"{BaseUrl}/orders?preview=true", payload);
    var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
    if (res.IsSuccessStatusCode)
    {
        Console.WriteLine($"Preview accepted. Total: ${doc.RootElement.GetProperty("data").GetProperty("total_amount")}");
        return true;
    }
    var error = doc.RootElement.GetProperty("error");
    Console.WriteLine($"Preview failed: {error.GetProperty("message")}");
    if (error.TryGetProperty("errors", out var errors))
        foreach (var e in errors.EnumerateArray())
            Console.WriteLine($"  {e.GetProperty("field")}: {e.GetProperty("message")}");
    return false;
}

Step 5: Submit the Order

Submit the same payload without the preview parameter. A 201 response carries only two fields: id, the UUID you use in URLs and receive in webhooks, and order_id, a human-readable reference. Fetch GET /orders/{id} for status and everything else.

At submission SingleFile also checks the entity name: a name that already exists in SingleFile returns 409 ENTITY_NAME_EXISTS, and a name already registered with the state returns 400 ENTITY_NAME_EXISTS, each with the conflicting name in the message.

curl -s -X POST "$BASE_URL/orders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json
# {"success": true, "data": {"id": "53aa6c4f-726b-4776-be03-3cd2faf9a1ac", "order_id": "bJmnXG5v-iwEA7Kff"}}

curl -s "$BASE_URL/orders/53aa6c4f-726b-4776-be03-3cd2faf9a1ac" -H "Authorization: Bearer $ACCESS_TOKEN"
def submit_formation_order(payload):
    response = requests.post(f"{BASE_URL}/orders", json=payload, headers=HEADERS)
    body = response.json()
    if response.status_code == 201:
        order = body["data"]
        print(f"Submitted. id={order['id']} order_id={order['order_id']}")
        return order
    print(f"Submission failed: {body['error']['message']}")
    return None

def get_order(order_uuid):
    response = requests.get(f"{BASE_URL}/orders/{order_uuid}", headers=HEADERS)
    response.raise_for_status()
    return response.json()["data"]

submitted = submit_formation_order(formation_payload)
order = get_order(submitted["id"])
print(order["status"], order["entity"]["id"], order["total_amount"])
async function submitFormationOrder(payload) {
  const res = await fetch(`${BASE_URL}/orders`, {
    method: "POST",
    headers: { ...HEADERS, "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const body = await res.json();
  if (res.status === 201) {
    console.log(`Submitted. id=${body.data.id} order_id=${body.data.order_id}`);
    return body.data;
  }
  console.error(`Submission failed: ${body.error.message}`);
  return null;
}

async function getOrder(orderUuid) {
  const res = await fetch(`${BASE_URL}/orders/${orderUuid}`, { headers: HEADERS });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  return (await res.json()).data;
}

const submitted = await submitFormationOrder(formationPayload);
const order = await getOrder(submitted.id);
console.log(order.status, order.entity.id, order.total_amount);
func submitFormationOrder(payload map[string]interface{}) (map[string]interface{}, error) {
	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", baseURL+"/orders", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+accessToken)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var result struct {
		Data map[string]interface{} `json:"data"`
	}
	json.NewDecoder(resp.Body).Decode(&result)
	if resp.StatusCode != 201 {
		return nil, fmt.Errorf("submission failed: %d", resp.StatusCode)
	}
	return result.Data, nil // {"id": "<uuid>", "order_id": "<reference>"}
}
async Task<JsonElement?> SubmitFormationOrder(object payload)
{
    var res = await client.PostAsJsonAsync($"{BaseUrl}/orders", payload);
    var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
    if (res.StatusCode != System.Net.HttpStatusCode.Created) return null;
    return doc.RootElement.GetProperty("data").Clone(); // id + order_id
}

The order detail returns status (received, submitted, filed, completed, and so on), status_history, entity (the new entity's UUID once created), order_items, total_amount and documents. Register a webhook to receive order.submitted, order.completed and document.created events instead of polling.

Drafts

Send "is_draft": true to store the order without placing it. Update it with PUT /orders/{id} (full payload) and place it with POST /orders/{id}. Draft orders are attributed to the API key's owner, so on_behalf_of cannot be combined with is_draft.

Complete Formation Workflow

def complete_formation_workflow(org_name, entity_name):
    organization_id = find_organization(org_name)
    schema = get_formation_schema("llc", "delaware")
    missing = [f for f in schema["required"] if f not in {"organization", "entity_type", "filing_type", "jurisdiction", "entity_name",
               "registered_agent_name", "registered_agent_address", "organizer_name", "authorized_person_name"}]
    if missing:
        raise Exception(f"Schema now requires fields this code does not send: {missing}")

    payload = create_formation_payload(organization_id, entity_name)
    if not preview_formation_order(payload):
        raise Exception("Order preview failed")
    submitted = submit_formation_order(payload)
    if not submitted:
        raise Exception("Order submission failed")
    return {"organization_id": organization_id, "id": submitted["id"], "order_id": submitted["order_id"]}

result = complete_formation_workflow("Acme Ventures LLC", "Acme AI Solutions LLC")
async function completeFormationWorkflow(orgName, entityName) {
  const organizationId = await findOrganization(orgName);
  const schema = await getFormationSchema("llc", "delaware");
  const known = new Set(["organization", "entity_type", "filing_type", "jurisdiction", "entity_name",
    "registered_agent_name", "registered_agent_address", "organizer_name", "authorized_person_name"]);
  const missing = schema.required.filter((f) => !known.has(f));
  if (missing.length) throw new Error(`Schema now requires fields this code does not send: ${missing}`);

  const payload = createFormationPayload(organizationId, entityName);
  if (!(await previewFormationOrder(payload))) throw new Error("Order preview failed");
  const submitted = await submitFormationOrder(payload);
  if (!submitted) throw new Error("Order submission failed");
  return { organization_id: organizationId, id: submitted.id, order_id: submitted.order_id };
}

const result = await completeFormationWorkflow("Acme Ventures LLC", "Acme AI Solutions LLC");

Attaching Documents

A formation order can carry one supporting document (for example a signed operating agreement or a document your client provided) in the document field. The document is stored with the order for the filing team; it does not replace any schema field, and the structured payload is still validated in full.

Two ways to attach it:

Reference an uploaded document (recommended). Upload the file first with POST /entities/{entity_id}/documents/upload or POST /organizations/{organization_id}/documents/upload (multipart file plus filing_type; PDF or DOCX up to 50 MB), then reference it:

"document": { "id": "79f521ea-ca24-4b5f-89a6-4aad147ecfa3" }

The document stays visible through the documents endpoints, so you can confirm it before and after the order.

Inline upload. Send the file base64-encoded inside the order payload:

"document": {
  "filename": "operating-agreement.pdf",
  "content_type": "application/pdf",
  "content": "<base64-encoded file bytes>"
}
import base64

def attach_document_inline(payload, path):
    with open(path, "rb") as f:
        payload["document"] = {
            "filename": path.split("/")[-1],
            "content_type": "application/pdf",
            "content": base64.b64encode(f.read()).decode("ascii"),
        }
    return payload
import { readFile } from "fs/promises";
import { basename } from "path";

async function attachDocumentInline(payload, path) {
  payload.document = {
    filename: basename(path),
    content_type: "application/pdf",
    content: (await readFile(path)).toString("base64"),
  };
  return payload;
}

Notes on inline uploads:

  • Previews do not store the document; it is uploaded only when the order is created.
  • The order detail returns the new document's id in document, but an inline upload is stored hidden until the order is processed, so GET /documents/{id} returns 404 until then. Upload-then-reference avoids this.
  • document must be either {id} or {filename, content_type, content}, never both. A top-level document_id is still accepted for backward compatibility but is deprecated.
  • There is no endpoint for adding a document to an order after it is placed. For a draft, update the draft with PUT /orders/{id}.

Common Errors

ResponseCauseFix
400 SCHEMA_VALIDATION_ERROR, organization is requiredPayload used organization_id or omitted the organizationSend "organization": {"id": "<uuid>"}
400 SCHEMA_VALIDATION_ERROR, entity_type must be one of: LLC, Limited Liability CompanyLowercase or abbreviated entity type in the payloadUse the exact enum value from the schema
400 SCHEMA_VALIDATION_ERROR, registered_agent_name is requiredA nested registered_agent object was sentSend the flat registered_agent_name and registered_agent_address fields
400 SCHEMA_VALIDATION_ERROR, primary_contact.telephone_number must be a valid phonePlaceholder number such as +1-555-123-4567Use a real, valid number
409 ENTITY_NAME_EXISTSAn entity with this name already exists in SingleFileChoose another name, or file against the existing entity
400 ENTITY_NAME_EXISTSThe name is already registered with the stateChoose another name
403 PERMISSION_DENIED on POST /organizations/createCompany accounts cannot create organizationsUse an existing organization
400 INVALID_ID_FORMATA path id that is not a UUIDUse id from API responses, not order_id or a numeric id

What's Next?


Did this page help you?