> ## Documentation Index
> Fetch the complete documentation index at: https://embed.usesticker.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Organization Setup

> POST /v1/organizations/setup - Provision organizations, users, and shipping locations

## Endpoint

```
POST /v1/organizations/setup
```

**Base URL:** `https://api.usesticker.com`

**Staging/Sandbox URL:** `https://staging.api.usesticker.com`

## Description

Sets up an organization with a user profile and shipping locations in Sticker. This endpoint handles both creating new organizations and adding new users to existing organizations.

Sticker's data model assumes a many-to-one relationship from profiles to organizations:

* An **organization** represents the customer business, company, practice, store, school, or legal entity.
* A **profile** represents an employee or user inside that organization.
* Multiple profiles can belong to the same organization.
* Different businesses must not share the same `internalOrgId`.

**Behavior:**

* If organization with `internalOrgId` **does not exist**: Creates organization, user profile, and shipping locations
* If organization **already exists**: Creates a new user profile linked to the existing organization

<Warning>
  `internalOrgId` is the unique key Sticker uses to identify a business organization for your partner account. Reusing one generic `internalOrgId` across multiple unrelated companies will group those companies into the same Sticker organization.
</Warning>

## Authentication

Requires Partner API key with `organization:write` scope.

```bash theme={null}
Authorization: Bearer sk_live_your_api_key_here
```

## Request Body

```json theme={null}
{
  "internalOrgId": "org-12345",
  "organizationName": "Acme Medical Practice",
  "internalUserId": "user-789",
  "user": {
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@acmemedical.com",
    "phoneNumber": "+1-555-0100"
  },
  "userAddress": {
    "line1": "456 User Street",
    "line2": "Apt 2B",
    "city": "San Francisco",
    "province": "California",
    "postalCode": "94102",
    "country": "United States"
  },
  "shippingLocations": [
    {
      "internalShippingLocationId": "loc-001",
      "name": "Main Office",
      "nickname": "HQ",
      "address": {
        "line1": "123 Medical Plaza",
        "line2": "Suite 200",
        "city": "San Francisco",
        "province": "CA",
        "postalCode": "94102",
        "country": "United States"
      },
      "contact": {
        "name": "Office Manager",
        "phone": "+1-555-0101",
        "email": "office@acmemedical.com"
      },
      "isDefault": true,
      "deliveryInstructions": "Ring bell, leave at front desk"
    }
  ],
  "billingAddress": {
    "line1": "123 Medical Plaza",
    "line2": "Suite 200",
    "city": "San Francisco",
    "province": "California",
    "postalCode": "94102",
    "country": "United States"
  },
  "organizationEmail": "billing@acmemedical.com",
  "organizationPhone": "+1-555-0100"
}
```

## Request Parameters

### Required Fields

<ParamField path="internalOrgId" type="string" required>
  Your unique internal identifier for this business organization. Use the same value only for users who belong to the same business. Use a different value for every distinct customer business.
</ParamField>

<ParamField path="organizationName" type="string" required>
  Display name for the organization.
</ParamField>

<ParamField path="internalUserId" type="string" required>
  Your internal identifier for this user or employee. Each user in the same organization should have a unique `internalUserId`.
</ParamField>

<ParamField path="user" type="object" required>
  User profile information.
</ParamField>

<ParamField path="user.firstName" type="string" required>
  User's first name.
</ParamField>

<ParamField path="user.lastName" type="string" required>
  User's last name.
</ParamField>

<ParamField path="user.email" type="string" required>
  User's email address. Must be a valid email format.
</ParamField>

### Optional Fields

<ParamField path="user.phoneNumber" type="string">
  User's phone number.
</ParamField>

<ParamField path="userAddress" type="object">
  User's personal address (different from organization shipping locations).
</ParamField>

<ParamField path="shippingLocations" type="array">
  Array of shipping locations for the organization. Recommended for new organizations.
</ParamField>

<ParamField path="billingAddress" type="object">
  Organization's billing address.
</ParamField>

<ParamField path="organizationEmail" type="string">
  Organization's primary contact email.
</ParamField>

<ParamField path="organizationPhone" type="string">
  Organization's primary phone number.
</ParamField>

### Address Object

Used for `userAddress`, `billingAddress`, and `shippingLocations[].address`:

<ParamField path="address.line1" type="string" required>
  Street address line 1.
</ParamField>

<ParamField path="address.line2" type="string">
  Street address line 2 (apartment, suite, etc.).
</ParamField>

<ParamField path="address.city" type="string" required>
  City name.
