Brand
Entry point, profile, contact and soul.
Privacy and analytics
We use necessary technical cookies and, only with your consent, analytics and marketing pixels to improve the website and measure campaigns.
Entry point, profile, contact and soul.
Brand summary; an external MCP client calls it first to orient itself. Returns the brand name and short id, plan, Brand Manager, active work and pending task counts, and the capabilities the caller may use in this brand. Inside a conversation the brand is already known: call it only when the person asks how the brand is doing.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.overview |
| MCP tool | brand_overview |
| Scope | brand.read |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Nödo, Voice, MCP, API, Chat, Screens, Apps |
| Status | Available |
| Widget | record |
Input
{}Output
{
id: string // uuid
shortId: string // Human id such as BRD-8C1TZA.
name: string
plan: string | null
manager: {
id: string // uuid
name: string
avatarUrl: string | null
}
| null
counts: {
activeWorks: integer // -9007199254740991–9007199254740991
pendingTasks: integer // -9007199254740991–9007199254740991
pendingMemory: integer // -9007199254740991–9007199254740991
}
role: string | null // Brand role of the caller, or null for staff.
capabilities: string[] // Capability ids the caller may use here.
}The brand voice compiled from the current Genome (which the brand may not have confirmed yet): tone, archetypes, vocabulary, guardrails, locales and on/off-brand examples. Use it before longer or public-facing writing on behalf of the brand when the voice already in context is not enough; missing sections are listed in gaps so you ask instead of guessing.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.soul.read |
| MCP tool | brand_soul_read |
| Scope | brand.read |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Nödo, Voice, MCP, API, Chat, Screens, Apps |
| Status | Available |
Input
{}Output
{
revision: integer // -9007199254740991–9007199254740991
genomeRevision: integer // -9007199254740991–9007199254740991 · Accepted Genome revision it was compiled from.
compiledAt: string
voice: {
summary: string // ≤ 600
tone: Array<{
axis: string
position: number // -2–2
}> // ≤ 6 items · e.g. formal(-2)…casual(+2).
traits: string[] // ≤ 8 items
archetypes: string[] // ≤ 3 items
}
vocabulary: {
use: string[] // ≤ 30 items
avoid: string[] // ≤ 30 items
}
guardrails: string[] // ≤ 15 items
audiences: string[] // ≤ 6 items
locales: Array<{
locale: string
notes: string // ≤ 300
}> // ≤ 8 items
writing: {
address: string // ≤ 300 · How the brand addresses the reader (tú, usted, vos) and its exceptions.
addressConfirmed?: boolean // False while no person has confirmed the form of address (Genome 2.1): it is a proposal, so Nödo says so once instead of assuming it.
sayDontSay: Array<{
say: string
dontSay: string
}> // ≤ 10 items
conventions: string[] // ≤ 8 items
channels: Array<{
channel: string
guidance: string
}> // ≤ 6 items
} // default {"address":"","sayDontSay":[],"conventions":[],"channels":[]}
examples: Array<{
context: string // default ""
onBrand: string
offBrand: string
}> // ≤ 10 items
gaps: string[] // Genome sections missing or unaccepted; Nödo asks instead of guessing.
}Changes display name, country, city, organization type or primary language. Written to the Genome with an optimistic revision check; send expectedRevision from genome.read.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.profile.update |
| MCP tool | brand_profile_update |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | Nödo, MCP, API, Chat, Screens, Apps |
| Status | Available |
| Widget | confirm |
Input
{
expectedRevision: integer // -9007199254740991–9007199254740991
displayName?: string // ≤ 120
countryCode?: string // ≤ 2
city?: string // ≤ 120
organizationType?: string // ≤ 40
primaryLanguage?: string // ≤ 12
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Public channels of the brand: website, email, phone and social profiles.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.contacts.list |
| MCP tool | brand_contacts_list |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Nödo, Voice, MCP, API, Chat, Screens, Apps |
| Status | Available |
| Widget | list |
Input
{}Output
{
items: Array<{
id: string // uuid
type: "website" | "email" | "phone" | "whatsapp" | "instagram" | "facebook" | "linkedin" | "tiktok" | "x" | "youtube" | "pinterest" | "other"
value: string
label: string | null
}>
}Adds, changes or removes one public channel. Values are normalized and checked per type; duplicates are rejected.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.contacts.save |
| MCP tool | brand_contacts_save |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | Nödo, MCP, API, Chat, Screens, Apps |
| Status | Available |
| Widget | confirm |
Input
{
op: "upsert"
id?: string // uuid
type: "website" | "email" | "phone" | "whatsapp" | "instagram" | "facebook" | "linkedin" | "tiktok" | "x" | "youtube" | "pinterest" | "other"
value: string // ≤ 500
label?: string // ≤ 80
}
| {
op: "delete"
id: string // uuid
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Adds or changes one «Contact and social» entry, address included, and returns the stored row. Values are normalized and checked per type; duplicates and entries over the per-type limit are rejected.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.connections.save |
| MCP tool | brand_connections_save |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
type: "website" | "instagram" | "facebook" | "twitter" | "linkedin" | "tiktok" | "youtube" | "pinterest" | "whatsapp" | "email" | "phone" | "address" | "other"
value: string // ≤ 2000
label?: string // ≤ 200
connectionId?: string // uuid
}Output
{
connection: {
id: string // uuid
}
}Saves the brand content focus edited by hand (networks, content types, frequency, objective) and marks it completed. Replaces the whole document.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.content_focus.update |
| MCP tool | brand_content_focus_update |
| Scope | brand.write |
| Minimum role | Member |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
content: {
networks: Record<string, unknown>
generalNotes?: string | null
}
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Nömad team only: rewrites the brand content focus with AI in the background. The document shows generating until the run ends; a failure to queue marks it failed.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.content_focus.regenerate |
| MCP tool | brand_content_focus_regenerate |
| Scope | brand.write |
| Minimum role | Member |
| Human confirmation | No |
| Doors | API, Screens, Apps |
| Status | Available |
Input
{}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Hides one recommended Store product for the brand («not interested»), or brings it back with dismissed: false.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.recommendations.dismiss |
| MCP tool | brand_recommendations_dismiss |
| Scope | brand.write |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
productId: string // ≤ 120
dismissed: boolean
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Records that the caller opened one Store product in this brand, so the product launcher can sort by recency. Personal; never visible to others.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.product_visits.record |
| MCP tool | brand_product_visits_record |
| Scope | brand.write |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Screens, Apps |
| Status | Available |
Input
{
productId: string // ≤ 160
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}The ids of the brand items the caller marked as favourites, of one entityType (product, work, file…), newest first; with entityIds, only which of those are favourites.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.favorites.list |
| MCP tool | brand_favorites_list |
| Scope | brand.read |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
entityType: string // ≤ 60
entityIds?: string[] // ≤ 500 items
}Output
{
ids: string[]
}Marks one item of the brand (a file, a work…) as the caller's own favourite, or removes it when it already was. Returns the new state.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.favorites.toggle |
| MCP tool | brand_favorites_toggle |
| Scope | brand.write |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Screens, Apps |
| Status | Available |
Input
{
entityType: string // ≤ 60
entityId: string // ≤ 200
}Output
{
isFavorite: boolean
}Marks or unmarks up to 200 items of the brand as the caller's favourites in one write. Returns the ids that changed.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.favorites.set |
| MCP tool | brand_favorites_set |
| Scope | brand.write |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | Screens, Apps |
| Status | Available |
Input
{
entityType: string // ≤ 60
entityIds: string[] // ≤ 200 items
favorite: boolean
}Output
{
changed: string[]
}Creates one product or service of the brand with its price, features and cover. The cover is a Library file of the same brand; its URL is taken from the file, never from the caller.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.offerings.create |
| MCP tool | brand_offerings_create |
| Scope | brand.write |
| Minimum role | Member |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
title: string // ≤ 500
description?: string | null
short_description?: string | null
category?: string | null
category_id?: string | null
type: "product" | "service"
status?: string // ≤ 40
price?: number | null
currency?: string // ≤ 3
sale_price?: number | null
price_unit?: string // ≤ 40
features?: string[] // ≤ 100 items
sku?: string | null
is_recurring?: boolean
recurring_interval?: "weekly" | "monthly" | "quarterly" | "annual" | null
sort_order?: integer // -9007199254740991–9007199254740991
image_asset_id?: string | null
}Output
{
offering: {
id: string // uuid
brand_id: string // uuid
}
}Changes the given fields of one brand offering. The cover URL is rebuilt from image_asset_id; a category must belong to the same brand.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.offerings.update |
| MCP tool | brand_offerings_update |
| Scope | brand.write |
| Minimum role | Member |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
offeringId: string // uuid
changes: {
title?: string // ≤ 500
description?: string | null
short_description?: string | null
category?: string | null
category_id?: string | null
type?: "product" | "service"
status?: string // ≤ 40
price?: number | null
currency?: string // ≤ 3
sale_price?: number | null
price_unit?: string // ≤ 40
features?: string[] // ≤ 100 items
sku?: string | null
is_recurring?: boolean
recurring_interval?: "weekly" | "monthly" | "quarterly" | "annual" | null
sort_order?: integer // -9007199254740991–9007199254740991
image_asset_id?: string | null
}
}Output
{
offering: {
id: string // uuid
brand_id: string // uuid
}
}Deletes one brand offering and its images for good. There is no undo.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.offerings.delete |
| MCP tool | brand_offerings_delete |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, Screens, Apps |
| Status | Available |
Input
{
offeringId: string // uuid
}Output
{
ok: true
id?: string
revision?: integer // -9007199254740991–9007199254740991
}Adds a Library file of the same brand to an offering gallery (at the end unless sortOrder is given). The first image also becomes the cover.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.offerings.add_image |
| MCP tool | brand_offerings_add_image |
| Scope | brand.write |
| Minimum role | Member |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
offeringId: string // uuid
assetId: string // uuid
sortOrder?: integer // -9007199254740991–9007199254740991
}Output
{
image: {
id: string // uuid
}
}Turns Nödo's periodic check-ins for the brand on or off and picks their local hour (one of the allowed hours). Returns the stored settings.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.heartbeat.update |
| MCP tool | brand_heartbeat_update |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
enabled: boolean
localHour: integer // 0–23
}Output
{
enabled: boolean
localHour: integer // -9007199254740991–9007199254740991
}Names a brand, the way «Ya tengo un nombre» does on the domain step: the Genome first, then the operational row, and a brand created without a name gets its identity steps back. 2 to 80 characters; the placeholder code is refused (invalid_name).
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.name.set |
| MCP tool | brand_name_set |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
name: string // ≤ 200
}Output
{
name: string
}Sets the brand up without a name yet: it carries a working code instead (its short id, unique and neutral) and the profile answers what that implies (no website yet, a brand that is starting). The identity steps return once the brand is named.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.start_unnamed |
| MCP tool | brand_setup_start_unnamed |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
name: string
naming: {
status: string
code: string
since: string | null
namedAt: string | null
}
}Skips the setup's logo step: logos found on the website stay in the Library as proposals, leave the Brand Kit and the Genome, and the brand logo is cleared when it only came from them.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.logo_skip |
| MCP tool | brand_setup_logo_skip |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
ok: true
}Starts reading the logo, colours and typefaces of the brand's website as soon as it is typed, so they are ready by the time the setup reaches them. Completing the profile joins this run instead of starting another.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.extraction_start |
| MCP tool | brand_setup_extraction_start |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
website: string // ≤ 200
}Output
{
ok: true
}Reads the website's home page (public addresses only, no credentials) and says whether it has content of its own and, when it does, what the brand does in one or two sentences (summary), so the setup does not ask for the description again. A parked, for-sale or under-construction page does not count. Saves nothing.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.setup.website_read |
| MCP tool | brand_setup_website_read |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
website: string // ≤ 200
}Output
{
readable: boolean
summary: string | null
}Records in the setup that the logo is chosen, after the person uploaded their own file (the upload already made it the brand logo). Changes nothing else.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.logo_accept |
| MCP tool | brand_setup_logo_accept |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
ok: true
}Confirms one logo of the Library (assetId, a logo or icon) as the brand logo and records the setup decision. A candidate proposed from the website becomes approved. Also needs brand.kit.manage.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.logo_choose |
| MCP tool | brand_setup_logo_choose |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
assetId: string // uuid
}Output
{
ok: true
}Publishes in the Kit only the extracted colours the person ticked (colorIds; all of them when absent) and keeps the rest out of the Kit and the Genome without deleting them. Colours added by hand are never withdrawn. skipped withdraws every extracted one.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.colors_decide |
| MCP tool | brand_setup_colors_decide |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
decision: "accepted" | "skipped"
colorIds?: string[] // ≤ 48 items
}Output
{
ok: true
}Publishes the confirmed typefaces in the Kit (up to 3): a Google family with every weight Google serves; one of the brand's own with the files copied from its website when acceptSiteFiles, otherwise by name. Families the Kit already has are left alone.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.typography_decide |
| MCP tool | brand_setup_typography_decide |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
decision: "accepted" | "skipped"
fonts?: Array<{
family: string // ≤ 80
role: "heading" | "body"
acceptSiteFiles?: boolean
}> // ≤ 3 items · default []
}Output
{
ok: true
}Starts writing the brand's Genome (Brand DNA) from what was read and answered. A run that stopped moving is closed first. Blocking brand questions refuse it with questions_required.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.genome_start |
| MCP tool | brand_setup_genome_start |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
ok: true
}Restarts the brand analysis from its first unfinished document (research, then wizard cards) without duplicating a live run; a run that stopped moving is closed first. Once a minute (cooldown). Trashed or closing brands start nothing.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.analysis_retry |
| MCP tool | brand_setup_analysis_retry |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
ok: true
}Remembers that the setup window (flow), the panel card (card) or the completed-setup moment (completion) was dismissed at this checkpoint, so it does not come back until something changes.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.dismiss |
| MCP tool | brand_setup_dismiss |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
checkpoint: string // ≤ 60
target?: "flow" | "card" | "completion" // default "flow"
}Output
{
ok: true
}Nödo drafts the brand description from the saved profile, the naming state and the files attached to the setup, with what it read to write it (sources). without leaves files out (the keys of their source chips). Saves nothing.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.setup.description_draft |
| MCP tool | brand_setup_description_draft |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
description: string // ≤ 4000
without?: string[] // ≤ 12 items · default []
}Output
{
draft: string
sources: Array<{
key: string
}>
}Leaves one file out of what Nödo reads for the brand description and the research. The file stays in the Library.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.attachment_remove |
| MCP tool | brand_setup_attachment_remove |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
assetId: string // uuid
}Output
{
ok: true
}Sends every answer of the brand wizard to the pipeline that writes the Genome (Brand DNA) in the background. Blocking brand questions refuse it with brand_questions_require_answers.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.wizard.feedback_submit |
| MCP tool | brand_wizard_feedback_submit |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
wizardSession?: integer // 1–100 · default 1
}Output
{
ok: true
}Queues the run that writes the brand wizard's question cards from its research, in the background. A repeat within five minutes is not queued twice. Returns the queued run id.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.wizard.cards_generate |
| MCP tool | brand_wizard_cards_generate |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
ok: true
runId: string
}Trashes (soft delete) every half-made onboarding brand the signed-in person created, cancels the runs they started and says where to land (/brands with an organization, /account otherwise). One that cannot be abandoned does not stop the others (failed).
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.onboarding.abandon |
| MCP tool | brand_onboarding_abandon |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, Screens, Apps |
| Status | Available |
Input
{}Output
{
destination: "/brands" | "/account"
failed: integer // -9007199254740991–9007199254740991
}The brand's general settings as the apps draw them: identity (name, short id, logo), country, city, communication language and version (send it back as expectedVersion to brand.settings.update), the languages to choose from, what the reader may do there and the counts of members, connections and trashed files.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.settings.read |
| MCP tool | brand_settings_read |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{}Output
{
brand: {
id: string
shortId: string | null
name: string
logoAssetId: string | null
logoUrl: string | null
country: string | null
city: string | null
version: integer | null
updatedAt: string | null
communicationLanguage: string | null
}
permissions: {
canEditSettings: boolean
canViewTeam: boolean
canManageTeam: boolean
canViewConnections: boolean
canCloseBrand: boolean
}
communicationLanguages: Array<{
code: string
name: string
nativeName: string | null
sortOrder: number | null
}>
counts: {
members: integer // -9007199254740991–9007199254740991
connections: integer // -9007199254740991–9007199254740991
trash: integer // -9007199254740991–9007199254740991
}
}Saves the brand's general settings (name, ISO country, city, organization type, languages, age, description) into the Genome when it exists, else the operational row. Send expectedVersion; a brand changed elsewhere answers revision_conflict. Only a request that sends description changes it.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.settings.update |
| MCP tool | brand_settings_update |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
name: string // ≤ 200
country?: string | null
city?: string | null
description?: string | null
age?: string | null
brandType?: string | null
organizationType?: string | null
defaultLocale?: "es" | "en" | null
communicationLanguage?: string | null
expectedVersion?: integer | null
}Output
{
ok: true
}Makes one file of the brand's Library its logo (assetId); the header, the sidebar and the brand switcher show it. Returns the delivered address.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.logo.set |
| MCP tool | brand_logo_set |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
assetId: string // uuid
}Output
{
url: string
assetId: string
}Sets the brand's time zone, the one its editorial plans, Nödo's heartbeat and the posts composer fall back to. Null clears it. An unknown zone is invalid_input.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.time_zone.set |
| MCP tool | brand_time_zone_set |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{
timeZone: string | null // IANA zone (`Europe/Madrid`), or null to fall back.
}Output
{
timeZone: string | null
}Creates one more provisional brand for the signed-in person, active at once; its profile is answered next with brand.setup.profile_save and brand.setup.profile_complete. With workspaceId (its owner) the brand is born there; newWorkspace starts a workspace of its own; otherwise it goes to the workspace the person owns. An untouched provisional brand of theirs is reused (created: false). Refusals: conflict brand_create_limit_reached, rate_limited brand_create_rate_limited, forbidden staff_account.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.create |
| MCP tool | brand_setup_create |
| Scope | brand.write |
| Minimum role | self |
| Human confirmation | No |
| Doors | API, Screens, Apps |
| Status | Available |
Input
{
workspaceId?: string | null
newWorkspace?: boolean
}Output
{
id: string
shortId: string
name: string
created: boolean // False when an untouched provisional brand of the person was reused.
}The profile answers of a new brand (name, website, age, communication language, country, city, description), the step to resume on and the communication languages to choose from. setup is null when the brand has no profile to answer (it was not created as a provisional brand).
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.setup.profile_read |
| MCP tool | brand_setup_profile_read |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{}Output
{
setup: {
brand: {
id: string
shortId: string
name: string
onboardingStep: integer // -9007199254740991–9007199254740991 · The step to resume on.
hasWebsite: boolean | null
website: string | null
age: string | null // new_brand or existing_brand.
communicationLanguage: string | null
country: string | null
city: string | null
description: string | null
}
profileSetup: {
status: string // required, in_progress or completed.
currentStep: integer // -9007199254740991–9007199254740991
provisionalName: boolean
completedAt: string | null
}
}
| null
languages: Array<{
code: string
name: string
nativeName: string | null
flag: string | null
}> // Communication languages to choose from, in the catalogue order.
}Saves the answers of one profile step of a new brand and the step it moves to (step: 1, 2, 3, 5, 6, 7, 8 or 9). A name becomes the brand's name at once (sidebar, switcher, every open tab). Returns the saved profile. An invalid step or website answers invalid_input.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.profile_save |
| MCP tool | brand_setup_profile_save |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
step: integer // 1–9
age?: "new_brand" | "existing_brand"
city?: string // ≤ 100
communicationLanguage?: string // ≤ 5
country?: string // ≤ 100
description?: string // ≤ 4000
hasWebsite?: boolean
name?: string // ≤ 160
website?: string | null
}Output
{
brand: {
id: string
shortId: string
name: string
onboardingStep: integer // -9007199254740991–9007199254740991 · The step to resume on.
hasWebsite: boolean | null
website: string | null
age: string | null // new_brand or existing_brand.
communicationLanguage: string | null
country: string | null
city: string | null
description: string | null
}
profileSetup: {
status: string // required, in_progress or completed.
currentStep: integer // -9007199254740991–9007199254740991
provisionalName: boolean
completedAt: string | null
}
}Completes a new brand's profile with every answer: starts the research, the brand provisioning and (with a website) the Brand Kit reading, and applies a plan picked before sign-up. pendingCheckout is that plan when it still needs its checkout. The description needs 30 characters for a new brand without website, else 10.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.setup.profile_complete |
| MCP tool | brand_setup_profile_complete |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
age: "new_brand" | "existing_brand"
city: string // ≤ 100
communicationLanguage: string // ≤ 5
country: string // ≤ 100
description: string // ≤ 4000
hasWebsite: boolean
name: string // ≤ 160
website: string | null
}Output
{
brand: {
id: string
shortId: string
name: string
onboardingStep: integer // -9007199254740991–9007199254740991 · The step to resume on.
hasWebsite: boolean | null
website: string | null
age: string | null // new_brand or existing_brand.
communicationLanguage: string | null
country: string | null
city: string | null
description: string | null
}
profileSetup: {
status: string // required, in_progress or completed.
currentStep: integer // -9007199254740991–9007199254740991
provisionalName: boolean
completedAt: string | null
}
pendingCheckout: {
productId: string
interval: "month" | "year"
}
| null
}A brand started in the old onboarding wizard, to finish it: its answers (name, website, age, communication language, country, city, description), the step to resume on and the communication languages. New brands use brand.setup.profile_read.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.onboarding.read |
| MCP tool | brand_onboarding_read |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{}Output
{
brand: {
id: string
shortId: string
name: string
onboardingStep: integer // -9007199254740991–9007199254740991 · The step to resume on.
hasWebsite: boolean | null
website: string | null
age: string | null // new_brand or existing_brand.
communicationLanguage: string | null
country: string | null
city: string | null
description: string | null
}
languages: Array<{
code: string
name: string
nativeName: string | null
flag: string | null
}> // Communication languages to choose from, in the catalogue order.
}Saves one step of a brand started in the old onboarding wizard (step: 2, 3, 4, 5, 6, 7 or 8 of that wizard). A website starts the Brand Kit reading. invalid_input with invalid_step, invalid_age or invalid_website.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.onboarding.step_save |
| MCP tool | brand_onboarding_step_save |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
step: integer // -9007199254740991–9007199254740991
age?: string // ≤ 40
city?: string // ≤ 100
communicationLanguage?: string // ≤ 5
country?: string // ≤ 100
hasWebsite?: boolean
name?: string // ≤ 50
website?: string // ≤ 200
}Output
{
ok: true
}Finishes a brand started in the old onboarding wizard with its age and description: it becomes the person's active brand and its research and provisioning start. platform names the client for the brand's history.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.onboarding.complete |
| MCP tool | brand_onboarding_complete |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
age: string // ≤ 40
description: string // ≤ 4000
platform?: "ios" | "android"
}Output
{
brandId: string
shortId: string
createdAt: string
}Sets what outside agents connected to this brand may write: read_only, approval (each write waits for the person) or direct. null follows the workspace policy.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.agent_policy.set |
| MCP tool | brand_agent_policy_set |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps |
| Status | Available |
Input
{
policy: "read_only" | "approval" | "direct" | null
}Output
{
ok: true
}The brand's description, products and website, as its home shows them, the currency its payer is billed in and, when App Store or Google Play collects the plan, that store with the price it charged.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.summary |
| MCP tool | brand_summary |
| Scope | brand.read |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
description: string | null
products: unknown
website: string | null
billingCurrency: string // default "EUR"
planStore: {
provider: "apple" | "google"
grossMinor: integer | null
currency: string | null
interval: "month" | "year" | null
}
| null // default null
}What a brand may know about its Brand Manager: their public card (name, photo, job title; docs/public-person-card.md), live state (available, break, out of office…) and the public card of who covers while they are out. Never an email, phone, notes or log. presence is null when the brand has none to show.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.manager.presence |
| MCP tool | brand_manager_presence |
| Scope | brand.read |
| Minimum role | Viewer |
| Human confirmation | No |
| Doors | API, MCP, Screens, Apps, Nödo |
| Status | Available |
Input
{}Output
{
presence: Record<string, unknown> | null
}What closing this brand would do right now (the tenancy review): open works and tasks, people, plan and connections, blockers, whether it closes now or when what was paid ends (close_when), and what to type to confirm (requires.typed, the brand name). Changes nothing; pass its fingerprint to brand.close.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.close.review |
| MCP tool | brand_close_review |
| Scope | brand.read |
| Minimum role | Owner |
| Human confirmation | No |
| Doors | Screens, Apps |
| Status | Available |
Input
{}Output
{
version: 1
operation: "account.delete" | "workspace.close" | "workspace.transfer" | "workspace.leave" | "workspace.member.remove" | "brand.close" | "brand.leave" | "brand.member.remove" | "brand.reopen" | "workspace.reopen"
subject: {
type: "account" | "workspace" | "brand"
id: string // uuid
name: string | null
email?: string | null
state?: "active" | "closing" | "closed"
workspace?: {
id: string // uuid
name: string | null
}
}
actor: {
id: string // uuid
role: string | null
}
target: {
id: string // uuid
name: string | null
email: string | null
avatar_url?: string | null
role?: string | null
scope?: string | null
}
| null
allowed: boolean
requires: {
reauth: boolean
typed: string | null
}
blockers: Array<{
code: string
workspace?: {
id: string // uuid
name: string | null
}
}>
decisions: Array<{
code: "workspace_successor"
workspace: {
id: string // uuid
name: string | null
}
options: Array<"transfer" | "close">
close_available: boolean
candidates: Array<{
id: string // uuid
name: string | null
email: string | null
avatar_url?: string | null
role?: string | null
scope?: string | null
}>
default: "transfer" | "close"
}
| {
code: "close_when"
options: Array<"now" | "when_commitments_end">
now_available: boolean
default: "now" | "when_commitments_end"
paid_works: integer // -9007199254740991–9007199254740991
plan_ends_at: string | null
}
| {
code: "new_owner"
candidates: Array<{
id: string // uuid
name: string | null
email: string | null
avatar_url?: string | null
role?: string | null
scope?: string | null
}>
previous_owner_options: Array<"admin" | "leave">
default: "admin" | "leave"
}>
workspaces: Array<{
id: string // uuid
name: string | null
role: string | null
outcome: "leave" | "close" | "decide" | "transfer" | "member_removed" | "stays" | "reopen"
people: Array<{
id: string // uuid
name: string | null
email: string | null
avatar_url?: string | null
role?: string | null
scope?: string | null
}>
brands: Array<{
id: string // uuid
name: string | null
workspace_id: string // uuid
state: "active" | "closing" | "closed"
works_open: integer // -9007199254740991–9007199254740991
tasks_open: integer // -9007199254740991–9007199254740991
tasks_assigned: integer // -9007199254740991–9007199254740991
files: integer // -9007199254740991–9007199254740991
connections: integer // -9007199254740991–9007199254740991
connections_authorized: integer // -9007199254740991–9007199254740991
conversations: integer // -9007199254740991–9007199254740991
invoices: integer // -9007199254740991–9007199254740991
orders: integer // -9007199254740991–9007199254740991
people: Array<{
id: string // uuid
name: string | null
email: string | null
avatar_url?: string | null
role?: string | null
scope?: string | null
}>
commitments: {
paid_works: Array<{
id: string // uuid
name: string | null
status: string
delivery_at: string | null
}>
plan_works: integer // -9007199254740991–9007199254740991
plans: Array<{
id: string
name: string | null
store: "stripe"
status: string
period_end: string | null
renewal_cancelled: boolean
}>
agreements: Array<{
id: string
name: string | null
status: string
}>
unsettled_orders: integer // -9007199254740991–9007199254740991
unsettled_payments: integer // -9007199254740991–9007199254740991
unsettled_quotes: integer // -9007199254740991–9007199254740991
reconciliation: integer // -9007199254740991–9007199254740991
}
| null
}>
access?: "narrowed" | "brand"
billing?: {
name: string | null
tax_id: string | null
}
| null
}>
fingerprint: string
}Closes this brand exactly as reviewed with brand.close.review: it becomes a read-only archive now or when what was paid ends (closeWhen, default the review's). Money records are kept; Nömad staff can reopen it. Owner only, with a sign-in from the last 10 minutes and the typed brand name. Refusals: forbidden with reauthentication_required (sign in again first) or permission_denied; conflict with blocked (the blocker codes after a colon), review_changed, confirmation_mismatch, decision_required, paid_work_pending, successor_required or staff_account; not_found.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.close |
| MCP tool | brand_close |
| Scope | brand.write |
| Minimum role | Owner |
| Human confirmation | Yes |
| Doors | Screens, Apps |
| Status | Available |
Input
{
fingerprint: string // ≤ 64 · The `fingerprint` of the review the person saw; a changed situation answers `conflict` `review_changed`.
confirmation?: string // ≤ 320 · What the person typed when the review asks for it (`requires.typed`).
closeWhen?: "now" | "when_commitments_end"
}Output
{
operation: "account.delete" | "workspace.close" | "workspace.transfer" | "workspace.leave" | "workspace.member.remove" | "brand.close" | "brand.leave" | "brand.member.remove" | "brand.reopen" | "workspace.reopen"
signedOut: boolean // The person no longer has a session (account deleted): end it on the device.
next: "stay" | "workspace" | "brands" | "signed_out" // Where the person lands next.
operationId: string | null
}Everyone who reaches this brand (the workspace owner, all-brands and per-brand workspace grants and direct members), with role and profile, oldest first, and its pending invitations, newest first. currentUserId is the reader. Brand owners and admins read the whole team; others the direct members.
| Field | Value |
|---|---|
| Kind | Read |
| HTTP method | POST /api/v1/brand.team.read |
| MCP tool | brand_team_read |
| Scope | brand.read |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{}Output
{
currentUserId: string
members: Array<{
userId: string
role: string // owner, admin, billing, member, viewer or brand_manager.
since: string | null
profile: {
id: string
fullName: string | null
avatarUrl: string | null
email: string | null
}
| null
}>
invitations: Array<{
id: string
email: string
role: string // owner, admin, billing, member, viewer or brand_manager.
expiresAt: string | null
createdAt: string | null
}>
}Removes one person from this brand, also when their access comes from the workspace (the tenancy brand.member.remove): their open tasks are unassigned and they are told. Passing the caller's own id leaves the brand (brand.leave), which only needs to reach it; removing anyone else needs brand.team.manage. The owner is never removed (conflict protected_member); any other blocker answers conflict remove_blocked with the reason after a colon. Refusals: forbidden with reauthentication_required (sign in again first) or permission_denied; conflict with blocked (the blocker codes after a colon), review_changed, confirmation_mismatch, decision_required, paid_work_pending, successor_required or staff_account; not_found.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.member.remove |
| MCP tool | brand_member_remove |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | Yes |
| Doors | Screens, Apps |
| Status | Available |
Input
{
userId: string // uuid
}Output
{
ok: true
left: boolean // The caller left the brand: it is not theirs to open any more.
}Changes one person's role in this brand (admin, billing, member, viewer) in their workspace brand map, leaving their other brands as they are. Only the workspace owner may (forbidden workspace_owner_required); conflict with protected_member, all_brands_member or direct_brand_owner_requires_transfer for people whose role is decided elsewhere; not_found member_not_found.
| Field | Value |
|---|---|
| Kind | Write |
| HTTP method | POST /api/v1/brand.member.role.set |
| MCP tool | brand_member_role_set |
| Scope | brand.write |
| Minimum role | Admin |
| Human confirmation | No |
| Doors | Screens, Apps, API |
| Status | Available |
Input
{
userId: string // uuid
role: "admin" | "billing" | "member" | "viewer"
}Output
{
userId: string
role: string
changed: boolean
}Updated on January 1, 1970