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
| Method | What 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:
| Scope | Allows |
|---|---|
workflows:read | Reading workflows |
workflows:write | Creating and changing workflows |
workflows:execute | Running workflows |
jobs:read | Reading jobs |
assets:read | Reading characters, locations, objects and other assets |
assets:write | Creating and changing assets |
credits:read | Reading the credit balance |
apps:read | Reading published apps |
pipelines:read | Reading pipelines |
pipelines:execute | Starting and cancelling pipelines |
pipelines:approve | Approving 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 nowYou 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
Related
OAuth apps
Authentication
Authentication
Errors
Last updated on
Organizations and workspaces
Manage Nodaro organizations and workspaces from TypeScript. Create schools and teams, invite members, hand out join codes, and read credit usage reports.
Types
Every TypeScript type, helper function and constant that @nodaro/sdk exports, grouped by resource, with the page that documents each one.