NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
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 server accepts these scopes:

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
presets:readReading node presets
workspaces:readListing the workspaces the user belongs to
workspaces:writeChoosing the workspace the app works in

OAuth apps shows what the consent screen says for each scope. 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

How do I register an OAuth app with the SDK?

Call client.developerApps.create with a name, at least one redirect URI and the scopes you need. The answer includes the clientId and the clientSecret. The secret is shown only once, so store it right away.

Can I exchange an OAuth code in the browser?

No. client.oauth.exchangeCode needs your client secret, which must stay on your server. Run the exchange in server code and give the browser only what it needs.

How many OAuth apps can I register?

Five apps that you register yourself. A sixth create fails with 400 limit_reached. Apps that registered themselves, such as MCP clients, do not count.

What happens when I rotate a client secret?

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

Last updated on

On this page