</ParamField>

<ParamField path="address.province" type="string" required>
  State/Province. Accepts full name (e.g., "California") or abbreviation (e.g., "CA").
</ParamField>

<ParamField path="address.postalCode" type="string" required>
  ZIP or postal code.
</ParamField>

<ParamField path="address.country" type="string">
  Country. Defaults to "United States". Currently only US is supported.
</ParamField>

### Shipping Location Object

<ParamField path="shippingLocations[].internalShippingLocationId" type="string" required>
  Your internal identifier for this shipping location.
</ParamField>

<ParamField path="shippingLocations[].name" type="string" required>
  Display name for the location (e.g., "Main Office", "Warehouse").
</ParamField>

<ParamField path="shippingLocations[].nickname" type="string">
  Short nickname for the location.
</ParamField>

<ParamField path="shippingLocations[].address" type="object" required>
  Address object (see above).
</ParamField>

<ParamField path="shippingLocations[].contact" type="object">
  Contact information for delivery.
</ParamField>

<ParamField path="shippingLocations[].contact.name" type="string">
  Contact person's name.
</ParamField>

<ParamField path="shippingLocations[].contact.phone" type="string">
  Contact phone number.
</ParamField>

<ParamField path="shippingLocations[].contact.email" type="string">
  Contact email address.
</ParamField>

<ParamField path="shippingLocations[].isDefault" type="boolean">
  Whether this is the default shipping location. Defaults to `false`.
</ParamField>

<ParamField path="shippingLocations[].deliveryInstructions" type="string">
  Special instructions for delivery drivers.
</ParamField>

## Response

### Success Response (200 OK)

```json theme={null}
{
  "success": true,
  "data": {
    "organization": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Acme Medical Practice",
      "internalOrgId": "org-12345",
      "stripeCustomerId": "cus_xxxxxxxxxxxxx"
    },
    "profile": {
      "id": "660f9500-f30c-52e5-b827-557766551111",
      "partner": "770a0600-g41d-63f6-c938-668877662222",
      "organization": "550e8400-e29b-41d4-a716-446655440000",
      "internal_user_id": "user-789",
      "user": "880b1700-h52e-74g7-d049-779988773333",
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@acmemedical.com",
      "phone_number": "+1-555-0100",
      "created_at": "2024-01-15T10:30:00.000Z"
    },
    "shippingLocations": [
      {
        "id": "990c2800-i63f-85h8-e150-880099884444",
        "name": "Main Office",
        "nickname": "HQ",
        "internalShippingLocationId": "loc-001",
        "address": {
          "line1": "123 Medical Plaza",
          "line2": "Suite 200",
          "city": "San Francisco",
          "province": "California",
          "postalCode": "94102",
          "country": "United States"
        },
        "isDefault": true,
        "isActive": true
      }
    ],
    "isNewOrganization": true
  }
}
```

### Response Fields

<ResponseField name="success" type="boolean">
  Whether the operation succeeded.
</ResponseField>

<ResponseField name="data.organization" type="object">
  Organization details.
</ResponseField>

<ResponseField name="data.organization.id" type="string">
  Sticker's unique identifier for the organization (UUID).
</ResponseField>

<ResponseField name="data.organization.stripeCustomerId" type="string">
  Stripe customer ID for billing.
</ResponseField>

<ResponseField name="data.profile" type="object">
  Created user profile.
</ResponseField>

<ResponseField name="data.profile.id" type="string">
  Sticker's unique identifier for the user profile (UUID).
</ResponseField>

<ResponseField name="data.profile.user" type="string">
  Supabase Auth user ID linked to this profile.
</ResponseField>

<ResponseField name="data.shippingLocations" type="array">
  Array of shipping locations. For existing organizations, returns all existing locations.
</ResponseField>

<ResponseField name="data.isNewOrganization" type="boolean">
  `true` if a new organization was created, `false` if user was added to existing organization.
</ResponseField>

## Error Responses

