> Markdown version of https://authpi.com/docs/use-cases/invite-a-teammate/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Create an organization and invite a teammate

Create a tenant organization in your issuer and send its first member invitation with the AuthPI Admin SDK — the core of B2B multi-tenant onboarding.

## Outcome

A new **organization** (one tenant) in your issuer, with a **pending invitation** sent to its first member. By the end you'll have the organization's `id`, an invitation your teammate can accept, and the call to confirm both exist.

## Prerequisites

- An AuthPI account with an [**Issuer**](/docs/concepts/issuers/) configured — note its `issuer_id`.
- An account [**API key**](/docs/guides/api-keys/) with the scopes `issuers.organizations:write` and `issuers.organizations.invitations:write`.
- The Admin SDK installed:

**TypeScript:**
```bash
npm install @authpi/admin
```

**Python:**
```bash
pip install authpi-admin
```

**cURL:**
```bash
# No install needed — set your key as environment variables.
export AUTHPI_KEY_ID=key_...
export AUTHPI_KEY_SECRET=...
```

## Implementation

Create the organization, then create an invitation scoped to it. The Admin SDK resolves your account id from the API key, so you only supply the `issuer_id`.

**TypeScript:**
```typescript
import { AuthPIAdmin } from "@authpi/admin";

const admin = new AuthPIAdmin({
  apiKey: { id: process.env.AUTHPI_KEY_ID!, secret: process.env.AUTHPI_KEY_SECRET! },
});

const issuer = admin.issuer("iss_your_issuer_id");

// 1. Create the tenant organization
const org = await issuer.organizations.create({
  name: "Acme, Inc.",
});

// 2. Invite its first member
const invitation = await issuer.organization(org.id).invitations.create({
  email_invited: "founder@acme.example",
  scopes: ["member"],
});

console.log(org.id, invitation.status); // -> org_..., "pending"
```

**Python:**
```python
import os
from authpi_admin import AuthPIAdmin

async with AuthPIAdmin(
    api_key=(os.environ["AUTHPI_KEY_ID"], os.environ["AUTHPI_KEY_SECRET"]),
) as admin:
    issuer = admin.issuer("iss_your_issuer_id")

    # 1. Create the tenant organization
    org = await issuer.organizations.create({"name": "Acme, Inc."})

    # 2. Invite its first member
    invitation = await issuer.organization(org.id).invitations.create({
        "email_invited": "founder@acme.example",
        "scopes": ["member"],
    })

    print(org.id, invitation.status)  # -> org_..., "pending"
```

**cURL:**
```bash
# 1. Create the organization
ORG_ID=$(curl -s -X POST \
  "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/organizations" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme, Inc." }' | jq -r '.data.id')

# 2. Invite its first member
curl -X POST \
  "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/organizations/$ORG_ID/invitations" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "email_invited": "founder@acme.example", "scopes": ["member"] }'
```

## Verify

List the organization's invitations and confirm yours is there with status `pending`:

**TypeScript:**
```typescript
const page = await issuer.organization(org.id).invitations.list({ status: "pending" });
console.log(page.data.map((i) => i.email_invited)); // -> ["founder@acme.example"]
```

**Python:**
```python
page = await issuer.organization(org.id).invitations.list(status="pending")
print([i.email_invited for i in page.data])  # -> ["founder@acme.example"]
```

**cURL:**
```bash
curl "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/organizations/$ORG_ID/invitations?status=pending" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET"
```

Success looks like: one entry, your invitee's email, `"status": "pending"`. The invitee also receives an email with a hosted accept link.

## Common failures

- **`403 forbidden` on create** — the API key is missing a scope. Confirm what it has with `GET /v1/me` (the key's `scopes` are listed under `accounts[0]`); you need `issuers.organizations:write` to create the org and `issuers.organizations.invitations:write` to invite.
- **`400 invalid_request` on the invitation** — `A pending invitation already exists for this email`: one pending invitation per email per organization. [Resend](/docs/guides/invitations/) the existing one to nudge the invitee, or revoke it and re-create to change the scopes. A different 400, `Invitations are disabled for this organization`, means the org-level invitation toggle is off.
- **`404 not_found` on the organization** — the `org_id` belongs to a different issuer. Organizations are issuer-scoped: the `issuer_id` in the path must own the `org_id`.

## Production notes

- **Idempotency.** Network retries can double-create. Send an `Idempotency-Key` header — `organizations.create(body, { idempotencyKey })` in TypeScript, `organizations.create(body, idempotency_key=...)` in Python — so a retried create returns the original result instead of a second org. See [idempotency](/docs/reference/idempotency/).
- **React with webhooks, not polling.** Subscribe to the [events](/docs/guides/webhooks/) `organization.invitation.created` and `organization.invitation.accepted` rather than polling the list endpoint.
- **Least privilege.** Give this key only `issuers.organizations:write` and `issuers.organizations.invitations:write` — one minimally-scoped key per service. See [API keys](/docs/guides/api-keys/).
- **Rate limits** apply to these management calls — batch tenant provisioning accordingly. See [rate limits](/docs/reference/rate-limits/).

## Next step

- [Manage the organization lifecycle](/docs/guides/org-lifecycle/) — suspend and delete tenants
- [Validate tokens in your API](/docs/guides/validate-tokens/) — read the active-organization claim once members sign in