---
title: "Set the home-screen / favicon icon (emoji + bg)"
method: PUT
path: "/app/{id}/icon"
---

# Set the home-screen / favicon icon (emoji + bg)

`PUT /app/{id}/icon`

## Request

- Base URL: `https://charm.ing`
- URL: `https://charm.ing/app/{id}/icon`
- Auth: HTTP bearer

Owner-only. Replaces the icon as a unit — both `emoji` and `bg` are required; there is no partial / PATCH semantics. Stricter than `PUT /app/{id}`: invalid `emoji` or `bg` is rejected with 400 `invalid_icon` rather than silently dropped and folded into `warnings[]` (that warn-and-drop contract is for agent publishes, where a typo must never fail). Last-writer-wins with a near-simultaneous agent publish; the rendered icon SVG / PNG caches revalidate on next read via their content-derived ETag.

## Request body

- IconStrict: Home-screen / favicon icon for `PUT /app/{id}/icon`. Same shape as `Icon`, but validated strictly: an invalid `emoji` or `bg` is rejected with 400 `invalid_icon` rather than silently dropped (the dashboard icon picker needs a hard rejection; `Icon`'s warn-and-drop contract is for agent publishes, where a typo must never fail).
  - `emoji` string, required: A single emoji, rendered centered (e.g. `⚽`). Must contain at least one grapheme, or the request is rejected with 400 `invalid_icon`. A value with more than one grapheme (e.g. `⚽⚾`) is accepted, not rejected — only the first grapheme renders and the rest are silently dropped, the same truncation `Icon.emoji` discloses (`Extra glyphs are dropped.`); this endpoint is strict about invalid input, not about single-glyph input.
  - `bg` string, required: Background color as a hex string (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Named colors (`"green"`), `rgb()`/`hsl()`, and image URLs are rejected.

## Response `200`

Icon updated; the response echoes the normalized icon.

- object
  - `icon` IconStrict, required: Home-screen / favicon icon for `PUT /app/{id}/icon`. Same shape as `Icon`, but validated strictly: an invalid `emoji` or `bg` is rejected with 400 `invalid_icon` rather than silently dropped (the dashboard icon picker needs a hard rejection; `Icon`'s warn-and-drop contract is for agent publishes, where a typo must never fail).
    - `emoji` string, required: A single emoji, rendered centered (e.g. `⚽`). Must contain at least one grapheme, or the request is rejected with 400 `invalid_icon`. A value with more than one grapheme (e.g. `⚽⚾`) is accepted, not rejected — only the first grapheme renders and the rest are silently dropped, the same truncation `Icon.emoji` discloses (`Extra glyphs are dropped.`); this endpoint is strict about invalid input, not about single-glyph input.
    - `bg` string, required: Background color as a hex string (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Named colors (`"green"`), `rgb()`/`hsl()`, and image URLs are rejected.

## Other responses

- `400`: Body is not `{ emoji: string, bg: string }` (`invalid_request`), or `emoji`/`bg` failed the icon validators (`invalid_icon`).
- `401`: User-scoped auth required.
- `403`: Caller does not own this app, or the app is anon-owned.
- `404`: App not found.

---

[API](https://skmtc.dev/charm/apis/charming-api.md) · [All operations](https://skmtc.dev/charm/apis/charming-api/llms.txt) · [OpenAPI document](https://skmtc.dev/charm/apis/charming-api/revisions/a9d06c419b3a?raw)
