Create a Registered Agent Location

Learn how to add a new registered agent Location to your existing Organization so a company can receive service of process and state correspondence in a given jurisdiction.

📘

This guide covers registered agent locations only. To create a CMRA location (a physical mailbox for general mail reception), see Create and Onboard a New CMRA Location instead.

Overview

A registered agent location is the address at which Stable, acting as registered agent, accepts service of process and official state mail on behalf of one of your companies in a single US jurisdiction. Unlike a CMRA, a registered agent location:

  • Is tied to an existing Company (you must create the company first)
  • Does not require USPS Form 1583, document uploads, or a signature packet
  • Is requested with a 2-letter US state code (for example WY or DE) rather than an IATA-style facility code
  • May briefly return with status: "pending" while we finish provisioning the address with our partners

You'll learn how to:

  • Create a Company (if you don't have one yet)
  • Create a registered agent Location for that company in a given jurisdiction
  • Provide jurisdiction-specific extra information (e.g. for Wyoming)
  • Handle a pending response and detect when the location becomes active

Prerequisites

Before you begin, you'll need:

  • A Stable API key
  • A registered agent jurisdiction code (a 2-letter US state or territory code, e.g. WY, DE, CA). See Location codes for the full list
  • The id of an existing Company. If you don't have one yet, you'll create it as Step 1 below

Authentication

All API requests require authentication using your API key in the x-api-key header:

curl -H "x-api-key: your-api-key" https://api.usestable.com/v1/...
const fetch = require('node-fetch');

const url = 'https://api.usestable.com/v1/locations';
const options = {
  headers: {
    'x-api-key': 'your-api-key',
    'Content-Type': 'application/json',
  },
  // ...
};

fetch(url, options)
  .then(response => console.log('Response:', response.json()))
  .catch(error => console.error('Error:', error));
import requests

url = 'https://api.usestable.com/v1/...'
headers = {
    'x-api-key': 'your-api-key',
    'Content-Type': 'application/json',
}

response = requests.get(url, headers=headers)

if response.ok:
    print(response.json())
else:
    print(f"Error: {response.status_code}, {response.text}")
🔒

API Key Security

Keep your API key secure and never expose it in client-side code. All API requests should be made from your backend services.

Process Overview

The registered agent Location creation flow follows these steps:

  1. Create a Company (or reuse an existing one)
  2. Create the registered agent Location in a given jurisdiction
  3. Handle the response, including the possible pending status

There is no document upload step and no signature packet. Most registered agent locations are returned in active status on the first request.

Step 1: Create a Company

A registered agent location must be attached to a Company. If you've already created the company, skip to Step 2 and use its id.

curl -X POST https://api.usestable.com/v1/companies \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "legalName": "Ada'\''s Apples LLC",
    "entityType": "limited-liability-company",
    "jurisdictions": ["WY"],
    "formationJurisdiction": "WY",
    "formationDate": "2024-01-15"
  }'
const fetch = require('node-fetch');

const url = 'https://api.usestable.com/v1/companies';

const body = {
  legalName: "Ada's Apples LLC",
  entityType: 'limited-liability-company',
  jurisdictions: ['WY'],
  formationJurisdiction: 'WY',
  formationDate: '2024-01-15',
};

const options = {
  method: 'POST',
  headers: {
    'x-api-key': 'your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
};

fetch(url, options)
  .then(response => response.json())
  .then(data => console.log('Response:', data))
  .catch(error => console.error('Error:', error));
import requests

url = 'https://api.usestable.com/v1/companies'

body = {
    "legalName": "Ada's Apples LLC",
    "entityType": "limited-liability-company",
    "jurisdictions": ["WY"],
    "formationJurisdiction": "WY",
    "formationDate": "2024-01-15",
}

headers = {
    "x-api-key": "your-api-key",
    "Content-Type": "application/json",
}

response = requests.post(url, headers=headers, json=body)

if response.ok:
    print("Response:", response.json())
else:
    print(f"Error: {response.status_code}, {response.text}")

Save the id returned in the response. You'll pass it as companyId in Step 2.

Step 2: Create the Registered Agent Location

Send a POST /v1/locations with the jurisdiction's 2-letter state code as locationCode and the company's id as companyId.

curl -X POST https://api.usestable.com/v1/locations \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "locationCode": "DE",
    "companyId": "550e8400-e29b-41d4-a716-446655440000"
  }'