<AccordionGroup>
  <Accordion title="400 Validation Error">
    ```json theme={null}
    {
      "error": "Validation Error",
      "message": "Invalid request body",
      "code": "VALIDATION_ERROR",
      "details": [
        {
          "code": "invalid_string",
          "message": "Invalid email",
          "path": ["user", "email"]
        }
      ]
    }
    ```

    **Common causes:**

    * Missing required fields
    * Invalid email format
    * Invalid state/province (must be valid US state)
  </Accordion>

  <Accordion title="401 Unauthorized">
    ```json theme={null}
    {
      "error": "Unauthorized",
      "message": "Invalid or missing API key",
      "code": "UNAUTHORIZED"
    }
    ```

    **Solution:** Check your API key is correct and included in the Authorization header.
  </Accordion>

  <Accordion title="409 User Already Exists">
    ```json theme={null}
    {
      "error": "Conflict",
      "message": "User already exists in this organization",
      "code": "USER_ALREADY_EXISTS",
      "data": {
        "profileId": "660f9500-f30c-52e5-b827-557766551111",
        "email": "jane@acmemedical.com"
      }
    }
    ```

    **Solution:** The user with this `internalUserId` already exists in this organization. Use the existing profile ID for handshake requests.
  </Accordion>

  <Accordion title="500 Setup Error">
    ```json theme={null}
    {
      "error": "Internal Server Error",
      "message": "Failed to setup organization",
      "code": "SETUP_ERROR"
    }
    ```

    **Solution:** Retry with exponential backoff. Contact support if the error persists.
  </Accordion>
</AccordionGroup>

## Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  async function setupOrganization(org, user, locations) {
    const response = await fetch('https://api.usesticker.com/v1/organizations/setup', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.STICKER_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        internalOrgId: org.id,
        organizationName: org.name,
        internalUserId: user.id,
        user: {
          firstName: user.firstName,
          lastName: user.lastName,
          email: user.email,
          phoneNumber: user.phone
        },
        shippingLocations: locations.map(loc => ({
          internalShippingLocationId: loc.id,
          name: loc.name,
          address: {
            line1: loc.address.line1,
            line2: loc.address.line2,
            city: loc.address.city,
            province: loc.address.state,
            postalCode: loc.address.zip
          },
          isDefault: loc.isPrimary
        }))
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Setup failed: ${error.message}`);
    }

    return await response.json();
  }
  ```

  ```python Python theme={null}
  import httpx
  import os

  async def setup_organization(org: dict, user: dict, locations: list):
      async with httpx.AsyncClient() as client:
          response = await client.post(
              "https://api.usesticker.com/v1/organizations/setup",
              headers={
                  "Authorization": f"Bearer {os.getenv('STICKER_API_KEY')}",
                  "Content-Type": "application/json",
              },
              json={
                  "internalOrgId": org["id"],
                  "organizationName": org["name"],
                  "internalUserId": user["id"],
                  "user": {
                      "firstName": user["first_name"],
                      "lastName": user["last_name"],
                      "email": user["email"],
                      "phoneNumber": user.get("phone")
                  },
                  "shippingLocations": [
                      {
                          "internalShippingLocationId": loc["id"],
                          "name": loc["name"],
                          "address": {
                              "line1": loc["address"]["line1"],
                              "line2": loc["address"].get("line2"),
                              "city": loc["address"]["city"],
                              "province": loc["address"]["state"],
                              "postalCode": loc["address"]["zip"]
                          },
                          "isDefault": loc.get("is_primary", False)
                      }
                      for loc in locations
                  ]
              }
          )
          
          response.raise_for_status()
          return response.json()
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.usesticker.com/v1/organizations/setup \
    -H "Authorization: Bearer sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "internalOrgId": "org-12345",
      "organizationName": "Acme Medical Practice",
      "internalUserId": "user-789",
      "user": {
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane@acmemedical.com"
      },
      "shippingLocations": [
        {
          "internalShippingLocationId": "loc-001",
          "name": "Main Office",
          "address": {
            "line1": "123 Medical Plaza",
            "city": "San Francisco",
            "province": "CA",
            "postalCode": "94102"
          },
          "isDefault": true
        }
      ]
    }'
  ```
</CodeGroup>

## Notes

<Warning>
  **Organization Identity:** Do not reuse one shared `internalOrgId` for multiple customer businesses. For example, if you have 50 stores, cleaning companies, practices, or schools, you should send 50 distinct `internalOrgId` values. Employees from the same business should share that business's `internalOrgId` and have distinct `internalUserId` values.
</Warning>

<Info>
  **State/Province Normalization:** The API accepts both full state names ("California") and abbreviations ("CA"). All values are normalized and stored as full state names internally.
</Info>

<Warning>
  **Stripe Customer Creation:** When creating a new organization, a Stripe customer is automatically created for billing. The `stripeCustomerId` is returned in the response for reference.
</Warning>

<Tip>
  **Adding Users to Existing Organizations:** Call this endpoint again with the same `internalOrgId` but a different `internalUserId` to add additional users to an existing organization.
</Tip>
