Pular para o conteúdo principal

Assinar um tenant no plano Pro

Você pode colocar um tenant no plano Pro com uma chamada à Logto Cloud API, sem abrir o Console. Junto com a criação de tenant, isso permite que sua automação crie um tenant de produção e o assine em duas chamadas.

A chamada cobra a primeira fatura no cartão salvo em sua conta de cobrança. Ninguém está presente para confirmar o pagamento, então a chamada ou é bem-sucedida completamente ou não deixa nada para trás: um pagamento falho nunca cria uma assinatura.

Antes de começar​

Você precisa de:

  • Um Logto Cloud Personal Access Token (PAT) com acesso a esta API. Entre em contato conosco para obter um.
  • O papel Admin no tenant. O usuário que cria um tenant é seu Admin.
  • Uma conta de cobrança com um cartão salvo. Sua conta Logto Cloud recebe uma na primeira vez que você assina qualquer tenant no plano Pro em Console > Configurações > Plano e Cobrança. A API cobra o cartão da sua conta de cobrança padrão, a menos que o tenant já tenha estado em um plano pago antes ou você escolha outra.
  • Um tenant de produção no plano Free. Tenants de desenvolvimento e tenants cobertos por contrato corporativo não são suportados.
VariávelDescrição
CLOUD_API_ENDPOINTO endpoint da Logto Cloud API. Para Logto Cloud, use https://cloud.logto.io.
LOGTO_CLOUD_PATUm PAT para sua conta Logto Cloud.
TENANT_IDO ID do tenant a ser assinado.

Assinar o tenant​

Chame POST /api/tenants/{tenantId}/subscription com um cabeçalho Idempotency-Key:

export IDEMPOTENCY_KEY="$(uuidgen)"

curl -X POST "$CLOUD_API_ENDPOINT/api/tenants/$TENANT_ID/subscription" \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{ "skuId": "pro-202509" }'
  1. Idempotency-Key: OBRIGATÓRIO. Uma string única de 1 a 255 caracteres que identifica esta tentativa. Gere uma por tentativa, armazene-a e envie o mesmo valor ao tentar novamente.
  2. skuId: OBRIGATÓRIO. O plano a ser assinado. Use pro-202509 para o plano Pro.
  3. customerId: OPCIONAL. A conta de cobrança a ser cobrada, quando você tem mais de uma. Quando omitido, um tenant que já esteve em um plano pago antes é cobrado em sua conta de cobrança anterior; qualquer outro tenant é cobrado em sua conta de cobrança padrão. Veja Escolher a conta de cobrança.

O status da resposta informa o que aconteceu:

  • 201: a assinatura foi criada e o tenant está no plano Pro.
  • 200: esta chave já criou a assinatura. Nada foi cobrado novamente.

Exemplo de resposta:

{
"subscription": {
"id": "sub_1Qabc...",
"planId": "pro-202509",
"status": "active",
"currentPeriodStart": "2026-09-20T06:00:00.000Z",
"currentPeriodEnd": "2026-10-20T06:00:00.000Z",
"isEnterprisePlan": false,
"isDevPlan": false,
"quotaScope": "dedicated"
},
"tenant": {
"id": "abc123",
"tag": "production",
"planId": "pro-202509"
}
}

Escolher a conta de cobrança​

Sua conta Logto Cloud pode ter mais de uma conta de cobrança, cada uma com seu próprio cartão e endereço de cobrança; por exemplo, uma para cada empresa que você paga. Uma delas é a padrão, e a API a cobra, a menos que você nomeie outra.

Liste suas contas de cobrança com GET /api/me/stripe-customers:

curl "$CLOUD_API_ENDPOINT/api/me/stripe-customers" \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT"
[
{
"customerId": "cus_Rabc...",
"isDefault": true,
"name": "Acme Inc.",
"email": "billing@acme.example",
"createdAt": "2026-03-02T09:12:45.000Z"
},
{
"customerId": "cus_Rdef...",
"isDefault": false,
"name": null,
"email": "finance@other.example",
"createdAt": "2026-08-14T15:30:00.000Z"
}
]

name e email são os salvos na conta de cobrança e podem ser null. Contas de cobrança que não existem mais são omitidas.

