# How to create Custom (Structured) Content in Arc XP

Source: https://dev.arcxp.com/custom-content/how-to-guides/how-to-create-custom-structured-content-in-arc-xp/

You can create a structured content schema in two ways: in Data Manager, under Content Types, or programmatically through the Arc XP Draft API. Both produce the same schema.

This guide covers the API, which is what you want for anything repeatable: provisioning the same schema across sandbox, staging, and production, or scripting schema setup as part of a deployment pipeline. If you are defining a schema once, by hand, Data Manager is the shorter path, and it shows your organization’s schema and indexed-field usage against its limits as you build. Creating and editing schemas there requires the admin permission.

See [Structured (Custom) Content in Arc XP](https://dev.arcxp.com/custom-content/introducing-structured-custom-content/) to learn more about Structured Content.

This guide walks through a complete schema creation request using `POST /draft/v1/schema-type`, with a working events schema as the example.

## Before you start

You need:

- A valid Arc XP API token with write access to the Draft API
- Your organization’s Arc XP API base URL (i.e., `https://api.myorg.arcpublishing.com`)
- A clear picture of your schema structure: field names, types, and which fields need search indexing

That last point is the most important one to get right before sending your first request.

> **Schema fields are immutable:**
> 
> You cannot remove fields, change a field’s data type, make optional fields required, or remove or change search indexing on a field. The API rejects these changes with a `400` error (code `ErrInvalidParam`, message `schema type update contains breaking changes: …`), even if the schema has zero records.
> 
> Safe changes: adding new fields, updating display names and descriptions, adding search indexing to a previously unindexed field, making required fields optional, are always allowed.
> 
> See the [Developer Guide](https://dev.arcxp.com/custom-content/core-concepts/developer-guide/) for the full list of allowed and disallowed changes.

## Plan your schema

Two decisions are hard to reverse: field types and search indexing. Both need to be right when you create the schema.

**Field types** are permanent. A field defined as `short-text` cannot become `date-time`. If you get this wrong, you’d need to start with a new schema.

**Search indexing** is configured with the `search_settings` object on a field. It has two independent dimensions, and **both are set through the Draft API**:

- **`retrieval`** makes the field queryable via the Arc XP Content API (reader-facing query/filter).
- **`editorial`** makes the field searchable/filterable inside editorial tools (Data Center / Data Manager).

Each dimension is optional, and each takes **exactly one** index kind. A field with no `search_settings` is stored but not indexed for search.

Decide both for every field upfront. Think through which fields editors or developers will want to filter or search on, not just which ones need to be stored.

A schema can have up to 200 fields. The other limit that matters is how many fields you make searchable (indexed): that capacity is a pool shared across **all schemas in your org**, not a per-schema allowance.

## Make the request

### Endpoint

```
POST /draft/v1/schema-type
```

Authentication uses a Bearer token in the `Authorization` header.

### Request body

A schema requires `name`, `display_name`, and `fields`. The `name` is the unique machine-readable identifier: 3–30 characters, kebab-case (`^[a-z0-9]+(-[a-z0-9]+)*$`, e.g. `calendar-event` or `staff-profile`). It cannot be changed after creation. `display_name` is required (1–50 characters); `description` is optional (up to 500 characters).

A schema must have between **1 and 200 fields**, and field `key`s must be unique within the schema. Each field requires `key`, `type`, and `display_name` — `key` is 3–30 characters, kebab- or snake-case (e.g. `event_date`), and `display_name` is 1–50 characters. Everything else is optional but worth including.

**Field types:**

| Type       | Use for                         |
| ---------- | ------------------------------- |
| short-text | Titles, labels, short strings   |
| long-text  | Descriptions, body copy         |
| date-time  | Dates and timestamps (ISO 8601) |
| geo-point  | Location coordinates (lat/lng)  |

Additional field types are supported, including `boolean`, `integer`, `decimal`, `date`, `enum`, `rich-text`, `array` (including multi-select, an `array` of `enum`), `object`, and `reference`. See [field types](https://dev.arcxp.com/custom-content/reference/structured-content-field-types) for the full list, and [validation rules](https://dev.arcxp.com/custom-content/reference/structured-content-field-types#validation-rules) to constrain field values. The valid search index kinds depend on the field type (see below).

**Search indexing (`search_settings`):**

Search indexing is set with the `search_settings` object. It has two optional keys — `editorial` and `retrieval` — each an **array containing exactly one index kind**:

```json
"search_settings": {
  "editorial": ["keyword"],
  "retrieval": ["keyword"]
}
```

The valid index kinds depend on the field type and on the dimension (`editorial` vs `retrieval`):

| Field type | editorial kinds   | retrieval kinds |
| ---------- | ----------------- | --------------- |
| short-text | keyword, semantic | keyword         |
| long-text  | semantic          | —               |
| date-time  | range             | range           |
| geo-point  | geo               | geo             |

(For the other field types: `boolean`, `enum`, and multi-select support `keyword`; `integer`, `decimal`, and `date` support `range`.)

The available index kinds are:

- `keyword` — exact-match filtering and faceting
- `semantic` — embedding/semantic search
- `range` — numeric and date range queries
- `geo` — geo-distance queries

Which kinds you can use on a field depends on its type and on whether you’re indexing it for `editorial` or `retrieval` (see the table above).

### cURL example

This creates an events schema with four fields: a required title indexed for keyword search (both editorial and retrieval), an optional description, a required date, and an optional location.

Terminal window

```bash
curl -X POST "https://api.{YOUR_ORG}.arcpublishing.com/draft/v1/schema-type" \
  -H "Authorization: Bearer {YOUR_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "calendar-event",
    "display_name": "Calendar Event",
    "description": "Schema for event listings such as concerts, conferences, and local happenings.",
    "fields": [
      {
        "key": "title",
        "display_name": "Event Title",
        "description": "Name of the event",
        "type": "short-text",
        "is_required": true,
        "search_settings": {
          "editorial": ["keyword"],
          "retrieval": ["keyword"]
        }
      },
      {
        "key": "description",
        "display_name": "Description",
        "description": "Full event description visible to readers",
        "type": "long-text",
        "is_required": false
      },
      {
        "key": "event_date",
        "display_name": "Event Date",
        "description": "Start date and time of the event",
        "type": "date-time",
        "is_required": true
      },
      {
        "key": "venue_location",
        "display_name": "Venue Location",
        "description": "Coordinates of the event venue",
        "type": "geo-point",
        "is_required": false
      }
    ]
  }'
```

Replace `{YOUR_ORG}` with your organization identifier and `{YOUR_API_TOKEN}` with a valid token.

## Understand the response

A successful request returns `200 OK` with the full schema object:

```json
{
  "name": "calendar-event",
  "display_name": "Calendar Event",
  "description": "Schema for event listings such as concerts, conferences, and local happenings.",
  "schema_version": 1,
  "created_at": "2026-06-26T12:00:00.000Z",
  "updated_at": "2026-06-26T12:00:00.000Z",
  "fields": [
    {
      "key": "title",
      "display_name": "Event Title",
      "description": "Name of the event",
      "type": "short-text",
      "is_required": true,
      "search_settings": {
        "editorial": ["keyword"],
        "retrieval": ["keyword"]
      }
    },
    {
      "key": "description",
      "display_name": "Description",
      "description": "Full event description visible to readers",
      "type": "long-text",
      "is_required": false
    },
    {
      "key": "event_date",
      "display_name": "Event Date",
      "description": "Start date and time of the event",
      "type": "date-time",
      "is_required": true
    },
    {
      "key": "venue_location",
      "display_name": "Venue Location",
      "description": "Coordinates of the event venue",
      "type": "geo-point",
      "is_required": false
    }
  ]
}
```

`schema_version` starts at `1` and increments with each PATCH that changes the schema. `created_at` and `updated_at` are system-managed and read-only.

## Common errors

**400: Invalid request**Returned for all validation failures, including:

- A required property is missing (`name`, `display_name`, `fields`, or a field’s `key` / `type` / `display_name`)
- A field type is not recognized
- The request contains an unrecognized property (the API rejects unknown fields), or a field’s `validation` uses a rule that is not supported for its type
- An index kind is not valid for the field’s type, or more than one index kind is supplied for `editorial` or `retrieval`
- The schema has zero fields or more than 200, or two fields share the same `key`
- **A schema with that `name` already exists** — schema names are unique per organization (message: `duplicate schema name '<name>' already exists`); a name that belonged to a deleted schema returns `Schema has been deleted.`
- An org limit is exceeded (maximum number of schema types, or the org-wide indexed-field capacity, e.g. `already reached or exceeded 25 indexed fields across all schemas`)

Check the `error_message` in the response for specifics.

**403: Forbidden**Structured Content is not enabled for your organization. Contact Arc XP support to enable it.

## Verify the schema was created

Fetch the schema by name to confirm it looks right:

Terminal window

```bash
curl "https://api.{YOUR_ORG}.arcpublishing.com/draft/v1/schema-type/calendar-event" \
  -H "Authorization: Bearer {YOUR_API_TOKEN}"
```

To list all schemas in your org:

Terminal window

```bash
curl "https://api.{YOUR_ORG}.arcpublishing.com/draft/v1/schema-type" \
  -H "Authorization: Bearer {YOUR_API_TOKEN}"
```
