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
- Create or select a SendLit organization from the Organizations page.
- 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
useOrganizationDefaultchoice. - For customer-owned delivery, enable team ESPs. The provisioned team can configure its own provider later using its team key or the dashboard.
- For shared delivery, add and activate an organization mailbox and select
it as the default. Enable automatic grants if requests will omit an
explicit
- 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:
| Scope | Allows |
|---|---|
teams:provision | Provision a team |
teams:read | Read provisioned team state |
teams:manage | Update, suspend, resume, or archive teams |
teams:keys | Replace a provisioned team's integration key |
usage:read | Read 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.fromNameandsender.replyToconfigure the team's organization delivery grant.mailingAddressis the physical address used in managed marketing footers.delivery.useOrganizationDefaultgrants 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.teamEspEnabledcontrols whether the team may add its own ESP.delivery.teamCanChangeDefaultcontrols whether the team may change its delivery default.quota.dailyLimitandquota.monthlyLimitconstrain the team's use of the shared organization mailbox. Usenullfor 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_atEncrypt 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.
| Status | Error | Meaning |
|---|---|---|
400 | validation error | The request body is invalid |
401 | authentication error | The Bearer credential is missing or invalid |
403 | organization_key_required | The caller used a user session or team key |
403 | organization_scope_required | The organization key lacks the route's scope |
404 | team_not_found | The team does not exist inside the key's organization |
409 | provisioning_conflict | The external ID exists with different creation input |
409 | invalid_lifecycle_transition | The requested status transition is not allowed |
429 | too_many_requests | The provisioning rate limit was exceeded |
500 | server_error | SendLit 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.
Transactional email
Send a single email through the REST API.
List contacts GET
Returns a paginated list of contacts. Pass filter as serialized ContactFilterWithAggregator JSON for inline filtering, or segmentId to only return contacts currently matching that saved segment's filter (404 if the segment doesn't exist). SendLit supports fixed generic contact filters over first-class fields, tags, and custom fields; client-specific concepts should be synced into namespaced tags or customFields. q, filter, and segmentId combine with AND. The response's total reflects the combined filters.