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

Locations

Create locations, generate establishing shots, approve one, and add time-of-day, weather, season, angle and motion variants from TypeScript.

client.locations scripts everything Location Studio does: it creates and edits locations, generates establishing-shot candidates, approves one as the main image, and adds variants for time of day, weather, seasons, angles and lighting, plus atmosphere clips. A location keeps its main image, its variant collections, its reference photos and a caption, so every later shot can show the same place. The methods call the Locations REST API. See Locations for the editor view.

Methods

MethodWhat it does
list(params?)List your locations
listArchived(params?)List your archived locations
get(id)Read one location, with its jobs in progress
create(input)Create a location
update(id, input)Change a location
delete(id) and restore(id)Archive a location, or bring it back
generate(input)Generate establishing-shot candidates
generateAsset(input)Generate a time-of-day, weather, season, angle or lighting variant
generateSurroundContinuation(input)Generate the next view of a 360-degree ring
generateMotion(input)Animate the main image into an atmosphere clip
removeAsset(id, data)Remove one take from a variant collection
approveMainImage(id, candidateJobId)Make a candidate the main image
recaption(id)Write the location's description again

client.locations

list(params?)

Lists your locations. By default it returns active locations only. Paging is optional: without limit you get the whole list and no cursor; with limit, at most 500, you get one page and a nextCursor.

list(params?: { archived?: boolean; limit?: number; cursor?: string }): Promise<{
  locations: Location[]
  nextCursor?: string | null
}>

Prop

Type

const { locations } = await client.locations.list()

const page = await client.locations.list({ limit: 100 })
const next = await client.locations.list({ limit: 100, cursor: page.nextCursor ?? undefined })

Keep paging until nextCursor is null.

listArchived(params?)

Lists your archived locations. It is a shortcut for list({ archived: true }).

listArchived(params?: { limit?: number; cursor?: string }): Promise<{ locations: Location[]; nextCursor?: string | null }>

Prop

Type

const { locations: archived } = await client.locations.listArchived()

get(id)

Reads one location, with pendingJobs, the variants still generating, and previousCandidates, up to 5 earlier main-image candidates, newest first. Promote one of those with approveMainImage().

get(id: string): Promise<LocationDetail>

Prop

Type

const location = await client.locations.get(locationId)
console.log(location.sourceImageUrl, location.weather, location.atmosphereMotions)

An archived location is still returned by id, so workflow nodes that point at it keep loading. A Location has its main image in sourceImageUrl, six collections (timeOfDay, weather, seasons, angles, lighting and atmosphereMotions, each a list of { name, url }), boards, referencePhotos, canonicalDescription, styleLock and updatedAt.

create(input)

Creates a location. name and nodeId are required. A script without a canvas node can pass "mcp-managed" as nodeId.

create(input: CreateLocationInput): Promise<{ id: string }>

Prop

Type

const { id: locationId } = await client.locations.create({
  nodeId: "mcp-managed",
  name: "Rainy Tokyo Alley",
  description: "Neon-soaked alley with vending machines",
  category: "urban",
  style: "realistic",
})

update(id, input)

Changes a location. Only the fields you send are written. The variant collections are not part of this call, because generation jobs add to them while you work. Use generateAsset() and removeAsset() for them.

update(id: string, input: UpdateLocationInput): Promise<{ id: string; updatedAt: string }>

Prop

Type

await client.locations.update(locationId, {
  canonicalDescription: "A narrow, rain-soaked alley lit by neon signs",
  styleLock: false,
  piiConsentAt: new Date().toISOString(),
  expectedUpdatedAt: location.updatedAt,
})

The 409 concurrent_modification arrives as a plain NodaroError with that code. Read the location again, merge your change, and retry.

delete(id) and restore(id)

delete() archives a location, and restore() brings it back. Permanent deletion is only available in the Nodaro app. When a restored name matches an active location's name, ignoring case, the server adds (restored) and returns the name it used.

delete(id: string): Promise<{ success: true; archived: true }>
restore(id: string): Promise<{ id: string; name: string }>

Prop

Type

await client.locations.delete(locationId)
const { name } = await client.locations.restore(locationId)

generate(input)

