---
title: "Update business"
method: PATCH
path: "/api/businesses/{business_id}"
tags: ["businesses"]
---

# Update business

`PATCH /api/businesses/{business_id}`

## Request

- Base URL: `https://api.mymarky.ai`
- URL: `https://api.mymarky.ai/api/businesses/{business_id}`
- Auth: HTTP bearer

Update a business. Only provided fields are changed.

Changing caption_suffix, website or logo_url does not rewrite posts that
are already made. `propagation` says how many unpublished posts still carry
the old value and, for text, gives the update_posts call that fixes them.

Errors:
    400 invalid_request: No fields were given to update.
    400 invalid_value: The business_id is not a valid UUID.
    401 authentication_required: API key or token is missing, invalid, or revoked.
    403 permission_denied: Your plan does not include API access, or this business is not in your org.
    404 resource_not_found: No business with that ID exists.
    409 NAME_MATCHES_OTHER_WORKSPACE: Another business in your org already has that name. Nothing was saved. Send allow_duplicate_name=true to rename anyway.
    429 rate_limit_exceeded: You sent too many requests this minute.

## Path parameters

- `business_id` string, required: A UUID identifier.

## Headers

- `Marky-Version` '2026-04-08' | '2026-07-17' | '2026-08-02' | '2026-08-04' | '2026-08-06'

## Request body