Para cobrar uma conta de cobrança específica para uma assinatura, envie seu customerId no corpo:

curl -X POST "$CLOUD_API_ENDPOINT/api/tenants/$TENANT_ID/subscription" \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{ "skuId": "pro-202509", "customerId": "cus_Rdef..." }'

Para alterar qual conta de cobrança é cobrada por padrão, tanto para a API quanto para o Console (isso não move um tenant que já esteve em um plano pago antes, veja a observação abaixo):

curl -X PATCH "$CLOUD_API_ENDPOINT/api/me/stripe-customers/cus_Rdef..." \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT" \
-H "Content-Type: application/json" \
-d '{ "isDefault": true }'

A chamada responde 204 em caso de sucesso, 404 quando a conta de cobrança não é sua, e 422 customer_unavailable quando ela não existe mais.

nota:

Um tenant que já esteve em um plano pago antes, mesmo que essa assinatura tenha sido cancelada desde então, permanece na conta de cobrança que pagou por ele. Tal tenant é sempre cobrado nessa conta de cobrança, independentemente de qual seja sua padrão. Deixe customerId de fora: nomear uma conta de cobrança responde 422 customer_fixed. Entre em contato conosco para mover um tenant para outra conta de cobrança.

Uma nova tentativa com o mesmo Idempotency-Key deve nomear a mesma conta de cobrança da primeira solicitação, ou nenhuma; outra responde 400 idempotency_key_mismatch.

Repetir com segurança​

Um timeout de rede não informa se o cartão foi cobrado. O Idempotency-Key é o que torna uma repetição segura: o Logto realiza a cobrança no máximo uma vez por chave, então repetir com a mesma chave é sempre seguro.

  1. Quando o resultado for desconhecido, repita com a mesma chave. Isso cobre um timeout do cliente ou conexão perdida, uma resposta 5xx e 409 attempt_in_progress (uma solicitação anterior com esta chave pode ainda estar em execução, então aguarde alguns segundos primeiro).
  2. A repetição finaliza a tentativa. Ela responde 201 ou 200 assim que a assinatura existir, ou o erro que encerrou a tentativa.
  3. Inicie uma nova tentativa com uma nova chave apenas quando a tentativa terminar em erro. Uma repetição com a mesma chave então responde com uma mensagem de que a tentativa já falhou. Corrija a causa primeiro, por exemplo, atualizando o cartão.
  4. Pare e entre em contato conosco quando a mensagem pedir. Inclua error.requestId quando a resposta tiver um.
nota:

Enquanto uma tentativa estiver aberta, o tenant fica reservado para ela: uma solicitação com uma chave diferente recebe 409 attempt_in_progress até que a tentativa aberta termine. Repita a tentativa aberta com sua própria chave em vez de iniciar uma nova. As chaves são vinculadas à sua conta.

Repita dentro de 23 horas. Depois disso, o Logto não pode mais garantir uma única cobrança e mantém a tentativa: a repetição com a mesma chave responde 409 attempt_in_progress com uma mensagem para entrar em contato com o suporte, e nós resolvemos manualmente.

Erros​

Os erros usam o status HTTP e um corpo JSON com uma message e, para a maioria dos erros, um error.code:

