> ## Documentation Index
> Fetch the complete documentation index at: https://developer.hellgate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Schemas

> Typed, versioned declarations of the payloads Specter decides on — the built-in transaction schema and your own custom schemas.

A **schema** declares the shape of a decision payload: which fields exist, what type each one holds, and
which of them carry regulated data. Every ruleset is bound to exactly one `(schema, version)`, and that
binding decides how Specter validates the decision request, which field paths rules may reference, and how
each value is redacted, retained, and audited.

Specter ships one schema and lets you register your own:

| Kind         | Who defines it             | Versioning                                                               | Example                                                                             |
| ------------ | -------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| **Built-in** | Hellgate                   | Immutable from your side; grows only additively through Specter releases | `transaction` — the card-payment payload every Specter instance understands         |
| **Custom**   | You, through the Admin API | Revision-controlled: publish new versions, deprecate old ones            | `customer-registration`, `refund-request`, any event you want to express rules over |

<Info>
  This is the same model Guardian uses for
  [generic tokens](/products/guardian/tokens/generic-tokens) and their
  [types](/products/guardian/management/types): a schema declares the shape of a flexible payload, the
  platform validates against it, and everything downstream keys off the declaration rather than guessing
  from field names. Specter applies it to decision payloads instead of stored tokens.
</Info>

## How a request finds its schema

The decision request does not name a schema. It names a **context**, and the context resolves to a schema
through the ruleset that is `ACTIVE` for it:

```mermaid theme={null}
flowchart LR
    R["POST /api/decisions<br/>context: checkout"] --> C["Active ruleset<br/>for checkout"]
    C --> S["Bound schema<br/>(transaction)"]
    S --> V[Validate payload]
    S --> P[Resolve rule field paths]
```

* A ruleset declares its binding at creation (`schema: {"name": "transaction"}` for the built-in,
  `{"name": "refund-request", "version": 2}` for a custom schema) and keeps it for life. Rebinding a
  context means creating a new ruleset with the new binding and activating it.
* Rules inherit the binding from their ruleset. A field reference that the bound schema does not declare
  rejects at submission with `422`.
* Every decision records the `(schema, version)` it was evaluated against and returns it as `schema`.

Contexts that need different payload shapes are simply different contexts — there is no dispatch on the
shape of an incoming body.

## The built-in transaction schema

`transaction` is the card-payment payload: `credential`, `customer`, `transaction`, `device`, `billing`,
`shipping`, `items`, `airline`, `metadata`. It is present on every instance, version 1 today, and
bound to every ruleset that existed before custom schemas arrived. The
[decision request reference](/products/specter/api-reference/decisions/create-decision) is this
schema rendered field by field, including the type and classification marker of each field. You can also
read it through the registry: `GET /api/admin/schemas/transaction/versions/1`.

New fields arrive only additively, through Specter releases — the seven BIN attributes on card
credentials are an example. Nothing you send against it today stops validating.

## Custom schemas

