# OAuth e apps de desenvolvedor

> Registre e gerencie apps OAuth do Nodaro com client.developerApps, e troque códigos, revogue tokens e leia dados da tela de consentimento com client.oauth.

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

**`client.developerApps`** gerencia os apps OAuth que você possui: apps que permitem que outros usuários do Nodaro deem ao seu software acesso às contas deles. **`client.oauth`** cobre o lado do servidor do fluxo OAuth 2.0: troca um código de autorização por um token de acesso, revoga tokens e retorna os dados públicos que uma tela de consentimento mostra. O fluxo completo de consentimento está descrito em [OAuth](https://nodaro.ai/docs/developers/oauth).

## Métodos
| Método | O que faz |
| --- | --- |
| [`developerApps.list()`](#developerappslist) | Lista os seus apps |
| [`developerApps.get(id)`](#developerappsgetid) | Lê um app |
| [`developerApps.create(input)`](#developerappscreateinput) | Registra um app e obtém o segredo dele |
| [`developerApps.update(id, input)`](#developerappsupdateid-input) | Altera um app |
| [`developerApps.delete(id)`](#developerappsdeleteid) | Exclui um app |
| [`developerApps.rotateSecret(id)`](#developerappsrotatesecretid) | Substitui o segredo de um app |
| [`oauth.exchangeCode(input)`](#oauthexchangecodeinput) | Troca um código de autorização por um token de acesso |
| [`oauth.revoke(token)`](#oauthrevoketoken) | Revoga um token de acesso |
| [`oauth.getAppInfo(clientId, redirectUri?)`](#oauthgetappinfoclientid-redirecturi) | Lê os dados públicos de um app para uma tela de consentimento |

## Escopos
Um app pede escopos, e um usuário os aprova. O servidor aceita estes escopos:

| Escopo | Permite |
| --- | --- |
| `workflows:read` | Ler workflows |
| `workflows:write` | Criar e alterar workflows |
| `workflows:execute` | Executar workflows |
| `jobs:read` | Ler jobs |
| `assets:read` | Ler personagens, locais, objetos e outras entidades |
| `assets:write` | Criar e alterar entidades |
| `credits:read` | Ler o saldo de créditos |
| `apps:read` | Ler apps publicados |
| `pipelines:read` | Ler pipelines |
| `pipelines:execute` | Iniciar e cancelar pipelines |
| `pipelines:approve` | Aprovar e rejeitar etapas de pipelines |
| `presets:read` | Ler predefinições de nós |
| `workspaces:read` | Listar os espaços de trabalho de que o usuário faz parte |
| `workspaces:write` | Escolher o espaço de trabalho em que o app atua |

[Apps OAuth](https://nodaro.ai/docs/developers/oauth#scopes) mostra o que a tela de consentimento diz para cada escopo. Uma chamada sem o escopo de que precisa lança um `ForbiddenError` cujo `missingScope` indica qual é. Veja [Escopos e permissões ausentes](https://nodaro.ai/docs/developers/sdk/auth#scopes-and-missing-permissions).

## client.developerApps
Só o dono pode ler ou alterar um app. Os segredos são retornados exatamente uma vez.

### developerApps.list()
Lista os seus apps.

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

```ts
const { data: apps } = await client.developerApps.list()
const mine = apps.filter((app) => (app.kind ?? "user") === "user")
```

Um `DeveloperApp` tem `id`, `name`, `description`, `logoUrl`, `homepageUrl`, `redirectUris`, `allowedOrigins`, `scopesRequested`, `clientId`, `status` (`active`, `suspended` ou `pending_review`), `kind`, `createdAt` e `updatedAt`. `kind` é `"user"` para um app que você registrou. Os outros valores, `dynamic_mcp`, `first_party_mcp` e `community_instance`, são clientes que se registraram sozinhos. Só os apps `"user"` contam para o limite de cinco.

### developerApps.get(id)
Lê um app. O segredo nunca vem incluído.

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

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do app." },
}}
/>

```ts
const { data: app } = await client.developerApps.get(appId)
```

### developerApps.create(input)
Registra um app. A resposta inclui `clientSecret`. Guarde-o agora: o servidor mantém apenas um hash dele.

```ts
create(input: CreateDeveloperAppInput): Promise<{ data: DeveloperApp & { clientSecret: string } }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "O nome do app que os usuários veem na tela de consentimento." },
redirectUris: { type: 'string[]', required: true, description: "De 1 a 10 URIs, cada uma com https:// ou http://localhost." },
scopesRequested: { type: 'DeveloperAppScope[]', required: true, description: "Pelo menos um escopo." },
allowedOrigins: { type: 'string[]', description: "Até 5 origens simples, sem caminho, query ou hash, que podem chamar a API de um navegador com os tokens deste app." },
description: { type: 'string', description: "Uma descrição para a tela de consentimento." },
homepageUrl: { type: 'string', description: "A página inicial do seu app." },
logoUrl: { type: 'string', description: "O logotipo do seu app." },
}}
/>

```ts
const { data } = await client.developerApps.create({
name: "My integration",
redirectUris: ["https://example.com/oauth/callback"],
scopesRequested: ["workflows:read", "workflows:execute"],
})
console.log(data.clientId, data.clientSecret) // save both now
```

Você pode registrar cinco apps. Um sexto falha com `400 limit_reached`.

### developerApps.update(id, input)
Altera um app. Passe apenas os campos a alterar; as regras de `create()` valem para cada um.

```ts
update(id: string, input: UpdateDeveloperAppInput): Promise<{ data: DeveloperApp }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do app." },
name: { type: 'string', description: "O nome do app." },
description: { type: 'string', description: "A descrição." },
homepageUrl: { type: 'string', description: "A página inicial." },
logoUrl: { type: 'string', description: "O logotipo." },
redirectUris: { type: 'string[]', description: "A nova lista completa de URIs de redirecionamento." },
allowedOrigins: { type: 'string[]', description: "A nova lista completa de origens de navegador." },
scopesRequested: { type: 'DeveloperAppScope[]', description: "A nova lista completa de escopos." },
}}
/>

```ts
await client.developerApps.update(appId, {
redirectUris: ["https://example.com/oauth/callback", "https://staging.example.com/oauth/callback"],
})
```

### developerApps.delete(id)
Exclui um app.

```ts
delete(id: string): Promise<{ success: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do app." },
}}
/>

```ts
await client.developerApps.delete(appId)
```

Lança `NotFoundError` quando o ID não existe ou o app não é seu.

### developerApps.rotateSecret(id)
Cria um novo segredo do cliente e invalida o antigo na hora. O novo segredo só é retornado uma vez.

```ts
rotateSecret(id: string): Promise<{ clientSecret: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do app." },
}}
/>

```ts
const { clientSecret } = await client.developerApps.rotateSecret(appId)
```

Atualize o segredo no seu servidor imediatamente, porque as trocas de código com o segredo antigo deixam de funcionar.

## client.oauth
Os endpoints OAuth 2.0 que o servidor do seu app chama. Os nomes dos campos usam snake_case, como no padrão OAuth.

### oauth.exchangeCode(input)
Troca o código de autorização do redirecionamento de consentimento por um token de acesso (`POST /v1/oauth/token`). O SDK adiciona `grant_type: "authorization_code"` por você.

**Nunca o chame de um navegador.** A requisição contém o segredo do cliente, que deve ficar no seu servidor.

```ts
exchangeCode(input: {
client_id: string
client_secret: string
code: string
redirect_uri: string
}): Promise<{ access_token: string; token_type: "Bearer"; scope: string; expires_in: number }>
```

<TypeTable
type={{
client_id: { type: 'string', required: true, description: "O ID do cliente do seu app." },
client_secret: { type: 'string', required: true, description: "O segredo do cliente do seu app." },
code: { type: 'string', required: true, description: "O código do redirecionamento de consentimento." },
redirect_uri: { type: 'string', required: true, description: "A mesma URI de redirecionamento usada para obter o código." },
}}
/>

```ts

const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "https://example.com/oauth/callback",
})

// Act for the user who approved your app
const userClient = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(tokens.access_token),
})
```

`scope` lista os escopos concedidos, separados por espaços, e `expires_in` é a validade do token, em segundos.

### oauth.revoke(token)
Revoga um token de acesso (`POST /v1/oauth/revoke`, RFC 7009). Sempre responde `{ success: true }`, mesmo para um token desconhecido, porque o padrão proíbe revelar se um token era válido.

```ts
revoke(token: string): Promise<{ success: true }>
```

<TypeTable
type={{
token: { type: 'string', required: true, description: "O token de acesso a revogar." },
}}
/>

```ts
await client.oauth.revoke(accessToken)
```

### oauth.getAppInfo(clientId, redirectUri?)
Retorna os dados públicos de um app para uma tela de consentimento (`GET /v1/oauth/app-info`). Não precisa de token.

```ts
getAppInfo(clientId: string, redirectUri?: string): Promise<OAuthAppInfo>
```

<TypeTable
type={{
clientId: { type: 'string', required: true, description: "O ID do cliente do app." },
redirectUri: { type: 'string', description: "Uma URI de redirecionamento a verificar. A resposta então informa se exatamente essa URI está registrada." },
}}
/>

```ts
const info = await client.oauth.getAppInfo(clientId, "https://yourapp.com/oauth/callback")
if (!info.redirectUriRegistered) throw new Error("Unregistered redirect URI")
```

A resposta tem `name`, `description`, `logoUrl`, `homepageUrl`, `scopesRequested` e `redirectUriRegistered`. `redirectUriRegistered` é `true` apenas para uma URI registrada exatamente como foi passada, e `null` quando você não passa `redirectUri`. Isso permite que uma tela de consentimento recuse um redirecionamento não registrado sem revelar a lista de URIs.

## Frequently asked questions

### Como registrar um app OAuth com o SDK?

Chame client.developerApps.create com um nome, pelo menos uma URI de redirecionamento e os escopos de que você precisa. A resposta inclui o clientId e o clientSecret. O segredo só aparece uma vez, então guarde-o na hora.

### Posso trocar um código OAuth no navegador?

Não. client.oauth.exchangeCode precisa do segredo do cliente, que deve ficar no seu servidor. Faça a troca em código de servidor e passe ao navegador apenas o que ele precisa.

### Quantos apps OAuth posso registrar?

Cinco apps registrados por você. Um sexto create falha com 400 limit_reached. Os apps que se registraram sozinhos, como clientes MCP, não contam.

### O que acontece quando eu rotaciono o segredo do cliente?

client.developerApps.rotateSecret cria um novo segredo e invalida o antigo na hora. O novo segredo só é retornado uma vez.
