> ## 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

> Provisioning organizations and users in Sticker

## Overview

The Organization Setup endpoint is your one-stop shop for provisioning customer data in Sticker. Call it when a customer enables procurement in your platform—it creates the organization, user profile, shipping locations, and even sets up billing with Stripe.

## Data Model: Organizations and Profiles

Sticker models organizations and profiles as a many-to-one relationship:

* **Organization**: the business, company, practice, store, school, or legal entity buying supplies.
* **Profile**: an employee or user who belongs to that business.
* **Relationship**: many profiles can belong to one organization, but each distinct business should have its own organization.

Your backend should map to the same structure when you call Sticker:

```text theme={null}
Acme Cleaning LLC             internalOrgId = acme-cleaning
  - Jane Smith                internalUserId = user-123
  - John Doe                  internalUserId = user-456

Bright Star Janitorial Inc.   internalOrgId = bright-star-janitorial
  - Maria Lopez               internalUserId = user-789
```

<Warning>
  Do not reuse one generic `internalOrgId` for multiple unrelated businesses. Sticker treats `internalOrgId` as the unique identifier for a business organization. If multiple companies share the same `internalOrgId`, their users will be grouped into the same Sticker organization and will share org-level data such as shipping locations, payment methods, catalog, and billing context.
</Warning>

<Info>
  If a business has only one user, it still needs its own unique `internalOrgId`. Additional employees from the same business should reuse that same `internalOrgId` and use their own unique `internalUserId`.
</Info>

## When to Call This Endpoint

<Steps>
  <Step title="User Enables Procurement">
    A customer clicks "Enable Supplies" (or similar) in your platform
  </Step>

  <Step title="Gather Required Data">
    Collect the user's info and at least one shipping location
  </Step>

  <Step title="Call Setup Endpoint">
    Send data to Sticker's `/v1/organizations/setup` endpoint
  </Step>

  <Step title="Store the Profile ID">
    Save the returned `profile.id` for future handshake requests
  </Step>
</Steps>

## What Gets Created

When you call the setup endpoint, Sticker automatically:

1. **Creates a Stripe Customer** - For billing and payment method storage
2. **Creates the Organization** - Linked to your `internalOrgId`
3. **Creates Shipping Locations** - For order delivery
4. **Creates an Auth User** - For secure authentication
5. **Creates a User Profile** - Linked to the org and auth user

## API Endpoint

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

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

## Authentication

Include your Partner API Key in the Authorization header:

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

## Request Format

```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"
  },
  
  "shippingLocations": [
    {
      "internalShippingLocationId": "loc-001",
      "name": "Main Office",
      "nickname": "HQ",
      "address": {
        "line1": "123 Medical Plaza",
        "line2": "Suite 200",
        "city": "San Francisco",
        "province": "CA",
        "postalCode": "94102"
      },
      "contact": {
        "name": "Reception Desk",
        "phone": "+1-555-0101",
        "email": "office@acmemedical.com"
      },
      "isDefault": true,
      "deliveryInstructions": "Ring bell at front desk"
    }
  ],
  
  "billingAddress": {
    "line1": "123 Medical Plaza",
    "city": "San Francisco",
    "province": "California",
    "postalCode": "94102"
  },
  "organizationEmail": "billing@acmemedical.com",
  "organizationPhone": "+1-555-0100"
}
```

## Request Parameters

### Required Fields

| Field              | Type   | Description                                                                                              |
| ------------------ | ------ | -------------------------------------------------------------------------------------------------------- |
| `internalOrgId`    | string | **Your** unique identifier for the business organization. Do not reuse this across unrelated businesses. |
| `organizationName` | string | Display name for the organization                                                                        |
| `internalUserId`   | string | **Your** internal user identifier                                                                        |
| `user.firstName`   | string | User's first name                                                                                        |
| `user.lastName`    | string | User's last name                                                                                         |
| `user.email`       | string | User's email (must be valid format)                                                                      |

### Optional Fields

| Field               | Type   | Description                       |
| ------------------- | ------ | --------------------------------- |
| `user.phoneNumber`  | string | User's phone number               |
| `userAddress`       | object | User's personal address           |
| `shippingLocations` | array  | Organization's shipping locations |
| `billingAddress`    | object | Organization's billing address    |
| `organizationEmail` | string | Organization's contact email      |
| `organizationPhone` | string | Organization's phone number       |

### Address Object

| Field        | Type   | Required | Description                        |
| ------------ | ------ | -------- | ---------------------------------- |
| `line1`      | string | Yes      | Street address                     |
| `line2`      | string | No       | Apt/Suite/Unit                     |
| `city`       | string | Yes      | City                               |
| `province`   | string | Yes      | State (e.g., "CA" or "California") |
| `postalCode` | string | Yes      | ZIP code                           |
| `country`    | string | No       | Defaults to "United States"        |

<Info>
  **State Normalization:** The API accepts both abbreviations ("CA") and full names ("California"). Internally, all values are stored as full state names.
</Info>

### Shipping Location Object

| Field                        | Type    | Required | Description                         |
| ---------------------------- | ------- | -------- | ----------------------------------- |
| `internalShippingLocationId` | string  | Yes      | Your internal location ID           |
| `name`                       | string  | Yes      | Location name (e.g., "Main Office") |
| `nickname`                   | string  | No       | Short nickname                      |
| `address`                    | object  | Yes      | Address object (see above)          |
| `contact.name`               | string  | No       | Contact person name                 |
| `contact.phone`              | string  | No       | Contact phone                       |
| `contact.email`              | string  | No       | Contact email                       |
| `isDefault`                  | boolean | No       | Is this the default location?       |
| `deliveryInstructions`       | string  | No       | Notes for delivery drivers          |

