> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withpersona.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withpersona.com/_mcp/server.

# Create a Theme Set

POST https://api.withpersona.com/api/v1/theme-sets
Content-Type: application/json

Creates a new theme set. The `data` attribute carries the nested theme tree (`settings` / `light` / `dark`) — the same shape as the Code-First theme-set YAML. Style cells that are not supported are rejected with a 400 naming each offending path.

Reference: https://docs.withpersona.com/api-reference/theme-sets/create-a-theme-set

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Request

### Query parameters

- `fields` (map from string to string, optional) — Comma-separated list(s) of attributes to include in the response. This can be used to customize which attributes will be serialized in the response. See [Serialization](https://docs.withpersona.com/serialization#sparse-fieldsets) for more details.

### Headers

- `Key-Inflection` (enum, optional) — Determines casing for the API response.
  - Allowed values: `camel`, `kebab`, `snake`
- `Idempotency-Key` (string, optional) — Ensures the request is idempotent.
- `Persona-Version` (enum, optional) — Server API version. More info on versioning can be found [here](https://docs.withpersona.com/versioning).
  - Allowed values: `2025-12-08`, `2025-10-27`, `2023-01-05`, `2022-09-01`, `2021-08-18`, `2021-07-05`, `2021-02-21`, `2020-05-18`

### Body (application/json)

This endpoint expects an object.

- `data` (ThemeSetsPostRequestBodyContentApplicationJsonSchemaData, required)

## Response

### 201

This endpoint returns a Theme Set object.

- `data` (theme-set, required) — A Theme Set object

## Errors

### 400 Bad Request Error

The request was unacceptable, often due to invalid parameters.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

### 401 Unauthorized Error

An invalid API key was provided.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

### 403 Forbidden Error

The given API key doesn’t have permissions to perform the request or a quota has been exceeded.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

### 409 Conflict Error

The request conflicts with another request, often due to attempting to create a duplicate resource.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

### 422 Unprocessable Entity Error

The request modifies the resource in an unacceptable way, often due to an invalid action or parameter.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

### 429 Too Many Requests Error

Your organization’s rate limit has been exceeded. We recommend an exponential backoff on requests.

- `errors` (list of ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems, optional)

## Types

### ThemeSetsPostRequestBodyContentApplicationJsonSchemaData

- `attributes` (ThemeSetsPostRequestBodyContentApplicationJsonSchemaDataAttributes, required)

### theme-set

A Theme Set object

- `type` ("theme-set", optional)
- `id` (string, optional) — Unique identifier of the theme set (starts with "theset_").
- `attributes` (ThemeSetAttributes, optional)

### ThemeSetsPostResponsesContentApplicationJsonSchemaErrorsItems

- `title` (string, optional)
- `details` (string, optional)

### ThemeSetsPostRequestBodyContentApplicationJsonSchemaDataAttributes

- `name` (string, required) — Display name of the theme set.
- `styles` (theme-set-styles, optional) — Nested theme tree. Sparse — any omitted cell falls back to the Persona default cascade. `dark` mirrors the structure of `light` and is fully optional. Requests carrying non-permitted or mistyped cells at any depth are rejected with a 400 naming each offending path; values that are structurally valid but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.

### ThemeSetAttributes

- `name` (string, optional) — Display name of the theme set.
- `status` (string, optional) — Lifecycle status — one of "active" or "archived". Archived theme sets are retained but no longer editable.
- `styles` (theme-set-styles, optional) — Nested theme tree. Sparse — any omitted cell falls back to the Persona default cascade. `dark` mirrors the structure of `light` and is fully optional. Requests carrying non-permitted or mistyped cells at any depth are rejected with a 400 naming each offending path; values that are structurally valid but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.
- `created-at` (datetime, optional)
- `updated-at` (datetime, optional)
- `archived-at` (datetime, optional, nullable) — Set when the theme set is archived.

### theme-set-styles

Nested theme tree. Sparse — any omitted cell falls back to the Persona default cascade. `dark` mirrors the structure of `light` and is fully optional. Requests carrying non-permitted or mistyped cells at any depth are rejected with a 400 naming each offending path; values that are structurally valid but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.

- `settings` (ThemeSetStylesSettings, optional)
- `light` (theme-set-variant, optional) — One theme variant: `inquiry` holds flow chrome (navbar, modal, hosted flow), `components` holds the per-component style grid (component → variant → state → properties). Value leaves are typed objects such as `{ unit: hex, value: "#111111" }` (color), `{ unit: px, value: 16 }` (size), `{ x: ..., y: ... }` (dimensions) and `{ bitmask: 15 }` (corner mask). The accepted cells are exactly those of the Code-First theme-set YAML shape; a request containing a non-permitted or mistyped cell at any depth is rejected with a 400 naming each offending path. Values that pass the structural check but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.
- `dark` (theme-set-variant, optional) — One theme variant: `inquiry` holds flow chrome (navbar, modal, hosted flow), `components` holds the per-component style grid (component → variant → state → properties). Value leaves are typed objects such as `{ unit: hex, value: "#111111" }` (color), `{ unit: px, value: 16 }` (size), `{ x: ..., y: ... }` (dimensions) and `{ bitmask: 15 }` (corner mask). The accepted cells are exactly those of the Code-First theme-set YAML shape; a request containing a non-permitted or mistyped cell at any depth is rejected with a 400 naming each offending path. Values that pass the structural check but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.

### ThemeSetStylesSettings

- `use-dark-theme` (boolean, optional) — Whether the dark variant is served to devices that prefer dark mode.
- `preferred-theme-variant` (string, optional) — Variant served when no device preference applies — one of "light" or "dark".

### theme-set-variant

One theme variant: `inquiry` holds flow chrome (navbar, modal, hosted flow), `components` holds the per-component style grid (component → variant → state → properties). Value leaves are typed objects such as `{ unit: hex, value: "#111111" }` (color), `{ unit: px, value: 16 }` (size), `{ x: ..., y: ... }` (dimensions) and `{ bitmask: 15 }` (corner mask). The accepted cells are exactly those of the Code-First theme-set YAML shape; a request containing a non-permitted or mistyped cell at any depth is rejected with a 400 naming each offending path. Values that pass the structural check but cannot be reconciled into the stored theme (for example, shared button properties that disagree between the primary and secondary variants) are rejected with a 422.

- `inquiry` (map from string to any, optional)
- `components` (map from string to any, optional)
- `step-assets` (map from string to any, optional)

## Examples

**Request**

```json
{
  "data": {
    "attributes": {
      "name": "Brand Default",
      "styles": {
        "settings": {
          "use-dark-theme": false,
          "preferred-theme-variant": "light"
        },
        "light": {
          "inquiry": {
            "primary-color": {
              "unit": "hex",
              "value": "#111111"
            }
          },
          "components": {
            "button": {
              "primary": {
                "base": {
                  "background-color": {
                    "unit": "hex",
                    "value": "#111111"
                  },
                  "text-color": {
                    "unit": "hex",
                    "value": "#FFFFFF"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

**Response**

```json
{
  "data": {
    "type": "theme-set",
    "id": "theset_ABC123",
    "attributes": {
      "name": "Brand Default",
      "status": "active",
      "styles": {
        "settings": {
          "use-dark-theme": false,
          "preferred-theme-variant": "light"
        },
        "light": {
          "inquiry": {
            "primary-color": {
              "unit": "hex",
              "value": "#111111"
            }
          },
          "components": {
            "button": {
              "primary": {
                "base": {
                  "background-color": {
                    "unit": "hex",
                    "value": "#111111"
                  },
                  "text-color": {
                    "unit": "hex",
                    "value": "#FFFFFF"
                  }
                }
              }
            }
          }
        }
      },
      "created-at": "2026-01-01T00:00:00.000Z",
      "updated-at": "2026-01-01T00:00:00.000Z",
      "archived-at": null
    }
  }
}
```

**SDK Code**

```python Created
import requests

url = "https://api.withpersona.com/api/v1/theme-sets"

payload = { "data": { "attributes": {
            "name": "Brand Default",
            "styles": {
                "settings": {
                    "use-dark-theme": False,
                    "preferred-theme-variant": "light"
                },
                "light": {
                    "inquiry": { "primary-color": {
                            "unit": "hex",
                            "value": "#111111"
                        } },
                    "components": { "button": { "primary": { "base": {
                                    "background-color": {
                                        "unit": "hex",
                                        "value": "#111111"
                                    },
                                    "text-color": {
                                        "unit": "hex",
                                        "value": "#FFFFFF"
                                    }
                                } } } }
                }
            }
        } } }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Created
const url = 'https://api.withpersona.com/api/v1/theme-sets';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"data":{"attributes":{"name":"Brand Default","styles":{"settings":{"use-dark-theme":false,"preferred-theme-variant":"light"},"light":{"inquiry":{"primary-color":{"unit":"hex","value":"#111111"}},"components":{"button":{"primary":{"base":{"background-color":{"unit":"hex","value":"#111111"},"text-color":{"unit":"hex","value":"#FFFFFF"}}}}}}}}}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Created
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.withpersona.com/api/v1/theme-sets"

	payload := strings.NewReader("{\n  \"data\": {\n    \"attributes\": {\n      \"name\": \"Brand Default\",\n      \"styles\": {\n        \"settings\": {\n          \"use-dark-theme\": false,\n          \"preferred-theme-variant\": \"light\"\n        },\n        \"light\": {\n          \"inquiry\": {\n            \"primary-color\": {\n              \"unit\": \"hex\",\n              \"value\": \"#111111\"\n            }\n          },\n          \"components\": {\n            \"button\": {\n              \"primary\": {\n                \"base\": {\n                  \"background-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#111111\"\n                  },\n                  \"text-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#FFFFFF\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Created
require 'uri'
require 'net/http'

url = URI("https://api.withpersona.com/api/v1/theme-sets")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"data\": {\n    \"attributes\": {\n      \"name\": \"Brand Default\",\n      \"styles\": {\n        \"settings\": {\n          \"use-dark-theme\": false,\n          \"preferred-theme-variant\": \"light\"\n        },\n        \"light\": {\n          \"inquiry\": {\n            \"primary-color\": {\n              \"unit\": \"hex\",\n              \"value\": \"#111111\"\n            }\n          },\n          \"components\": {\n            \"button\": {\n              \"primary\": {\n                \"base\": {\n                  \"background-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#111111\"\n                  },\n                  \"text-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#FFFFFF\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Created
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.withpersona.com/api/v1/theme-sets")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"data\": {\n    \"attributes\": {\n      \"name\": \"Brand Default\",\n      \"styles\": {\n        \"settings\": {\n          \"use-dark-theme\": false,\n          \"preferred-theme-variant\": \"light\"\n        },\n        \"light\": {\n          \"inquiry\": {\n            \"primary-color\": {\n              \"unit\": \"hex\",\n              \"value\": \"#111111\"\n            }\n          },\n          \"components\": {\n            \"button\": {\n              \"primary\": {\n                \"base\": {\n                  \"background-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#111111\"\n                  },\n                  \"text-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#FFFFFF\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}")
  .asString();
```

```php Created
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.withpersona.com/api/v1/theme-sets', [
  'body' => '{
  "data": {
    "attributes": {
      "name": "Brand Default",
      "styles": {
        "settings": {
          "use-dark-theme": false,
          "preferred-theme-variant": "light"
        },
        "light": {
          "inquiry": {
            "primary-color": {
              "unit": "hex",
              "value": "#111111"
            }
          },
          "components": {
            "button": {
              "primary": {
                "base": {
                  "background-color": {
                    "unit": "hex",
                    "value": "#111111"
                  },
                  "text-color": {
                    "unit": "hex",
                    "value": "#FFFFFF"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Created
using RestSharp;

var client = new RestClient("https://api.withpersona.com/api/v1/theme-sets");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"data\": {\n    \"attributes\": {\n      \"name\": \"Brand Default\",\n      \"styles\": {\n        \"settings\": {\n          \"use-dark-theme\": false,\n          \"preferred-theme-variant\": \"light\"\n        },\n        \"light\": {\n          \"inquiry\": {\n            \"primary-color\": {\n              \"unit\": \"hex\",\n              \"value\": \"#111111\"\n            }\n          },\n          \"components\": {\n            \"button\": {\n              \"primary\": {\n                \"base\": {\n                  \"background-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#111111\"\n                  },\n                  \"text-color\": {\n                    \"unit\": \"hex\",\n                    \"value\": \"#FFFFFF\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Created
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["data": ["attributes": [
      "name": "Brand Default",
      "styles": [
        "settings": [
          "use-dark-theme": false,
          "preferred-theme-variant": "light"
        ],
        "light": [
          "inquiry": ["primary-color": [
              "unit": "hex",
              "value": "#111111"
            ]],
          "components": ["button": ["primary": ["base": [
                  "background-color": [
                    "unit": "hex",
                    "value": "#111111"
                  ],
                  "text-color": [
                    "unit": "hex",
                    "value": "#FFFFFF"
                  ]
                ]]]]
        ]
      ]
    ]]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.withpersona.com/api/v1/theme-sets")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```