{
"message": "O cartão foi recusado.",
"error": { "code": "card_declined", "declineCode": "insufficient_funds" }
}
Statuserror.codeSignificado e o que fazer
400(nenhum)O cabeçalho Idempotency-Key está ausente ou tem mais de 255 caracteres, ou o corpo não tem skuId.
400invalid_skuO skuId não pode ser comprado pela API.
400idempotency_key_mismatchA chave já foi usada para outro tenant, plano ou conta de cobrança. Use uma nova chave, a menos que a mensagem peça para contatar o suporte.
402card_declined, expired_card, incorrect_cvc, incorrect_numberO cartão não pôde ser cobrado. declineCode é incluído quando o emissor do cartão compartilha. Atualize o cartão no Console e tente novamente.
402processing_errorO cartão não pôde ser processado desta vez. Inicie uma nova tentativa em breve.
402authentication_requiredO emissor do cartão exige que o titular confirme o pagamento, o que uma chamada de API não pode fazer. Assine este tenant no Console.
403insufficient_roleVocê é membro do tenant, mas não é Admin.
403(nenhum)O token não tem acesso a esta API, ou o tenant está coberto por contrato corporativo ou está em uma região privada.
404(nenhum)O tenant não existe, ou você não é membro dele.
404customer_not_foundO customerId não é uma de suas contas de cobrança. Liste-as com GET /api/me/stripe-customers.
409subscription_existsO tenant já possui uma assinatura.
409attempt_in_progressUma tentativa para este tenant está aberta, ou esta tentativa está retida. Veja Repetir com segurança.
409no_customer, no_payment_methodSua conta não tem conta de cobrança, ou não tem cartão salvo. Assine um tenant no Console uma vez, ou adicione um cartão lá.
409tax_location_invalidO endereço de cobrança não pode ser usado para calcular impostos. Atualize-o no Console.
409customer_not_owned, customer_unavailable, currency_mismatch, attempt_abandonedA conta de cobrança do tenant não pôde ser verificada como sua, ou precisa de nossa ajuda. Entre em contato conosco.
422customer_fixedO tenant permanece na conta de cobrança que pagou por ele antes. Deixe customerId de fora, ou entre em contato conosco para mover o tenant.
422dev_tenant_not_supportedTenants de desenvolvimento não podem ser assinados pela API. Converta o tenant no Console.
422subscription_status_exceptionA última assinatura do tenant precisa de atenção primeiro. Entre em contato conosco.
500internal_error ou nenhumAlgo deu errado do nosso lado. Repita com a mesma chave e entre em contato se repetir.
502stripe_errorNosso provedor de pagamentos recusou ou falhou na solicitação. Repita com a mesma chave e entre em contato se repetir.
503stripe_unavailable, provisioning_failedO resultado é desconhecido, ou o pagamento foi bem-sucedido e a configuração ainda está para terminar. Repita com a mesma chave.

Você pode atualizar o cartão e o endereço de cobrança em Console > Configurações > Plano e Cobrança de qualquer tenant que use a mesma conta de cobrança.

Criar e assinar em um único script​

A criação de tenant não tem chave de idempotência. Se POST /api/tenants expirar, liste seus tenants com GET /api/tenants antes de criar novamente, para que uma repetição não crie um segundo tenant.

import { randomUUID } from 'node:crypto';

// Comentário: Defina o endpoint da API e os cabeçalhos de autenticação
const cloudApiEndpoint = process.env.CLOUD_API_ENDPOINT ?? 'https://cloud.logto.io';
const headers = {
authorization: `Bearer ${process.env.LOGTO_CLOUD_PAT}`,
'content-type': 'application/json',
};

// Comentário: Crie o tenant
const created = await fetch(`${cloudApiEndpoint}/api/tenants`, {
method: 'POST',
headers,
body: JSON.stringify({ name: 'Meu tenant automatizado', tag: 'production', regionName: 'EU' }),
});

if (!created.ok) {
throw new Error(`Falha na criação do tenant: ${await created.text()}`);
}

const tenant = await created.json();

// Comentário: Armazene a chave com o tenant, para que uma execução posterior possa repetir a mesma tentativa.
const idempotencyKey = randomUUID();

const subscribe = async () =>
fetch(`${cloudApiEndpoint}/api/tenants/${tenant.id}/subscription`, {
method: 'POST',
headers: { ...headers, 'idempotency-key': idempotencyKey },
body: JSON.stringify({ skuId: 'pro-202509' }),
});

const isOutcomeUnknown = async (response) =>
!response ||
response.status >= 500 ||
(response.status === 409 &&
(await response.clone().json()).error?.code === 'attempt_in_progress');

let subscription;

for (let attempt = 0; attempt < 5 && !subscription; attempt += 1) {
const response = await subscribe().catch(() => undefined);

if (response?.ok) {
subscription = await response.json();
} else if (await isOutcomeUnknown(response)) {
// Seguro: a mesma chave nunca cobra duas vezes.
await new Promise((resolve) => setTimeout(resolve, 2000 * (attempt + 1)));
} else {
throw new Error(`Assinatura recusada: ${await response.text()}`);
}
}

if (!subscription) {
throw new Error(
`Ainda desconhecido. Tente novamente mais tarde com a mesma chave: ${idempotencyKey}`
);
}