- BusinessUpdateRequest
  - `tone` string, nullable: Brand voice — the tone and personality Marky writes captions in (the 'Profile' field in the app).
  - `audience_combined` string, nullable: The target audience the brand writes for — who the posts are meant to reach (the 'Target Audience' field in the app).
  - `caption_writing_rules` string, nullable: Extra rules Marky follows when writing captions. Put each rule on its own line (newline-separated) — do NOT write one long paragraph. The app shows each line as a separate rule chip, so a run-on paragraph renders as one un-editable blob. E.g. 'Never use emojis\nAlways end with a question\nKeep captions under 80 words'.
  - `caption_suffix` string, nullable: Text appended to the end of every generated caption (e.g. a hashtag block or a link).
  - `custom_caption_prompt` string, nullable: A custom instruction injected into caption generation for this business.
  - `custom_meme_prompt` string, nullable: A custom instruction injected into meme generation for this business.
  - `custom_carousel_prompt` string, nullable: A custom instruction injected into carousel generation for this business.
  - `design_style` string, nullable: Free-text/markdown guidance on how this brand's graphics should be COMPOSED — which palette colour leads, how gradients are used, where the logo sits, whitespace, type treatment. Do NOT restate the raw brand tokens here (colours, fonts, logo file); those live in palettes / header_font / body_font / logo_url. This is the judgement the tokens can't express. SCOPE: it is added to the prompt for AI-GENERATED images and sets the look of theme covers. It does NOT reach a template design, so it will not change a batch generated from templates — for those, use palettes / header_font / body_font / logo_url, creative_types and include_stock. SIZE: 5000 characters, and writing this field REPLACES the whole value. Plan to the cap before composing; the response's `field_budgets` entry tells you how much room is left.
  - `brand_style_grid_url` string, nullable: URL of a rendered 2x2 grid of four sample posts, all in ONE of the treatments from the design_style table — the first look the user kept when they ran /brand-setup. Written by /brand-setup at the end of that run; the app shows it so someone can SEE their look instead of reading it.
  - `strategy` string, nullable: Social strategy doc as MARKDOWN — the goal we're working toward, what we're trying right now, what we won't do, cadence and mix, plus a running log of experiments and what we learned. This is the layer an agency revisits each quarter. Write plain markdown (## headings, - bullets, | tables |); the app renders it in a rich-text editor, so no HTML tags — they show up as literal text and spend the size budget without adding meaning. Keep the pillars themselves in topics, the brand voice in tone/audience_combined, and how graphics look in design_style; strategy holds what none of those can express — what we're trying to ACHIEVE and what we've learned. SIZE: 6000 characters, and writing this field REPLACES the whole doc. A longer value is still saved, but it comes back with a warning you have to act on in the same turn — so plan the doc to the cap BEFORE you compose it, rather than writing long and compacting after. The response's `field_budgets` entry for this field tells you exactly how much room is left.
  - `tagline` string, nullable: Brand tagline shown on designs.
  - `ctas` string[], nullable: Call-to-action phrases Marky can use on designs (e.g. 'Call today', 'Book now').
  - `palettes` Palette[], nullable: Brand color palettes used on generated designs.
    - `name` string: Label for this palette
    - `colors` string[]: Hex color codes in the palette, e.g. ['#0d0c0c', '#FFFFFF', '#121212'].
    - `text_color` string, nullable: Hex color used for body text on designs
    - `accent_color` string, nullable: Hex accent/brand color
    - `background_color` string, nullable: Hex background color for designs
  - `header_font` FontSetting
    - `name` string, required: Font family name, e.g. 'Montserrat'. Must be a font Marky can load (a Google Font, or one with a direct url below).
    - `url` string, nullable: Direct font file URL. Leave null for a Google Font (Marky resolves it by name).
  - `body_font` FontSetting
    - `name` string, required: Font family name, e.g. 'Montserrat'. Must be a font Marky can load (a Google Font, or one with a direct url below).
    - `url` string, nullable: Direct font file URL. Leave null for a Google Font (Marky resolves it by name).
  - `logo_url` string, nullable: URL of the brand logo.
  - `logo_background_color` string, nullable: Background color behind the logo on designs (hex, or '#00000000' for transparent).
  - `logo_width` integer, nullable: Logo width in pixels, used when laying the logo onto designs.
  - `name` string, nullable: Business name
  - `description` string, nullable: Business description
  - `industry` string, nullable: Industry
  - `website` string, nullable: Business website URL
  - `language` string, nullable: Content language, as an ISO 639-1 code (e.g. 'en') or English name (e.g. 'English'). Both accepted.
  - `events` BusinessEvent[], nullable: The business's own dated moments: launches, openings, sales, events (e.g. {'name': 'Hope Shop opening', 'date': '2026-10-10', 'kind': 'opening'}). They show up in list_upcoming_moments, and a caption that names one is dated to it. At most 20. REPLACES the whole list: send every event to keep (read them with get_business first), or [] to clear. Undo with revert_revisions.
    - `name` string, required: e.g. 'Hope Shop opening'.
    - `date` string, date, required: The day it happens, YYYY-MM-DD, on the business's calendar.
    - `kind` 'launch' | 'opening' | 'sale' | 'event' | 'other': What sort of moment it is.
    - `note` string, nullable: Optional detail, e.g. '20% off'.
  - `platform_writing_instructions` PlatformWritingInstructions: Per-platform caption-writing instructions, as a FIXED-KEY object. Why a typed object and not `dict[str, str]`: this is an MCP tool argument, and a free-form dict can't be expressed in a strict JSON Schema — the model has to invent both the keys and the value types. That is exactly how we got junk keys in `preferences` (audit-max #271) and a JSON-encoded string in `creative_types` (Sentry FRONTEND-3Y7). Naming every allowed key makes the wrong call impossible to express instead of merely invalid. MERGE-PATCH semantics: omit a platform to leave it alone. Send `""` to CLEAR that platform back to defaults. `""` clears rather than `null`, because under strict JSON Schema every property is required and unspecified ones arrive as explicit `null` — so `null`-clears would wipe every platform the caller didn't mean to touch on every single call (the regression tests/test_api_v1_papercuts.py Bug E exists to prevent). With `""` as the sentinel, both "absent" and `null` mean "don't touch" and there is no ambiguity.
    - `common` string, nullable: Applies to every platform.
    - `facebook` string, nullable
    - `instagram` string, nullable
    - `twitter` string, nullable
    - `linkedIn` string, nullable
    - `linkedInProfile` string, nullable
    - `tiktok` string, nullable
    - `pinterest` string, nullable
    - `googleBusiness` string, nullable
    - `instagramStory` string, nullable
    - `youtube` string, nullable
  - `include_stock` boolean, nullable: Whether generated posts may use stock photography.
  - `creative_types` CreativeTypeWeights: Relative weighting of creative formats, as a FIXED-KEY object. Same reasoning as PlatformWritingInstructions: a free-form dict is not expressible in strict JSON Schema, and this is the exact field where an agent once passed a JSON-encoded *string* and white-screened the workspace. Deliberately NOT strict-mode (every field optional, not required). Under strict mode the model must restate all seven weights on every call, so "add memes to my mix" would force it to re-derive the other six — and 45% of businesses with a mix (3,585 of 7,926) have deliberately non-uniform weights that a wrong guess would silently overwrite. Omitting a format leaves its stored weight alone. Weights are relative, not required to sum to 100 — the generator normalizes. The keys are the FRONTEND's format names, which is what `preferences.creative_types` actually stores (7,903 businesses use `design`, 3,626 use `image-only`). The generation pipeline aliases `image-only` -> its internal `image` format at read time (post_generation/format_selection.py). Writing the internal name here would create a key the media-mix picker can't see — a silent no-op write.
    - `design` integer, nullable
    - `image-only` integer, nullable: Stock or library photo with no design treatment.
    - `ai-image` integer, nullable: AI-generated image.
    - `gif` integer, nullable
    - `meme` integer, nullable
    - `video` integer, nullable
    - `carousel` integer, nullable
  - `aspect_ratio` '1:1' | '4:5' | '9:16', nullable: Default aspect ratio for generated designs.
  - `timezone` string, nullable: The business's IANA timezone, e.g. 'Europe/Warsaw'. Posting-schedule slots and every `*_local` time use it, and holidays in moments come from its country. Existing schedule slots keep the moment they publish at; re-send the posting schedule to set slots on the new clock.
  - `failed_to_publish_enabled` boolean, nullable: Deprecated: has no effect. Email notifications are set per user, in the app under Account > Notifications, not per business.
  - `low_scheduled_enabled` boolean, nullable: Deprecated: has no effect. Email notifications are set per user, in the app under Account > Notifications, not per business.
  - `post_published_enabled` boolean, nullable: Deprecated: has no effect. Email notifications are set per user, in the app under Account > Notifications, not per business.
  - `post_stats_digest_enabled` boolean, nullable: Deprecated: has no effect. Email notifications are set per user, in the app under Account > Notifications, not per business.
  - `allow_duplicate_name` boolean: Set true to rename this business to a name another business in your org already has. Without it that rename is refused with 409 NAME_MATCHES_OTHER_WORKSPACE, because it usually means the content belongs in that other business instead.

