SendLit logoSendLit Docs
Developers

Provisioning teams

Create and manage organization-scoped SendLit teams for external tenants.

Provisioning lets a trusted multi-tenant platform create one isolated SendLit team for each of its own tenants. For example, a website platform can provision one SendLit team for every customer workspace and use that team for contacts, newsletters, sequences, and transactional email.

Provisioning is a backend-to-backend workflow. If you only need another team for people already using the SendLit dashboard, create it from the dashboard instead.

How provisioning is scoped

Every SendLit team belongs to exactly one organization. The organization API key used for the request determines that organization; an organization ID in a request body or header cannot override it.

Provisioning creates the team, its delivery settings, and an initial team-scoped API key. It does not:

  • create an organization;
  • create a SendLit user account;
  • create organization or team membership; or
  • interpret an owner email as authorization.

An organization key manages provisioning. The returned team key accesses that one team's contacts and email APIs. Keep these credentials separate.

Prerequisites

  1. Create or select a SendLit organization from the Organizations page.
  2. Decide how new teams will deliver email:
    • For shared delivery, add and activate an organization mailbox and select it as the default. Enable automatic grants if requests will omit an explicit useOrganizationDefault choice.
    • For customer-owned delivery, enable team ESPs. The provisioned team can configure its own provider later using its team key or the dashboard.
  3. Create an organization API key and store its plaintext value in your backend secret manager. It is displayed only once.

Use the minimum scopes your integration needs:

ScopeAllows
teams:provisionProvision a team
teams:readRead provisioned team state
teams:manageUpdate, suspend, resume, or archive teams
teams:keysReplace a provisioned team's integration key
usage:readRead team quota usage

The examples below use:

SENDLIT_API_URL=https://api.sendlit.app
SENDLIT_ORGANIZATION_API_KEY=sl_org_live_...

Never put the organization key in browser code or a mobile application.

Provision your first team

Use an immutable identifier from your own database as externalId. A tenant, workspace, or account ID is appropriate. Do not use an email address, mutable slug, or display name.

curl --request POST "$SENDLIT_API_URL/provisioning/teams" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "externalId": "workspace_01JABCDEF",
    "name": "Acme",
    "sender": {
      "fromName": "Acme",
      "replyTo": "hello@acme.example"
    },
    "mailingAddress": "123 Main Street, Bengaluru, Karnataka 560001, India",
    "delivery": {
      "useOrganizationDefault": true,
      "teamEspEnabled": false,
      "teamCanChangeDefault": false
    },
    "quota": {
      "dailyLimit": 500,
      "monthlyLimit": 10000
    }
  }'

externalId and name are required. The other fields are optional:

  • sender.fromName and sender.replyTo configure the team's organization delivery grant.
  • mailingAddress is the physical address used in managed marketing footers.
  • delivery.useOrganizationDefault grants the active default organization mailbox and selects it for delivery. This requires an active default mailbox. If this property is omitted, the organization's automatic-grant policy supplies the default.
  • delivery.teamEspEnabled controls whether the team may add its own ESP.
  • delivery.teamCanChangeDefault controls whether the team may change its delivery default.
  • quota.dailyLimit and quota.monthlyLimit constrain the team's use of the shared organization mailbox. Use null for no per-team limit.

For a team that will connect its own ESP, omit sender and quota, set useOrganizationDefault to false, and set teamEspEnabled to true.

Creation response

The first successful request returns created: true and the only plaintext copy of the initial team API key:

{
    "teamId": "team_01JXYZ",
    "externalId": "workspace_01JABCDEF",
    "name": "Acme",
    "deliverySource": {
        "type": "organization"
    },
    "created": true,
    "apiKey": "sl_live_..."
}

Before acknowledging the operation in your own application, persist:

external_tenant_id
sendlit_team_id
encrypted_sendlit_team_api_key
sendlit_provisioned_at

Encrypt the team key at rest. SendLit stores only its hash and cannot return the plaintext later.

Use the team key

Use the returned sl_live_... key in the x-sendlit-apikey header for team-scoped APIs. Do not continue using the organization key for contacts or email content.

curl "$SENDLIT_API_URL/contacts" \
  --header "x-sendlit-apikey: $SENDLIT_TEAM_API_KEY"

The team key resolves exactly one team, so a request cannot use it to read another tenant's data.

Retry provisioning safely

Provisioning uses (organization, externalId) as its idempotency identity; it does not use an Idempotency-Key header. Retry a failed or timed-out request with the same externalId and the same creation payload.