A custom schema is a [JSON Schema](https://json-schema.org/) document with two Specter keywords on each
field: `x-specter-type` names the field's type, `x-data-classification` its scope marker.

```json theme={null}
{
  "name": "refund-request",
  "document": {
    "type": "object",
    "x-specter-type": "object",
    "required": ["order_reference", "amount", "currency"],
    "properties": {
      "order_reference": { "type": "string", "x-specter-type": "string" },
      "amount": { "type": "integer", "x-specter-type": "amount" },
      "currency": { "type": "string", "x-specter-type": "currency-code" },
      "reason": { "type": "string", "x-specter-type": "string" },
      "customer_email": {
        "type": "string",
        "x-specter-type": "email",
        "x-data-classification": "pii"
      }
    }
  }
}
```

The registry endpoints need the `admin:schemas:read` and `admin:schemas:write` scopes — see the
[Schemas API reference](/products/specter/api-reference/schemas/list-schemas).

<Steps>
  <Step title="Register">
    `POST /api/admin/schemas` with a `name` and the version-1 `document`. The document validates on
    the way in — unresolvable or external `$ref`, unknown type names, misspelled markers, and documents
    over the size, field-count, nesting, or `$ref` caps reject with `422`.
  </Step>

  <Step title="Bind a ruleset">
    Create a ruleset with `schema: {"name": "refund-request", "version": 1}` and a `context` of your
    choice, then activate it. Decisions sent with that context now validate against your document.
  </Step>

  <Step title="Publish a new version">
    `POST /api/admin/schemas/{name}/versions` with the changed `document`. Versions are immutable and
    numbered by Specter; every publication is a new version. Rulesets bound to version 1 keep working —
    move a context over by creating and activating a ruleset bound to version 2.
  </Step>

  <Step title="Deprecate the old version">
    `POST /api/admin/schemas/{name}/versions/{version}/deprecate`. A deprecated version keeps serving
    the rulesets already bound to it, but no new ruleset can bind to it and a draft bound to it can no
    longer be activated.
  </Step>
</Steps>

### Velocity counters across versions

When you publish a new version, `carry_over` (default `true`) decides whether
[velocity counters](/products/specter/concepts/rule-engine#velocity) for field paths present in both
versions continue counting across the version boundary. Publish with `carry_over: false` to have rulesets
bound to the new version start from zero; earlier versions keep their counters. Blacklist entries are
global and unaffected.

## Field types

`x-specter-type` is a closed vocabulary of 23 entries. Each carries a validation rule applied to the
decision payload at ingestion, so a rule can trust the value it compares against.

| Group            | Types                                                                                                               | Validation                                                                                                                                    |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Primitives       | `string`, `integer`, `number`, `boolean`                                                                            | JSON type                                                                                                                                     |
| Semantic strings | `email`, `iban`, `country-code`, `currency-code`, `language-code`, `date`, `datetime`, `url`, `uuid`, `phone`, `ip` | Format-checked: IBAN check digits, ISO 3166-1 alpha-2, ISO 4217, ISO 8601, E.164 phone numbers, and so on                                     |
| Amount           | `amount`                                                                                                            | Integer in minor units, paired with an adjacent `currency-code` field                                                                         |
| Card             | `pan`, `masked-pan`, `scheme-enum`, `card-funding-type-enum`, `card-segment-enum`                                   | Check digit for `pan`; closed value lists aligned with Guardian's [metadata inquiry](/products/guardian/tokens/metadata-inquiries) vocabulary |
| Structural       | `object`, `array`                                                                                                   | Container; rules address their leaves, or test membership in an array                                                                         |

Filters in rule conditions are type-checked against these declarations — `domain()` needs an `email`,
for example — so a mistyped pipe chain rejects when you save the ruleset, not at decision time.

## Classification markers

`x-data-classification` is one of `any`, `pii`, `pci-pan`, or `pci-sad` (lowercase; omitted means `any`).
A `pan`-typed field is always `pci-pan`; a `masked-pan` field defaults to `pii`. The marker is what Specter
acts on — never the field's name:

| The marker drives         | What happens                                                                                                                                                                                                                                                                        |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Ingestion gate**        | `pci-pan` and `pci-sad` values are accepted only on instances at PCI level `SAQ_D` or `ROC`; below that, a payload carrying them rejects with `422`. See [PCI level](/products/specter/plans-and-access#pci-level).                                                                 |
| **What rules may do**     | A `pci-pan` or `pci-sad` field cannot be compared against a literal, counted by a velocity rule, or key a blacklist entry; `pci-sad` cannot enter an HMAC fingerprint. `pii` fields are unrestricted in rules.                                                                      |
| **Redaction**             | `pii`, `pci-pan`, and `pci-sad` values are replaced by a sentinel in operational logs and error bodies, in the stored decision payload, and in simulation traces and evidence; the field path stays visible.                                                                        |
| **Storage and retention** | `pci-pan` is stored only as a fingerprint and `pci-sad` never at all. Your instance configures a retention window per scope; once it passes, fields of that scope read as `null` on the decision record. See [Decisions](/products/specter/concepts/decisions#the-decision-record). |
| **Audit**                 | Actions that reference `pii` or `pci-*` fields are audited by field path; the values never enter the audit trail.                                                                                                                                                                   |

A field without a marker is a statement that it carries none of these categories, not that it has yet
to be reviewed. The [data classification legend](/products/specter/api-reference/getting-started#data-classification)
describes the markers as they appear on the API reference.

## What works with a custom schema

A custom schema changes what a decision is *about*; it does not change how you request or inspect one. What
it cannot do is reach the card-payment machinery that sits behind the transaction schema.

| Capability                                                                                                                                                                               | `transaction` | Custom schema                                                 |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------- |
| `POST /api/decisions`, decision record, [simulation](/products/specter/concepts/rule-engine#simulation), [evidence and replay](/products/specter/concepts/decisions#evidence-and-replay) | ✓             | ✓                                                             |
| Condition, velocity, and blacklist rules; manual blacklist entries                                                                                                                       | ✓             | ✓                                                             |
| Backend rules ([backend](/products/specter/concepts/backends) fan-out)                                                                                                                   | ✓             | —                                                             |
| [Lifecycle events](/products/specter/concepts/lifecycle-events) and blacklist auto-population from them                                                                                  | ✓             | — (`POST /api/events` for such a decision rejects with `422`) |
| [Interceptors](/products/specter/integration/via-interceptor)                                                                                                                            | ✓             | —                                                             |

## Related

<CardGroup cols={2}>
  <Card title="Rulesets" icon="layer-group" href="/products/specter/concepts/rulesets">
    Where the schema binding lives.
  </Card>

  <Card title="Rule engine" icon="sliders" href="/products/specter/concepts/rule-engine">
    Field references, filters, and fingerprints over the bound schema.
  </Card>

  <Card title="Schemas API" icon="code" href="/products/specter/api-reference/schemas/list-schemas">
    Register, version, and deprecate custom schemas.
  </Card>

  <Card title="Guardian types" icon="shield" href="/products/guardian/management/types">
    The same schema-typed model for stored tokens.
  </Card>
</CardGroup>