const fetch = require('node-fetch');

const url = 'https://api.usestable.com/v1/locations';

const body = {
  locationCode: 'DE',
  companyId: '550e8400-e29b-41d4-a716-446655440000',
};

const options = {
  method: 'POST',
  headers: {
    'x-api-key': 'your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
};

fetch(url, options)
  .then(response => response.json())
  .then(data => console.log('Response:', data))
  .catch(error => console.error('Error:', error));
import requests

url = 'https://api.usestable.com/v1/locations'

body = {
    "locationCode": "DE",
    "companyId": "550e8400-e29b-41d4-a716-446655440000",
}

headers = {
    "x-api-key": "your-api-key",
    "Content-Type": "application/json",
}

response = requests.post(url, headers=headers, json=body)

if response.ok:
    print("Response:", response.json())
else:
    print(f"Error: {response.status_code}, {response.text}")

Field Reference

  • locationCode (required): 2-letter US state or territory code such as DE, WY, CA. See Location codes for the full list of registered agent jurisdictions. Do not pass an IATA-style CMRA code (e.g. SFO1) here, it will be routed through the CMRA flow.
  • companyId (required): UUID of an existing Company. The location will be created as that company's registered agent in the chosen jurisdiction.
  • autoScan (optional): For registered agent locations, only true is permitted. The field defaults to true, so you can omit it.
  • additionalInformation (required for some jurisdictions, e.g. Wyoming): Jurisdiction-specific extra data. See Jurisdiction-Specific Requirements below.
  • metadata (optional): Arbitrary key/value pairs to attach to the location. Each value must be a string or number.
📘

Fields that are not allowed

platformPrefill is for CMRA onboarding only and will be rejected for registered agent locations.

Response (active)

In most cases, the request returns with status: "active" once provisioning completes. The address fields will be populated with the registered agent address assigned to your company in that jurisdiction:

{
  "id": "70a3d702-bf1d-4153-8eb2-d3d889aff7f0",
  "status": "active",
  "address": {
    "line1": "<assigned registered agent street>",
    "line2": "",
    "city": "<city>",
    "state": "DE",
    "postalCode": "<postal code>"
  },
  "type": "registeredAgent",
  "onboarding": {
    "status": "complete"
  },
  "metadata": null
}

Response (pending)

If provisioning takes longer than the request window allows, the response will return with status: "pending". The address is still populated in this case, with the same fields as the active response:

{
  "id": "70a3d702-bf1d-4153-8eb2-d3d889aff7f0",
  "status": "pending",
  "address": {
    "line1": "<assigned registered agent street>",
    "line2": "",
    "city": "<city>",
    "state": "DE",
    "postalCode": "<postal code>"
  },
  "type": "registeredAgent",
  "onboarding": {
    "status": "complete"
  },
  "metadata": null
}

A pending response means the location has been accepted and is being provisioned with our partners. Once provisioning completes, the location will transition to active. Keep the id from the response and poll the GET endpoint to detect the transition.

Step 3: Handling a Pending Location

When a location is returned as pending, do not retry the create request. The first create call has already succeeded and provisioning is underway; a second create call for the same company and jurisdiction will be rejected with an error (see Registered agent already exists below). Instead, save the id from the create response and poll for status updates.

Polling for Activation

Call GET /v1/locations/{id} on an interval until status becomes active. We recommend exponential backoff starting at around 1 second and capping at around 30 seconds between polls.

curl https://api.usestable.com/v1/locations/70a3d702-bf1d-4153-8eb2-d3d889aff7f0 \
  -H "x-api-key: your-api-key"
async function waitForActive(locationId, apiKey, { maxAttempts = 20 } = {}) {
  let delay = 1000;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fetch(
      `https://api.usestable.com/v1/locations/${locationId}`,
      { headers: { 'x-api-key': apiKey } },
    );
    const location = await res.json();
    if (location.status === 'active') {
      return location;
    }
    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 2, 30000);
  }
  throw new Error('Location did not become active in time');
}
import time
import requests