Generates establishing-shot candidates (POST /v1/generate-location). With count above 1, every job is reserved before any starts, so a failure part way through rolls the batch back.

generate(input: GenerateLocationInput): Promise<{ jobIds: string[]; jobId?: string }>

Prop

Type

// One candidate, written to the location when it completes
const { jobIds: [jobId] } = await client.locations.generate({
  name: "Rainy Tokyo Alley",
  description: "Neon-soaked alley with vending machines",
  attachToLocationId: locationId,
})

// Four candidates to choose from
const { jobIds } = await client.locations.generate({ name: "Rainy Tokyo Alley", count: 4 })

With attachToLocationId and count of 1, the result becomes the main image when the job completes. Otherwise, pick a candidate with approveMainImage(). quality and resolution are priced as in Generate Image. A value the model does not support is ignored.

generateAsset(input)

Generates one variant of the location (POST /v1/generate-location-asset). With attachToLocationId, attachToColumn and attachName, the result is added to that collection when the job completes.

generateAsset(input: GenerateLocationAssetInput): Promise<{ jobId: string }>

Prop

Type

const { jobId } = await client.locations.generateAsset({
  name: "Rainy Tokyo Alley",
  assetType: "weather",
  variant: "storm",
  attachToLocationId: locationId,
  attachToColumn: "weather",
  attachName: "storm",
})

generateSurroundContinuation(input)

Generates the next view of a 360-degree ring (POST /v1/generate-surround-continuation), continuing from the previous view. The server keeps half of the previous view exactly as it is, paints the other half, and matches its colors, so neighboring views join without a seam. This method works on Nodaro Cloud; other editions answer 403 edition_required before any processing, which the SDK throws as ForbiddenError.

generateSurroundContinuation(input: GenerateSurroundContinuationInput): Promise<{ jobId: string }>

Prop

Type

const { jobId } = await client.locations.generateSurroundContinuation({
  referenceImageUrl: previousRingView,
  direction: "right",
  degrees: 45,
  provider: "nano-banana-pro",
  aspectRatio: "16:9",
  attachToLocationId: locationId,
  attachToColumn: "angles",
  attachName: "Surround 45°",
})

generateMotion(input)

Animates the location's image into an atmosphere clip (POST /v1/generate-location-motion), with Generate Video in image-to-video mode. A location has one motion collection, so the clip always goes to atmosphereMotions and there is no attachToColumn.

generateMotion(input: GenerateLocationMotionInput): Promise<{ jobId: string }>

Prop

Type

const { jobId } = await client.locations.generateMotion({
  name: "Rainy Tokyo Alley",
  motionPrompt: "Slow dolly-in, neon signs flicker, light rain falling",
  sourceImageUrl: location.sourceImageUrl!,
  provider: "kling",
  attachToLocationId: locationId,
  attachName: "neon dolly-in",
})

removeAsset(id, data)

Removes one take from a variant collection (POST /v1/locations/:id/remove-asset). Every entry with that url is removed in one step. Use it, for example, before you regenerate a 360-degree view.

removeAsset(id: string, data: { column: LocationAttachColumn; url: string }): Promise<{ removed: true }>

Prop

Type

await client.locations.removeAsset(locationId, { column: "angles", url: oldViewUrl })

Throws NotFoundError when the URL is not in that collection or the location is not yours.

approveMainImage(id, candidateJobId)

Makes a completed candidate from generate() the location's main image. A vision model then writes the location's description, and the method returns both.

approveMainImage(id: string, candidateJobId: string): Promise<{ sourceImageUrl: string; canonicalDescription: string | null }>

Prop

Type

const { sourceImageUrl, canonicalDescription } = await client.locations.approveMainImage(locationId, jobIds[0])

canonicalDescription is null when the description could not be written. The main image is still set; call recaption() to try again.

recaption(id)

Writes the location's description again from its current main image.

recaption(id: string): Promise<{ canonicalDescription: string }>

Prop

Type

const { canonicalDescription } = await client.locations.recaption(locationId)

Fails with 400 no_source_image when the location has no main image, and with a 502 when the vision model fails.

Frequently asked questions

Last updated on

On this page