## Response Format

### Success (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-...",
      "organization": "550e8400-...",
      "internal_user_id": "user-789",
      "user": "880b1700-...",
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@acmemedical.com",
      "created_at": "2024-01-15T10:30:00.000Z"
    },
    "shippingLocations": [
      {
        "id": "990c2800-...",
        "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
  }
}
```

### Key Response Fields

| Field                           | Description                                                      |
| ------------------------------- | ---------------------------------------------------------------- |
| `organization.id`               | Sticker's UUID for the org (store this if needed)                |
| `organization.stripeCustomerId` | Stripe customer for billing                                      |
| `profile.id`                    | User profile UUID (use in handshake if needed)                   |
| `profile.internal_user_id`      | Your internal user ID (echoed back)                              |
| `isNewOrganization`             | `true` if org was created, `false` if user was added to existing |

## Adding Users to Existing Organizations

Call the same endpoint with the same `internalOrgId` but a different `internalUserId`:

```json theme={null}
{
  "internalOrgId": "org-12345",
  "organizationName": "Acme Medical Practice",
  
  "internalUserId": "user-456",
  "user": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@acmemedical.com"
  }
}
```

The response will have `isNewOrganization: false`, and the new user will have access to the existing organization's shipping locations and shared payment methods.

## Common Mapping Mistake

Do not model every customer under one shared organization ID:

```json theme={null}
// Incorrect: two unrelated companies share the same internalOrgId
[
  {
    "internalOrgId": "suppliesShop",
    "organizationName": "Acme Cleaning LLC",
    "internalUserId": "acme-user-1"
  },
  {
    "internalOrgId": "suppliesShop",
    "organizationName": "Bright Star Janitorial Inc.",
    "internalUserId": "bright-star-user-1"
  }
]
```

Use a unique organization ID for each business:

```json theme={null}
// Correct: each business has its own internalOrgId
[
  {
    "internalOrgId": "acme-cleaning",
    "organizationName": "Acme Cleaning LLC",
    "internalUserId": "acme-user-1"
  },
  {
    "internalOrgId": "bright-star-janitorial",
    "organizationName": "Bright Star Janitorial Inc.",
    "internalUserId": "bright-star-user-1"
  }
]
```

## Error Handling

<AccordionGroup>
  <Accordion title="400 Validation Error">
    ```json theme={null}
    {
      "error": "Validation Error",
      "message": "Invalid request body",
      "code": "VALIDATION_ERROR",
      "details": [
        {
          "code": "invalid_string",
          "message": "State must be a valid US state",
          "path": ["shippingLocations", 0, "address", "province"]
        }
      ]
    }
    ```

    **Common causes:**

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

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

    **Solution:** Check `Authorization: Bearer {key}` 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-...",
        "email": "jane@acmemedical.com"
      }
    }
    ```

    **Solution:** User is already set up. Use the returned `profileId` for handshake.
  </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.street,
            city: loc.city,
            province: loc.state,
            postalCode: loc.zip
          },
          isDefault: loc.isPrimary
        }))
      })
    });

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

    const result = await response.json();
    
    // Store the profile ID in your database
    await db.users.update(user.id, {
      stickerProfileId: result.data.profile.id
    });
    
    return result;
  }
  ```

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

  async def setup_organization(org: dict, user: dict, locations: list) -> dict:
      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["street"],
                              "city": loc["city"],
                              "province": loc["state"],
                              "postalCode": loc["zip"]
                          },
                          "isDefault": loc.get("is_primary", False)
                      }
                      for loc in locations
                  ]
              }
          )
          
          response.raise_for_status()
          result = response.json()
          
          # Store profile ID in your database
          await db.users.update(user["id"], {
              "sticker_profile_id": result["data"]["profile"]["id"]
          })
          
          return result
  ```

  ```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>

## Best Practices

<AccordionGroup>
  <Accordion title="Use Your Internal IDs" icon="key">
    Always use your own internal IDs for `internalOrgId`, `internalUserId`, and `internalShippingLocationId`. `internalOrgId` must uniquely identify the customer business, while `internalUserId` must uniquely identify a user or employee.
  </Accordion>

  <Accordion title="One Business, One Organization ID" icon="building">
    Use the same `internalOrgId` only for users who belong to the same business. Use a different `internalOrgId` for every distinct customer business, even if that business currently has only one user.
  </Accordion>

  <Accordion title="Include Shipping Locations" icon="truck">
    Always include at least one shipping location when creating a new organization. This ensures users can complete checkout immediately.
  </Accordion>

  <Accordion title="Handle the 409 Response" icon="check">
    If you get a 409 (User Already Exists), don't treat it as an error—the user is already set up! Just use the returned profile ID.
  </Accordion>

  <Accordion title="Store the Profile ID" icon="database">
    Store the returned `profile.id` in your database. While you can use `internal_user_id` for handshake, having the profile ID is useful for debugging.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="User Handshake" icon="handshake" href="/integration/user-handshake">
    Authenticate users for each session
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/organization-setup">
    Complete API specification
  </Card>
</CardGroup>