def wait_for_active(location_id, api_key, max_attempts=20):
    delay = 1
    for _ in range(max_attempts):
        res = requests.get(
            f"https://api.usestable.com/v1/locations/{location_id}",
            headers={"x-api-key": api_key},
        )
        location = res.json()
        if location["status"] == "active":
            return location
        time.sleep(delay)
        delay = min(delay * 2, 30)
    raise TimeoutError("Location did not become active in time")

Response Once Active

{
  "id": "70a3d702-bf1d-4153-8eb2-d3d889aff7f0",
  "status": "active",
  "address": {
    "line1": "<assigned registered agent street>",
    "line2": "",
    "city": "<city>",
    "state": "DE",
    "postalCode": "<postal code>"
  },
  "type": "registeredAgent",
  "onboarding": {
    "status": "complete"
  },
  "metadata": null
}
📘

Do not retry the create request

A pending status is not a failure. The create call succeeded and provisioning is underway. Retrying the POST /v1/locations call for the same company and jurisdiction will be rejected with a 409 Conflict (Registered agent already exists). Always poll the existing id instead.

Jurisdiction-Specific Requirements

Some jurisdictions require additional information on the company before a registered agent location can be activated. Pass these through additionalInformation on the create request.

Wyoming (WY)

Wyoming requires additionalInformation to be provided on registered agent creation. Pass the company's management structure and the officials responsible for the entity:

{
  "locationCode": "WY",
  "companyId": "550e8400-e29b-41d4-a716-446655440000",
  "additionalInformation": {
    "managementType": "Member Managed",
    "officialMember": {
      "firstName": "Ada",
      "lastName": "Lovelace"
    }
  }
}

Supported fields on additionalInformation:

  • managementType (string, e.g. "Member Managed" or "Manager Managed")
  • official, officialDirector, officialManager, officialMember, officialPartner, officialPresident, officialSecretary, officialTreasurer: each takes { firstName, lastName }

Provide the official fields that match the company's entity type and management structure.

Other Jurisdictions

Most jurisdictions do not require additionalInformation. Pass only locationCode and companyId. If we add new jurisdiction-specific requirements, this section will be updated.

Error Handling

Registered agent already exists

{
  "status": 409,
  "message": "A registered agent already exists for Ada's Apples LLC in DE."
}

You'll get this when you call POST /v1/locations for a company and jurisdiction that already has a registered agent location, including the case where the first call returned status: "pending" and is still provisioning. Don't retry the create call; instead, look up the existing location by ID (or by listing the company's locations) and poll its status until it becomes active.

Missing companyId

{
  "status": 400,
  "message": "companyId is required for registered agent locations"
}

Solution: Include companyId in your request body.

platformPrefill Not Allowed

{
  "status": 400,
  "message": "platformPrefill is not allowed for registered agent locations"
}

Solution: Remove platformPrefill from your request body. It is only used for CMRA locations.

autoScan: false Not Allowed

{
  "status": 400,
  "message": "autoScan cannot be false for registered agent locations"
}

Solution: Omit autoScan or set it to true. Registered agent mail is always scanned.

Missing additionalInformation for Wyoming

{
  "status": 400,
  "message": "additionalInformation is required for WY"
}

Solution: Include the required additionalInformation object on the create request. See Wyoming above.

Invalid Location Code

{
  "status": 400,
  "message": "Validation error(s) when parsing body: 'locationCode must be a CMRA code (...) or a registered agent jurisdiction code (AK, AL, ...). See https://docs.usestable.com/docs/location-codes for more information.'"
}

Solution: Use one of the supported 2-letter jurisdiction codes from the Location codes page.

Best Practices

  • Always store the id from a pending response before polling. Treat the create call as a single-use action: a second create for the same company and jurisdiction returns 409 Conflict.
  • Use exponential backoff when polling for active status. Most pending locations activate in well under a minute.
  • Validate Wyoming inputs on your side before submitting, so you can surface clear errors to your users about which officials are needed.
  • Surface a clear loading state to your end user while a registered agent location is pending, with messaging that the address is being provisioned.