## Response `200`

Successful Response

- BusinessUpdateResponse: What update_business returns: the business, plus how far the change reaches.
  - `tone` string, nullable: Brand voice — the tone and personality Marky writes captions in (the 'Profile' field in the app).
  - `audience_combined` string, nullable: The target audience the brand writes for — who the posts are meant to reach (the 'Target Audience' field in the app).
  - `caption_writing_rules` string, nullable: Extra rules Marky follows when writing captions. Put each rule on its own line (newline-separated) — do NOT write one long paragraph. The app shows each line as a separate rule chip, so a run-on paragraph renders as one un-editable blob. E.g. 'Never use emojis\nAlways end with a question\nKeep captions under 80 words'.
  - `caption_suffix` string, nullable: Text appended to the end of every generated caption (e.g. a hashtag block or a link).
  - `custom_caption_prompt` string, nullable: A custom instruction injected into caption generation for this business.
  - `custom_meme_prompt` string, nullable: A custom instruction injected into meme generation for this business.
  - `custom_carousel_prompt` string, nullable: A custom instruction injected into carousel generation for this business.
  - `design_style` string, nullable: Free-text/markdown guidance on how this brand's graphics should be COMPOSED — which palette colour leads, how gradients are used, where the logo sits, whitespace, type treatment. Do NOT restate the raw brand tokens here (colours, fonts, logo file); those live in palettes / header_font / body_font / logo_url. This is the judgement the tokens can't express. SCOPE: it is added to the prompt for AI-GENERATED images and sets the look of theme covers. It does NOT reach a template design, so it will not change a batch generated from templates — for those, use palettes / header_font / body_font / logo_url, creative_types and include_stock. SIZE: 5000 characters, and writing this field REPLACES the whole value. Plan to the cap before composing; the response's `field_budgets` entry tells you how much room is left.
  - `brand_style_grid_url` string, nullable: URL of a rendered 2x2 grid of four sample posts, all in ONE of the treatments from the design_style table — the first look the user kept when they ran /brand-setup. Written by /brand-setup at the end of that run; the app shows it so someone can SEE their look instead of reading it.
  - `strategy` string, nullable: Social strategy doc as MARKDOWN — the goal we're working toward, what we're trying right now, what we won't do, cadence and mix, plus a running log of experiments and what we learned. This is the layer an agency revisits each quarter. Write plain markdown (## headings, - bullets, | tables |); the app renders it in a rich-text editor, so no HTML tags — they show up as literal text and spend the size budget without adding meaning. Keep the pillars themselves in topics, the brand voice in tone/audience_combined, and how graphics look in design_style; strategy holds what none of those can express — what we're trying to ACHIEVE and what we've learned. SIZE: 6000 characters, and writing this field REPLACES the whole doc. A longer value is still saved, but it comes back with a warning you have to act on in the same turn — so plan the doc to the cap BEFORE you compose it, rather than writing long and compacting after. The response's `field_budgets` entry for this field tells you exactly how much room is left.
  - `tagline` string, nullable: Brand tagline shown on designs.
  - `ctas` string[], nullable: Call-to-action phrases Marky can use on designs (e.g. 'Call today', 'Book now').
  - `palettes` Palette[], nullable: Brand color palettes used on generated designs.
    - `name` string: Label for this palette
    - `colors` string[]: Hex color codes in the palette, e.g. ['#0d0c0c', '#FFFFFF', '#121212'].
    - `text_color` string, nullable: Hex color used for body text on designs
    - `accent_color` string, nullable: Hex accent/brand color
    - `background_color` string, nullable: Hex background color for designs
  - `header_font` FontSetting
    - `name` string, required: Font family name, e.g. 'Montserrat'. Must be a font Marky can load (a Google Font, or one with a direct url below).
    - `url` string, nullable: Direct font file URL. Leave null for a Google Font (Marky resolves it by name).
  - `body_font` FontSetting
    - `name` string, required: Font family name, e.g. 'Montserrat'. Must be a font Marky can load (a Google Font, or one with a direct url below).
    - `url` string, nullable: Direct font file URL. Leave null for a Google Font (Marky resolves it by name).
  - `logo_url` string, nullable: URL of the brand logo.
  - `logo_background_color` string, nullable: Background color behind the logo on designs (hex, or '#00000000' for transparent).
  - `logo_width` integer, nullable: Logo width in pixels, used when laying the logo onto designs.
  - `id` string, required: Business ID
  - `name` string, nullable: Business name
  - `description` string, nullable: Business description
  - `industry` string, nullable: Industry category
  - `website` string, nullable: Business website URL
  - `language` string: Content language as an ISO 639-1 code (e.g. 'en', 'fr', 'es').
  - `platform_writing_instructions` object, nullable: The user's customized per-platform caption-writing instructions (empty when none set). Keys are canonical platform names (e.g. 'instagram', 'linkedIn') plus 'common' (applies to all platforms). Follow the matching entry when writing a caption or platform_overrides caption — the user's instructions ALWAYS outrank generic platform best practices. Update via PATCH (merge semantics). Returned by get_business only, not the list endpoint.
  - `events` BusinessEvent[]: The business's own dated moments: launches, openings, sales, events (e.g. {'name': 'Hope Shop opening', 'date': '2026-10-10', 'kind': 'opening'}). They show up in list_upcoming_moments, and a caption that names one is dated to it. At most 20.
    - `name` string, required: e.g. 'Hope Shop opening'.
    - `date` string, date, required: The day it happens, YYYY-MM-DD, on the business's calendar.
    - `kind` 'launch' | 'opening' | 'sale' | 'event' | 'other': What sort of moment it is.
    - `note` string, nullable: Optional detail, e.g. '20% off'.
  - `include_stock` boolean: Whether generated posts may use stock photos. True when never set, because stock is allowed by default. Set with update_business.
  - `aspect_ratio` string, nullable: Default aspect ratio for generated designs ('1:1', '4:5' or '9:16'). Null when never set. Set with update_business.
  - `creative_types` object: Stored content-mix weights per creative format (keys: design, image-only, ai-image, gif, meme, video, carousel). Empty when never set, and generation then uses the default mix. Set with update_business.
  - `timezone` string, nullable: The business's IANA timezone (e.g. 'Europe/Warsaw'). Posting-schedule slots and `*_local` times use it, and holidays in moments come from its country. Null when unset: times are then UTC and holidays default to the US. Set with update_business.
  - `created_at` string, required: When the business was created
  - `updated_at` string, nullable: Last update time
  - `revision_id` string, nullable: Id of the undo record this write created. Pass it to revert_revisions to put the record back exactly as it was before this call. Set only on the response to a write; null when you read the record.
  - `logo_source_size` LogoSourceSize: The width and height of a logo file as it was given to us, before squaring.
    - `width` integer, required: Width in px of the logo file you sent
    - `height` integer, required: Height in px of the logo file you sent
  - `propagation` BrandPropagation[]: One entry per changed caption_suffix, website or logo_url that unpublished posts still carry. affected_unpublished says how many. For text, `suggested` is the exact update_posts call that fixes them: tell the user the count and offer it. Empty when nothing needs follow-up.
    - `field` 'caption_suffix' | 'website' | 'logo_url', required
    - `old` string, required
    - `new` string, nullable, required
    - `affected_unpublished` integer, required: Unpublished posts that still carry the old value.
    - `requires` 'caption_replace' | 'rerender', required
    - `suggested` SuggestedRule: An `update_posts` request body, handed back verbatim: "call it with exactly this".
      - `match` SuggestedMatch, required
        - `statuses` string[], required
      - `set` SuggestedSet, required
        - `caption_replace` SuggestedReplace, required
          - `find` string, required
          - `replace` string, required
  - `warnings` FieldWarning[], required: Non-blocking notes about fields that need attention, empty when there is nothing to do. Act on them in the same turn you see them — that is while you still have the content in context.
    - `field` string, required: The field this warning is about, e.g. 'strategy', 'caption' or 'platform_overrides[0].caption'.
    - `code` string, nullable: Stable machine-readable code, e.g. 'cta_without_contact'. Branch on this, never on the message. Null on the older size-budget warnings.
    - `level` 'approaching_limit' | 'over_limit' | 'advisory', required: 'approaching_limit' = past the soft limit, act soon. 'over_limit' = past the hard limit, act now. 'advisory' = worth fixing before this publishes, but it may be intended.
    - `message` string, required: Plain-language explanation of what is wrong and how to fix it.
  - `field_budgets` FieldBudget[], required: Size ceilings for the fields that get pasted into the prompt for every generated post. READ THIS BEFORE COMPOSING a value for one of these fields — `remaining` is the room you have, and a budgeted field is REPLACED whole on write, so `limit` is the real target. Over-budget writes are accepted, not rejected, but they cost a full rewrite to fix.
    - `field` string, required: The field this budget applies to.
    - `limit` integer, required: Hard ceiling, in characters.
    - `used` integer, required: Length of the field's current value, 0 when unset.
    - `remaining` integer, required: Characters left before the hard limit, floored at 0. This is the room a REPLACEMENT value has, since writing a budgeted field replaces it whole.

## Other responses

- `400`: Bad request — the input was malformed or otherwise invalid.
- `401`: Authentication failed — the API key is missing or invalid.
- `403`: Forbidden — the key is valid but may not access this resource.
- `404`: Not found — no resource matches the id in the path.
- `422`: Validation error — one or more fields failed validation.
- `429`: Rate limited — retry after the window resets (see Retry-After).
- `500`: Server error — something failed on our side; retry or quote X-Request-Id.

---

[API](https://skmtc.dev/mymarky/apis/marky-public-api.md) · [All operations](https://skmtc.dev/mymarky/apis/marky-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mymarky/apis/marky-public-api/revisions/25aeacae455c?raw)