An identical replay returns the existing team without revealing its key:

{
    "teamId": "team_01JXYZ",
    "externalId": "workspace_01JABCDEF",
    "name": "Acme",
    "deliverySource": {
        "type": "organization"
    },
    "created": false,
    "apiKey": null
}

If the same externalId is replayed with different creation input, SendLit returns:

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": "provisioning_conflict"
}

This protects retries from silently changing a tenant. Use PATCH /provisioning/teams/:teamId for intentional changes.

Retry network failures, 429, and 5xx responses with exponential backoff and jitter. Do not automatically retry validation, authorization, or conflict responses. The provisioning API is currently limited to 30 requests per minute per source IP.

If SendLit created the team but your platform failed before storing the plaintext key, the retry correctly returns apiKey: null. Replace the lost key using the organization credential as described below.

Read and update a provisioned team

Read the current provisioned-team view with a teams:read organization key:

curl "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY"

Update mutable settings with teams:manage:

curl --request PATCH \
  "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Acme Cloud",
    "sender": {
      "fromName": "Acme Cloud",
      "replyTo": "support@acme.example"
    },
    "quota": {
      "monthlyLimit": 25000
    }
  }'

The patch endpoint can change the name, sender, mailing address, team-delivery controls, and shared-delivery quotas. It cannot change externalId, move the team to another organization, or update an archived team. Sender and quota updates require an organization delivery grant.

Replace a lost or exposed team key

Use an organization key with teams:keys:

curl --request POST \
  "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ/keys" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "name": "FrontLit production" }'

The 201 response contains the new plaintext secret in key:

{
    "keyId": "key_01JNEW",
    "keyPrefix": "sl_live_a1b2",
    "name": "FrontLit production",
    "expiresAt": null,
    "lastUsedAt": null,
    "revokedAt": null,
    "createdAt": "2026-08-12T10:00:00.000Z",
    "key": "sl_live_..."
}

This operation revokes the team's active keys previously created through organization-key provisioning. Store the replacement immediately and update your workers together; requests using a revoked key will fail.

Suspend, resume, and archive

Suspend new sends while retaining the team and its data:

curl --request POST \
  "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ/suspend" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY"

Only an active team can be suspended. Resume a suspended team with:

curl --request POST \
  "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ/resume" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY"

Invalid transitions return 409 invalid_lifecycle_transition.

Archive a tenant with:

curl --request DELETE \
  "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY"

Archival is a soft deprovisioning operation and returns 204 No Content. The provisioning API does not provide an unarchive operation, so do not archive a team as a temporary sending pause.

Read shared-delivery usage

With usage:read, retrieve the team's daily and monthly organization-delivery quota windows:

curl "$SENDLIT_API_URL/provisioning/teams/team_01JXYZ/usage" \
  --header "Authorization: Bearer $SENDLIT_ORGANIZATION_API_KEY"
{
    "day": {
        "limit": 500,
        "accepted": 120,
        "reserved": 8,
        "remaining": 372,
        "resetsAt": "2026-08-13T00:00:00.000Z"
    },
    "month": {
        "limit": 10000,
        "accepted": 2740,
        "reserved": 8,
        "remaining": 7252,
        "resetsAt": "2026-09-01T00:00:00.000Z"
    }
}

accepted has already consumed quota. reserved represents work accepted by SendLit but not yet finalized. This endpoint requires an active organization delivery grant for the team.

Errors

Error responses contain a stable error value. Branch on that value rather than a human-readable message.

StatusErrorMeaning
400validation errorThe request body is invalid
401authentication errorThe Bearer credential is missing or invalid
403organization_key_requiredThe caller used a user session or team key
403organization_scope_requiredThe organization key lacks the route's scope
404team_not_foundThe team does not exist inside the key's organization
409provisioning_conflictThe external ID exists with different creation input
409invalid_lifecycle_transitionThe requested status transition is not allowed
429too_many_requestsThe provisioning rate limit was exceeded
500server_errorSendLit could not complete provisioning

Treat a 404 as organization-scoped: a valid team belonging to another organization is intentionally not exposed through the caller's key.

Human access is separate

Provisioning creates an API-accessible team, not a human SendLit account. Signing up for SendLit later with an email used by the embedding platform does not automatically grant access to the provisioned team. Human access requires explicit team membership. Provisioning does not currently expose a public human invitation or account-claim flow. Organization membership must not be used as a shortcut because it can reveal other teams in the platform's organization.

See Authentication for credential types, API keys for team-key handling, and the API reference for the exhaustive request and response schemas.

On this page