# Organizações e espaços de trabalho

> Organizações e espaços de trabalho do Nodaro em TypeScript: crie escolas e equipes, convide membros, distribua códigos de entrada e veja o uso de créditos.

Source: https://nodaro.ai/pt-BR/docs/developers/sdk/organizations

**`client.organizations`** gerencia organizações, uma escola ou uma equipe, com os membros, os convites, o log de auditoria e os relatórios de uso delas. **`client.workspaces`** gerencia os espaços de trabalho dentro delas, como uma turma ou um projeto de equipe, com os membros e os códigos de entrada deles. Os métodos chamam a [API REST de espaços de trabalho](https://nodaro.ai/docs/developers/api/workspaces). Veja [Espaços de trabalho](https://nodaro.ai/docs/concepts/workspaces) para os conceitos.

As organizações são um recurso do Nodaro Cloud, ativado por instância. Em uma instância sem elas, todos os métodos desta página lançam `NotFoundError`.

## Pertencer e atuar
Pertencer a um espaço de trabalho e atuar nele são coisas diferentes. Estes dois recursos controlam quem pertence a cada lugar. Para **atuar** em um espaço de trabalho, de modo que as listas venham dele e o trabalho novo fique nele, crie um cliente com [`client.withWorkspace(workspaceId)`](https://nodaro.ai/docs/developers/sdk/client#withworkspaceworkspaceid) ou passe `workspaceId` para `createClient`:

```ts
const classroom = client.withWorkspace(workspaceId)
await classroom.workflows.run(workflowId) // runs in the class
await client.workflows.run(workflowId)    // runs in your personal space
```

[`client.me()`](https://nodaro.ai/docs/developers/sdk/client#me) retorna as organizações e os espaços de trabalho a que você pertence, além de `lastWorkspaceId`.

O servidor decide todas as permissões. Ele responde se quem faz a chamada pode convidar, remover ou renomear, e a resposta chega como um erro. Não tente adivinhar isso no seu cliente, porque uma configuração pode mudar a resposta.

## Métodos
| Método | O que faz |
| --- | --- |
| [`organizations.list()`](#organizationslist) | Lista as organizações a que você pertence |
| [`organizations.get(id)`](#organizationsgetid) | Lê uma organização |
| [`organizations.create(input)`](#organizationscreateinput) | Cria uma escola ou uma equipe |
| [`organizations.update(id, input)`](#organizationsupdateid-input) | Renomeia a organização ou altera as configurações dela |
| [`organizations.delete(id)`](#organizationsdeleteid) | Exclui uma organização |
| [`organizations.transferOwnership(id, userId)`](#organizationstransferownershipid-userid) | Torna outro membro o proprietário |
| [`organizations.leave(id)`](#organizationsleaveid) | Sai de uma organização |
| [`organizations.listMembers()`, `updateMember()`, `removeMember()`](#organization-members) | Gerencia os membros |
| [`organizations.invite(orgId, input)`](#organizationsinviteorgid-input) | Convida pessoas por e-mail |
| [`organizations.listInvitations()`, `revokeInvitation()`, `resendInvitation()`](#manage-invitations) | Gerencia os convites enviados |
| [`organizations.previewInvitation()`, `acceptInvitation()`](#preview-and-accept-an-invitation) | Mostra e aceita um convite |
| [`organizations.audit(orgId, opts?)`](#organizationsauditorgid-opts) | Lê o log de auditoria |
| [`organizations.usage()`, `usageRows()`, `usageCsv()`](#organization-usage) | Lê o uso de créditos |
| [`workspaces.list()`](#workspaceslist) | Lista os espaços de trabalho a que você pertence |
| [`workspaces.listForOrg(orgId, opts?)`](#workspaceslistfororgorgid-opts) | Lista os espaços de trabalho de uma organização |
| [`workspaces.get(id)`](#workspacesgetid) | Lê um espaço de trabalho |
| [`workspaces.create(orgId, input)`](#workspacescreateorgid-input) | Cria um espaço de trabalho |
| [`workspaces.update(id, input)`](#workspacesupdateid-input) | Altera um espaço de trabalho |
| [`workspaces.setArchived(id, archived)`](#workspacessetarchivedid-archived) | Arquiva ou desarquiva um espaço de trabalho |
| [`workspaces.listMembers()`, `addMember()`, `updateMember()`, `removeMember()`](#workspace-members) | Gerencia os membros do espaço de trabalho |
| [`workspaces.getJoinCode()`, `actOnJoinCode()`](#join-codes) | Gerencia o código de entrada |
| [`workspaces.join(code)`](#workspacesjoincode) | Entra em um espaço de trabalho com um código |
| [`workspaces.usage()`, `usageRows()`, `usageCsv()`](#workspace-usage) | Lê o uso de créditos de um espaço de trabalho |

## client.organizations
### organizations.list()
Lista as organizações a que você pertence (`GET /v1/orgs`).

```ts
list(): Promise<{ data: OrganizationView[] }>
```

```ts
const { data: orgs } = await client.organizations.list()
```

### organizations.get(id)
Lê uma organização (`GET /v1/orgs/:id`).

```ts
get(id: string): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da organização." },
}}
/>

```ts
const { data: org } = await client.organizations.get(orgId)
console.log(org.status) // pending, active, suspended or deleted
```

### organizations.create(input)
Cria uma organização, com você como proprietário (`POST /v1/orgs`). Uma nova organização normalmente começa como `pending` e passa a `active` quando o Nodaro a aprova. Enquanto ela está pendente, só você consegue vê-la e nada nela pode mudar, então avise o usuário de que ela aguarda aprovação.

```ts
create(input: { name: string; kind: "school" | "team"; slug?: string; acceptTerms?: boolean; settings?: OrgSettings }): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "O nome da organização." },
kind: { type: '"school" | "team"', required: true, description: "O tipo de organização. Ele define as configurações padrão." },
slug: { type: 'string', description: "Um nome curto e único. Um slug já em uso falha com 409 name_taken." },
acceptTerms: { type: 'boolean', description: "Obrigatório para uma escola: o proprietário confirma que tem autoridade para matricular alunos." },
settings: { type: 'OrgSettings', description: "Configurações que valem para todos os espaços de trabalho da organização." },
}}
/>

```ts
const { data: school } = await client.organizations.create({
name: "Sunrise School",
kind: "school",
acceptTerms: true,
})
```

Uma escola sem `acceptTerms: true` falha com `400 terms_required`.

### organizations.update(id, input)
Renomeia uma organização ou altera as configurações dela (`PATCH /v1/orgs/:id`).

```ts
update(id: string, input: { name?: string; settings?: OrgSettings }): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da organização." },
name: { type: 'string', description: "Um novo nome." },
settings: { type: 'OrgSettings', description: "As configurações a alterar. Veja a tabela de configurações abaixo." },
}}
/>

```ts
await client.organizations.update(orgId, {
settings: { default_workflow_visibility: "workspace", allowed_email_domains: ["school.example"] },
})
```

As configurações funcionam em camadas: uma configuração do espaço de trabalho prevalece sobre a da organização, que prevalece sobre o padrão do tipo dela. Definir uma chave como `false` é um valor real, e não um “não definido”.

| Chave | Tipo | Significado |
| --- | --- | --- |
| `admin_access` | `view` ou `edit` | O que os administradores podem fazer com o workflow de um membro |
| `default_workflow_visibility` | `private` ou `workspace` | A visibilidade de um novo workflow |
| `member_access_to_shared` | `view` ou `edit` | O que os membros podem fazer com um workflow compartilhado com o espaço de trabalho |
| `members_can_create_projects` | booleano | Se os membros podem criar projetos no espaço de trabalho |
| `member_caps_enabled` | booleano | Se valem limites de créditos por membro |
| `personal_space_enabled` | booleano | Se os membros mantêm um espaço pessoal |
| `workspace_admins_can_invite` | booleano | Se os administradores do espaço de trabalho podem convidar novas pessoas |
| `collaborators_can_invite` | booleano | Se um colaborador com permissão de edição pode convidar mais colaboradores |
| `allowed_email_domains` | lista de strings | Só na organização: os domínios de e-mail que podem entrar |
| `vocabulary_overrides` | objeto | Só na organização: novos rótulos para as palavras do tipo, como `{ "workspace": "Cohort" }`. Adicione `"workspace_gender": "m"` para uma palavra de espaço de trabalho gramaticalmente masculina, para que as frases em hebraico e em português do Brasil concordem com ela |

As duas chaves exclusivas da organização são substituídas por inteiro quando você as atualiza.

### organizations.delete(id)
Exclui uma organização (`DELETE /v1/orgs/:id`). Ela fica oculta para os antigos membros, mas nada é destruído. Arquive todos os espaços de trabalho antes: caso contrário, a chamada falha com `409 has_active_workspaces`.

```ts
delete(id: string): Promise<{ data: { id: string; status: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da organização." },
}}
/>

```ts
await client.organizations.delete(orgId)
```

### organizations.transferOwnership(id, userId)
Torna outro membro o proprietário (`POST /v1/orgs/:id/transfer-ownership`). Você passa a ser administrador.

```ts
transferOwnership(id: string, userId: string): Promise<{ data: { orgId: string; ownerUserId: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da organização." },
userId: { type: 'string', required: true, description: "O membro que se torna o proprietário." },
}}
/>

```ts
await client.organizations.transferOwnership(orgId, newOwnerId)
```

### organizations.leave(id)
Sai de uma organização (`POST /v1/orgs/:id/leave`). O proprietário não pode sair: transfira a propriedade antes, ou a chamada falha com `409 owner_cannot_leave`.

```ts
leave(id: string): Promise<{ data: { orgId: string; left: boolean } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da organização." },
}}
/>

```ts
await client.organizations.leave(orgId)
```

### Membros da organização
Liste, altere e remova os membros de uma organização (`/v1/orgs/:id/members`). Um membro está `active` ou `suspended`; um membro suspenso mantém a vaga, mas não pode atuar. O proprietário não pode ser suspenso, removido nem rebaixado.

```ts
listMembers(orgId: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<OrgMemberView>>
updateMember(orgId: string, userId: string, input: { role?: "admin" | "member"; status?: "active" | "suspended" }): Promise<{ data: OrgMemberView }>
removeMember(orgId: string, userId: string): Promise<{ data: { removed: boolean } }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
userId: { type: 'string', description: "O membro. Obrigatório em updateMember e removeMember." },
cursor: { type: 'string', description: "listMembers: o nextCursor da página anterior." },
limit: { type: 'number', description: "listMembers: o tamanho da página." },
role: { type: '"admin" | "member"', description: "updateMember: a nova função." },
status: { type: '"active" | "suspended"', description: "updateMember: suspende ou reativa o membro." },
}}
/>

```ts
const { data: members, nextCursor } = await client.organizations.listMembers(orgId)
await client.organizations.updateMember(orgId, userId, { role: "admin" })
```

### organizations.invite(orgId, input)
Convida pessoas por e-mail (`POST /v1/orgs/:id/invitations`). O método retorna **uma linha por endereço**.

```ts
invite(orgId: string, input: {
emails: string[]
orgRole?: "admin" | "member"
workspaceId?: string
workspaceRole?: "admin" | "member"
}): Promise<{ data: InvitationDelivery[] }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
emails: { type: 'string[]', required: true, description: "Os endereços a convidar." },
orgRole: { type: '"admin" | "member"', description: "A função na organização." },
workspaceId: { type: 'string', description: "Um espaço de trabalho ao qual adicionar as pessoas quando elas aceitarem." },
workspaceRole: { type: '"admin" | "member"', description: "A função delas nesse espaço de trabalho." },
}}
/>

```ts
const { data: rows } = await client.organizations.invite(orgId, {
emails: ["teacher@school.example"],
workspaceId,
workspaceRole: "admin",
})
for (const row of rows) {
if (row.status !== "sent") showInviteLink(row.email, row.link) // not emailed: share the link yourself
}
```

**Exiba o link.** Uma linha cujo `status` não é `sent` traz um `link` no lugar: a instalação não tem um provedor de e-mail, ou a entrega falhou. O convite existe de qualquer forma, e sem o link ninguém consegue chegar até ele. Um convite expira depois de 14 dias. O limite diário de convites da organização responde com `RateLimitedError`.

### Gerenciar convites
Liste, revogue e reenvie convites.

```ts
listInvitations(orgId: string, opts?: { status?: "open" | "accepted" | "revoked" | "expired"; workspaceId?: string; cursor?: string; limit?: number }): Promise<OrgPage<InvitationView>>
revokeInvitation(id: string): Promise<{ data: { id: string; revoked: boolean } }>
resendInvitation(id: string): Promise<{ data: InvitationDelivery & { id: string } }>
```

<TypeTable
type={{
orgId: { type: 'string', description: "listInvitations: o ID da organização." },
id: { type: 'string', description: "revokeInvitation e resendInvitation: o ID do convite." },
status: { type: '"open" | "accepted" | "revoked" | "expired"', description: "listInvitations: só os convites neste estado." },
workspaceId: { type: 'string', description: "listInvitations: só os convites para este espaço de trabalho." },
cursor: { type: 'string', description: "listInvitations: o nextCursor da página anterior." },
limit: { type: 'number', description: "listInvitations: o tamanho da página." },
}}
/>

```ts
const { data: open } = await client.organizations.listInvitations(orgId, { status: "open" })
await client.organizations.resendInvitation(open[0].id)
```

### Ver e aceitar um convite
`previewInvitation()` é **público**: funciona enquanto a pessoa convidada ainda não entrou na conta e retorna o endereço mascarado. `acceptInvitation()` exige que quem faz a chamada tenha entrado na conta com o mesmo e-mail do convite.

```ts
previewInvitation(token: string): Promise<{ data: InvitationPreview }>
acceptInvitation(token: string): Promise<{ data: { orgId: string; workspaceId: string | null } }>
```

<TypeTable
type={{
token: { type: 'string', required: true, description: "O token do link do convite." },
}}
/>

```ts
const { data: preview } = await client.organizations.previewInvitation(token)
const { data: joined } = await signedInClient.organizations.acceptInvitation(token)
```

A aceitação falha com `400 invitation_expired`, `invitation_revoked`, `invitation_accepted` ou `email_mismatch`, e com um `ForbiddenError` quando a organização só admite domínios de e-mail da lista.

### organizations.audit(orgId, opts?)
Lê o log de auditoria da organização, do mais recente para o mais antigo (`GET /v1/orgs/:id/audit`). O log continua legível enquanto a organização está suspensa.

```ts
audit(orgId: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<OrgAuditEntry>>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
limit: { type: 'number', description: "O tamanho da página." },
}}
/>

```ts
const { data: entries } = await client.organizations.audit(orgId, { limit: 50 })
for (const e of entries) console.log(e.createdAt, e.action, e.actor?.displayName ?? "system")
```

Cada entrada tem `id`, `workspaceId`, `action`, `targetType`, `targetId`, `details`, `createdAt` e `actor`, que é `null` nas ações que o sistema executou. `action` é uma lista **aberta**: mostre as ações que você conhece e a string bruta das demais. Um código que trata só uma lista fixa quebra na primeira ação nova.

### Uso da organização
Relatórios de uso de créditos para um intervalo de datas, para o proprietário e os administradores da organização (`GET /v1/orgs/:id/usage`). `usage()` agrupa o relatório, `usageRows()` retorna as execuções por trás dele, das mais recentes para as mais antigas, uma página por vez, e `usageCsv()` retorna o relatório ou as linhas como texto CSV.

```ts
usage(orgId: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: "workspace" | "member" | "model" | "day"; workspaceId?: string; userId?: string }): Promise<{ data: UsageReport }>
usageRows(orgId: string, opts?: { from?: string; to?: string; tz?: string; workspaceId?: string; userId?: string; cursor?: string; limit?: number }): Promise<OrgPage<UsageLogEntry>>
usageCsv(orgId: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: string; workspaceId?: string; userId?: string }): Promise<string>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
from: { type: 'string', description: "O primeiro dia do intervalo, incluído." },
to: { type: 'string', description: "O último dia do intervalo, incluído. Um intervalo tem no máximo 366 dias." },
tz: { type: 'string', description: "Um fuso horário IANA para os dias, como Europe/Rome." },
groupBy: { type: '"workspace" | "member" | "model" | "day"', description: "usage e usageCsv: como agrupar o relatório." },
workspaceId: { type: 'string', description: "Só um espaço de trabalho." },
userId: { type: 'string', description: "Só um membro." },
cursor: { type: 'string', description: "usageRows: o nextCursor da página anterior." },
limit: { type: 'number', description: "usageRows: o tamanho da página." },
}}
/>

```ts
const { data: report } = await client.organizations.usage(orgId, {
from: "2026-09-01",
to: "2026-09-30",
tz: "Europe/Rome",
groupBy: "workspace",
})
const csv = await client.organizations.usageCsv(orgId, { from: "2026-09-01", to: "2026-09-30" })
```

Cada linha informa três valores de créditos. `credits` é o quanto as execuções custaram até agora: o valor liquidado das execuções terminadas e a reserva retida das demais. `settledCredits` e `inFlightCredits` dividem esse valor em duas partes. Os totais cobrem o intervalo inteiro, mesmo quando um agrupamento está `truncated`.

- `platformAbsorbedCredits` é o excedente de uma execução cobrada por uso que passou do orçamento restante do espaço de trabalho, excedente que a plataforma absorve.
- `chargedToBudget` é igual a `settledCredits` menos `platformAbsorbedCredits`: os créditos liquidados que chegaram ao orçamento.
- `appMarkupAbsorbedCredits` é o acréscimo de um app que o orçamento não conseguiu cobrir. Ele não tem uma execução no relatório, então não faz parte de `chargedToBudget`.

As exportações em CSV são limitadas a dez por minuto por usuário.

## client.workspaces
### workspaces.list()
Lista os espaços de trabalho a que você pertence e o seu `lastWorkspaceId` (`GET /v1/workspaces`). O método retorna resumos, a mesma lista que `client.me()` traz.

```ts
list(): Promise<{ data: WorkspaceSummary[]; lastWorkspaceId: string | null }>
```

```ts
const { data: workspaces, lastWorkspaceId } = await client.workspaces.list()
```

### workspaces.listForOrg(orgId, opts?)
Lista os espaços de trabalho de uma organização (`GET /v1/orgs/:id/workspaces`).

```ts
listForOrg(orgId: string, opts?: { includeArchived?: boolean }): Promise<{ data: WorkspaceView[] }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
includeArchived: { type: 'boolean', default: 'false', description: "Inclui os espaços de trabalho arquivados." },
}}
/>

```ts
const { data: classes } = await client.workspaces.listForOrg(orgId)
```

### workspaces.get(id)
Lê um espaço de trabalho completo (`GET /v1/workspaces/:id`).

```ts
get(id: string): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
}}
/>

```ts
const { data: workspace } = await client.workspaces.get(workspaceId)
```

### workspaces.create(orgId, input)
Cria um espaço de trabalho em uma organização (`POST /v1/orgs/:id/workspaces`). A organização precisa estar ativa.

```ts
create(orgId: string, input: { name: string; slug?: string; description?: string; settings?: WorkspaceSettings }): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "O ID da organização." },
name: { type: 'string', required: true, description: "O nome do espaço de trabalho." },
slug: { type: 'string', description: "Um nome curto e único." },
description: { type: 'string', description: "Uma descrição." },
settings: { type: 'WorkspaceSettings', description: "Configurações que substituem as da organização." },
}}
/>

```ts
const { data: classOne } = await client.workspaces.create(orgId, { name: "Class 1" })
```

### workspaces.update(id, input)
Altera um espaço de trabalho (`PATCH /v1/workspaces/:id`).

```ts
update(id: string, input: { name?: string; description?: string | null; settings?: WorkspaceSettings }): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
name: { type: 'string', description: "Um novo nome." },
description: { type: 'string | null', description: "Uma nova descrição, ou null para removê-la." },
settings: { type: 'WorkspaceSettings', description: "As configurações a alterar." },
}}
/>

```ts
await client.workspaces.update(workspaceId, { settings: { members_can_create_projects: true } })
```

### workspaces.setArchived(id, archived)
Arquiva ou desarquiva um espaço de trabalho (`POST /v1/workspaces/:id/archive` ou `/unarchive`). Arquivar pode ser desfeito e não destrói nada: o espaço de trabalho deixa de aceitar trabalho novo e continua totalmente legível. Uma gravação em um espaço de trabalho arquivado falha com um `ForbiddenError`.

```ts
setArchived(id: string, archived: boolean): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
archived: { type: 'boolean', required: true, description: "true arquiva, false desarquiva." },
}}
/>

```ts
await client.workspaces.setArchived(workspaceId, true)
```

### Membros do espaço de trabalho
Liste, adicione, altere e remova os membros de um espaço de trabalho (`/v1/workspaces/:id/members`). Uma pessoa precisa já pertencer à organização para ser adicionada; para trazer alguém novo, convide a pessoa. Os proprietários e administradores da organização são administradores de todos os espaços de trabalho sem aparecer na lista.

```ts
listMembers(id: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<WorkspaceMemberView>>
addMember(id: string, input: { userId: string; role: "admin" | "member" }): Promise<{ data: WorkspaceMemberView }>
updateMember(id: string, userId: string, input: { role?: "admin" | "member"; status?: "active" | "suspended"; creditCap?: number | null }): Promise<{ data: WorkspaceMemberView }>
removeMember(id: string, userId: string): Promise<{ data: { removed: boolean } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
userId: { type: 'string', description: "O membro. Obrigatório em addMember, updateMember e removeMember." },
role: { type: '"admin" | "member"', description: "A função no espaço de trabalho." },
status: { type: '"active" | "suspended"', description: "updateMember: suspende ou reativa o membro neste espaço de trabalho." },
creditCap: { type: 'number | null', description: "updateMember: um limite de créditos para este membro, ou null para nenhum limite." },
cursor: { type: 'string', description: "listMembers: o nextCursor da página anterior." },
limit: { type: 'number', description: "listMembers: o tamanho da página." },
}}
/>

```ts
await client.workspaces.addMember(workspaceId, { userId, role: "member" })
await client.workspaces.updateMember(workspaceId, userId, { creditCap: 500 })
```

Adicionar alguém que não é um membro ativo da organização falha com `400 not_org_member`, e adicionar alguém duas vezes falha com `409 already_a_member`.

### Códigos de entrada
Um código de entrada permite que as pessoas entrem em um espaço de trabalho sem convite. Só os administradores do espaço de trabalho podem ler ou alterar o código.

```ts
getJoinCode(id: string): Promise<{ data: JoinCodeView | null }>
actOnJoinCode(id: string, action: "rotate" | "enable" | "disable"): Promise<{ data: JoinCodeView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
action: { type: '"rotate" | "enable" | "disable"', description: "actOnJoinCode: rotate cria um novo código e desativa o antigo na hora. enable e disable ativam e desativam o código." },
}}
/>

```ts
const { data: code } = await client.workspaces.actOnJoinCode(workspaceId, "enable")
console.log(code.code) // for example BCDFGHJK
```

`getJoinCode()` retorna `null` quando ainda não foi criado nenhum código.

### workspaces.join(code)
Entra em um espaço de trabalho com o código de entrada dele (`POST /v1/workspaces/join`). Funciona seja qual for o espaço de trabalho em que o seu cliente atua, então uma seleção desatualizada nunca impede a entrada.

```ts
join(code: string): Promise<{ data: { orgId: string; workspaceId: string } }>
```

<TypeTable
type={{
code: { type: 'string', required: true, description: "O código de entrada." },
}}
/>

```ts
const { data: joined } = await client.workspaces.join("BCDFGHJK")
```

Um código desconhecido ou desativado, ou o código de um espaço de trabalho arquivado, falha com `400 join_code_invalid`: uma única resposta para os três casos, para que um código não revele o que existe. Tentativas demais respondem com `RateLimitedError`.

### Uso do espaço de trabalho
Os mesmos relatórios de créditos da organização, para um espaço de trabalho (`GET /v1/workspaces/:id/usage`). Um membro vê as próprias execuções; um administrador vê as de todos e pode filtrar por `userId`.

```ts
usage(id: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: "member" | "model" | "day"; userId?: string }): Promise<{ data: UsageReport }>
usageRows(id: string, opts?: { from?: string; to?: string; tz?: string; userId?: string; cursor?: string; limit?: number }): Promise<OrgPage<UsageLogEntry>>
usageCsv(id: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: string; userId?: string }): Promise<string>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do espaço de trabalho." },
from: { type: 'string', description: "O primeiro dia, incluído." },
to: { type: 'string', description: "O último dia, incluído." },
tz: { type: 'string', description: "Um fuso horário IANA." },
groupBy: { type: '"member" | "model" | "day"', description: "Como agrupar o relatório." },
userId: { type: 'string', description: "Só para administradores: um membro." },
cursor: { type: 'string', description: "usageRows: o nextCursor da página anterior." },
limit: { type: 'number', description: "usageRows: o tamanho da página." },
}}
/>

```ts
const { data: byModel } = await client.workspaces.usage(workspaceId, { groupBy: "model" })
```

## Erros
O servidor decide todas as permissões e responde com um erro. No SDK, um 403 vira `ForbiddenError`, um 404 vira `NotFoundError` e um 429 vira `RateLimitedError`; leia a `message` deles para saber o motivo. Os outros status chegam como `NodaroError`, com o `code` do servidor.

| Status | Código | Quando |
| --- | --- | --- |
| 400 | `validation_error` | Um campo é inválido, um cursor está malformado, ou um relatório de uso tem data, fuso horário ou intervalo inválido |
| 400 | `terms_required` | Uma escola foi criada sem `acceptTerms: true` |
| 400 | `not_org_member` | A pessoa adicionada a um espaço de trabalho não é um membro ativo da organização dele |
| 400 | `token_workspace_mismatch` | Um token vinculado a um espaço de trabalho foi usado com outro espaço de trabalho |
| 400 | `join_code_invalid` | O código de entrada não existe, está desativado ou pertence a um espaço de trabalho arquivado |
| 400 | `invitation_expired`, `invitation_revoked`, `invitation_accepted`, `email_mismatch` | O convite não pode ser aceito |
| 403 | | A função é baixa demais, o membro está suspenso, a organização não está ativa, o espaço de trabalho está arquivado, ou o domínio de e-mail não é permitido |
| 404 | | A organização, o espaço de trabalho, o membro ou o convite não existe, ou você não é membro dele |
| 409 | `name_taken` | O slug está em uso |
| 409 | `already_a_member` | A pessoa já está no espaço de trabalho |
| 409 | `owner_cannot_leave` | O proprietário tentou sair |
| 409 | `has_active_workspaces` | A organização ainda tem espaços de trabalho que não estão arquivados |
| 429 | | Organizações criadas, tentativas de entrada, exportações CSV ou convites em excesso |
| 503 | `billing_unavailable` | Os relatórios de uso ainda não estão disponíveis nesta instância |
| 503 | `audit_unavailable` | Não foi possível registrar uma exportação CSV no log de auditoria. Tente de novo. |

## Frequently asked questions

### Qual é a diferença entre uma organização e um espaço de trabalho?

Uma organização é a escola ou a equipe, com um proprietário, administradores e membros. Um espaço de trabalho é onde os membros trabalham juntos dentro dela, como uma turma ou uma equipe de projeto. Todo espaço de trabalho pertence a uma organização.

### Como executo um workflow dentro de um espaço de trabalho?

Crie um cliente para esse espaço de trabalho com client.withWorkspace(workspaceId) e chame run nele. O cliente original continua trabalhando no seu espaço pessoal.

### Por que a minha nova organização está pendente?

Uma nova organização começa com o status pending e passa a active quando o Nodaro a aprova. Até lá, só o proprietário consegue vê-la, e nada nela pode mudar.

### O que devo fazer quando um convite volta com um link?

Mostre o link a quem enviou o convite. Uma linha cujo status não é sent não foi enviada por e-mail, porque a instalação não tem um provedor de e-mail ou a entrega falhou. O convite existe, e o link é a única forma de chegar até ele.
