Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch
TypeScript SDK

OAuth and developer apps

Register and manage your Nodaro OAuth apps with client.developerApps, and exchange codes, revoke tokens and read consent-screen data with client.oauth.

client.developerApps manages the OAuth apps you own: apps that let other Nodaro users grant your software access to their accounts. client.oauth covers the server side of the OAuth 2.0 flow: it exchanges an authorization code for an access token, revokes tokens, and returns the public data a consent screen shows. The full consent flow is described in OAuth.

Methods

MethodWhat it does
developerApps.list()List your apps
developerApps.get(id)Read one app
developerApps.create(input)Register an app and get its secret
developerApps.update(id, input)Change an app
developerApps.delete(id)Delete an app
developerApps.rotateSecret(id)Replace an app's secret
oauth.exchangeCode(input)Exchange an authorization code for an access token
oauth.revoke(token)Revoke an access token
oauth.getAppInfo(clientId, redirectUri?)Read an app's public data for a consent screen

Scopes

An app asks for scopes, and a user approves them. The SDK types these scopes as DeveloperAppScope:

ScopeAllows
workflows:readReading workflows
workflows:writeCreating and changing workflows
workflows:executeRunning workflows
jobs:readReading jobs
assets:readReading characters, locations, objects and other assets
assets:writeCreating and changing assets
credits:readReading the credit balance
apps:readReading published apps
pipelines:readReading pipelines
pipelines:executeStarting and cancelling pipelines
pipelines:approveApproving and rejecting pipeline stages

A call without the scope it needs throws a ForbiddenError whose missingScope names it. See Scopes and missing permissions.

client.developerApps

Only the owner can read or change an app. Secrets are returned exactly once.

developerApps.list()

Lists your apps.

list(): Promise<{ data: DeveloperApp[] }>
const { data: apps } = await client.developerApps.list()
const mine = apps.filter((app) => (app.kind ?? "user") === "user")

A DeveloperApp has id, name, description, logoUrl, homepageUrl, redirectUris, allowedOrigins, scopesRequested, clientId, status (active, suspended or pending_review), kind, createdAt and updatedAt. kind is "user" for an app you registered. The other values, dynamic_mcp, first_party_mcp and community_instance, are clients that registered themselves. Only "user" apps count toward the limit of five.

developerApps.get(id)

Reads one app. The secret is never included.

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

Prop

Type

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

developerApps.create(input)

Registers an app. The answer includes clientSecret. Store it now: the server keeps only a hash of it.

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

Prop

Type

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

You can register five apps. A sixth fails with 400 limit_reached.

developerApps.update(id, input)

Changes an app. Pass only the fields to change; the rules of create() apply to each.

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

Prop

Type

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

developerApps.delete(id)

Deletes an app.

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

Prop

Type

await client.developerApps.delete(appId)

Throws NotFoundError when the id does not exist or the app is not yours.

developerApps.rotateSecret(id)

Creates a new client secret and invalidates the old one at once. The new secret is returned only once.

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

Prop

Type

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

Update your server's secret right away, because code exchanges with the old secret stop working.

client.oauth

The OAuth 2.0 endpoints your app's server calls. Field names use snake_case, as the OAuth standard does.

oauth.exchangeCode(input)

Exchanges the authorization code from the consent redirect for an access token (POST /v1/oauth/token). The SDK adds grant_type: "authorization_code" for you.

Never call it from a browser. The request contains your client secret, which must stay on your server.

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 }>

Prop

Type

import { createClient, StaticTokenAuth } from "@nodaro/sdk"

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 lists the granted scopes, separated by spaces, and expires_in is the token's lifetime in seconds.

oauth.revoke(token)

Revokes an access token (POST /v1/oauth/revoke, RFC 7009). It always answers { success: true }, even for an unknown token, because the standard forbids revealing whether a token was valid.

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

Prop

Type

await client.oauth.revoke(accessToken)

oauth.getAppInfo(clientId, redirectUri?)

Returns an app's public data for a consent screen (GET /v1/oauth/app-info). It needs no token.

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

Prop

Type

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

The answer has name, description, logoUrl, homepageUrl, scopesRequested and redirectUriRegistered. redirectUriRegistered is true only for an exactly registered URI, and null when you pass no redirectUri. It lets a consent screen refuse an unregistered redirect without revealing the list of URIs.

Frequently asked questions

Last updated on

On this page