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

# Partner API overview

> Provision and meter isolated SpitShake tenant accounts from a platform partner.

The Partner API is the reseller control plane for platforms that provision separate SpitShake
tenant accounts for their customer firms. Partner credentials are issued by the SpitShake
operator; there is no public or self-service partner signup.

## Base URL and versioning

All Partner API endpoints are pinned to version 1:

```text theme={null}
https://spitshake.io/api/partner/v1
```

Breaking partner-contract changes will use a new versioned namespace. The ordinary tenant API's
`/api` and `/api/v1` aliases do not apply to this namespace.

## Authentication

Send the operator-issued `spk_` key as a Bearer credential:

```bash theme={null}
curl https://spitshake.io/api/partner/v1/accounts \
  -H "Authorization: Bearer $SPITSHAKE_PARTNER_KEY"
```

Partner keys work only under `/api/partner/v1`. Tenant API tokens, OAuth tokens, and browser
sessions are rejected here, and partner keys are rejected by tenant APIs. Requests are limited
to 100 per minute per partner key.

| Status | Meaning                                                              |
| ------ | -------------------------------------------------------------------- |
| `401`  | The Bearer key is missing or unknown.                                |
| `403`  | The partner is suspended, or requested a plan outside its allowlist. |
| `429`  | The partner exceeded 100 requests in the current minute.             |

## Tenant ownership model

Every provisioned firm is a normal, isolated SpitShake account with its own users, templates,
submissions, API tokens, and audit trail. The partner owns the provisioning and billing
relationship, so partner-managed firms cannot start a direct Stripe subscription.

A firm bringing an existing SpitShake account remains a standalone account. Stage B deliberately
does not provide an account-linking or account-claiming endpoint.

## Idempotent POST requests

Both provisioning and token-minting accept an `Idempotency-Key` header. Within an endpoint, retry
the exact request with the exact key:

* Same key and body after success replays the original status and response.
* Replays include the original one-time `api_token` and optional identity secret. This is the
  supported recovery path when the client times out after a successful request.
* Same key with a different body returns `422`.
* A duplicate while the first transaction is in flight returns `409`.
* Failed requests retain no claim, so the same request can be retried after correction.

Keys and their encrypted replay responses are retained for 24 hours.

## Suspension and offboarding

Suspension blocks the partner API immediately but does not revoke tenant API tokens belonging to
managed firms. Reactivation restores partner access. Key rotation invalidates the old partner key.

Offboarding is an operator action. It transactionally moves every managed firm to the free plan,
clears partner ownership and external IDs, writes an account audit event, removes replay records,
and then removes the partner. Firms continue as standalone accounts.

Operator lifecycle commands are:

```bash theme={null}
bundle exec rake 'partners:create[Partner Name]'
bundle exec rake 'partners:rotate_key[Partner Name]'
bundle exec rake 'partners:suspend[Partner Name]'
bundle exec rake 'partners:reactivate[Partner Name]'
bundle exec rake 'partners:offboard[Partner Name]'
```

The create and rotate commands print a raw key once. Store it immediately.

<Note>
  For production verification, run the task inside the service environment without dumping secrets:
  `railway run bash -c 'DATABASE_URL=$DATABASE_PUBLIC_URL bundle exec rake partners:create[...]'`.
  Replace `...` with the partner name before running it.
  If that runner is unavailable, read only the individual named variables required for the check;
  never export or print the full environment. Payload checks should print booleans, not payload data.
</Note>
