> ## 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.

# Protocol schema

> The Link protocol meta-schema — author your own protocols on a Standalone instance.

<Badge color="green">Standalone</Badge>

On a [Standalone](/products/link/plans-and-access) instance you can author and import **your own** protocols. A
protocol is a JSON document validated against the **Link protocol meta-schema** (`link-protocol-v1`) at import
time; a document that does not conform is rejected.

<Note>
  Custom protocols require a **Standalone** instance. A [Bundled](/products/link/plans-and-access) instance is
  locked to serving its Specter instance's backends. See [Protocols](/products/link/concepts/protocols) for the
  concept.
</Note>

The meta-schema is JSON Schema (draft 2020-12) and is served by every instance:

```bash theme={null}
curl https://developer.hellgate.io/schemas/link-protocol/v1
```

## Top-level structure

A protocol document has these fields (`additionalProperties` is `false` — unknown top-level keys are rejected):

| Field        | Required | Description                                                                                               |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `$schema`    | yes      | Reference to the meta-schema: `https://developer.hellgate.io/schemas/link-protocol/v1`                    |
| `$id`        | yes      | Canonical URI identifying this protocol. Backends reference the protocol by this URI; it is the join key. |
| `name`       | yes      | Human-readable protocol name.                                                                             |
| `version`    | yes      | Version identifier (a string, for example `1.0.0`).                                                       |
| `actions`    | yes      | Map of action name → [Action](#actions). At least one action is required.                                 |
| `components` | no       | Reusable schemas referenced via `$ref` (see [Components](#components)).                                   |

## Actions

`actions` is keyed by action name. Names may use **dot notation** to namespace related operations (for example
`notify.chargeback`). Each action is an object (`additionalProperties: false`):

| Field         | Required | Description                                                                                    |
| ------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `method`      | yes      | HTTP method: `POST`, `GET`, `PUT`, `PATCH`, or `DELETE`. Callers must invoke with this method. |
| `description` | yes      | Human-readable description of the action.                                                      |
| `request`     | yes      | The input definition — [monomorphic or discriminated](#requests).                              |
| `responses`   | yes      | Output definitions keyed by HTTP status (see [Responses](#responses)).                         |
| `required`    | no       | Whether backends **must** implement this action. Defaults to `false`.                          |

## Requests

`request` is one of two shapes.

### Monomorphic — a single schema

The request is a single [JSON Schema](#supported-json-schema-keywords):

```json theme={null}
"request": {
  "type": "object",
  "properties": {
    "transaction": { "$ref": "#/components/schemas/Transaction" }
  },
  "required": ["transaction"]
}
```

### Discriminated — a polymorphic union

A discriminator field selects a named **variant**. Each variant is a JSON Schema:

| Field               | Required | Description                                                                                 |
| ------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `discriminator`     | yes      | A JSONPath expression pointing to the discriminator field, for example `$.credential.type`. |
| `variants`          | yes      | Map of discriminator value → JSON Schema. At least one variant.                             |
| `required_variants` | no       | Variant names backends must implement. Each must exist in `variants`.                       |

```json theme={null}
"request": {
  "discriminator": "$.credential.type",
  "variants": {
    "pan": { "$ref": "#/components/schemas/PanRequest" },
    "token": { "$ref": "#/components/schemas/TokenRequest" }
  },
  "required_variants": ["pan"]
}
```

Backends implement a discriminated action by its `action.variant` key (for example `assess.pan`). See
[Backends](/products/link/concepts/backends#mapping).

## Responses

`responses` is keyed by HTTP status code (a string matching `^[1-5][0-9]{2}$`), with at least one entry. Each
entry nests the schema under `content` → `application/json` → `schema`:

```json theme={null}
"responses": {
  "200": {
    "content": {
      "application/json": {
        "schema": { "$ref": "#/components/schemas/AssessResult" }
      }
    }
  }
}
```

Backend response mappings target these status codes and are validated against the corresponding schema at
backend-write time — see [Backends](/products/link/concepts/backends#response-mapping).

## Components

`components.schemas` holds reusable schemas referenced from anywhere in the protocol via a JSON Pointer `$ref`
(for example `#/components/schemas/Transaction`):

```json theme={null}
"components": {
  "schemas": {
    "Transaction": {
      "type": "object",
      "properties": {
        "amount": { "type": "integer" },
        "currency": { "type": "string" }
      },
      "required": ["amount", "currency"]
    }
  }
}
```

## Supported JSON Schema keywords

Schemas (in requests, responses, and components) are JSON Schema. The meta-schema explicitly lists the supported
keywords; full validation is delegated to a JSON Schema validator (draft 2020-12):

`type`, `const`, `enum`, `properties`, `required`, `additionalProperties`, `items`, `oneOf`, `anyOf`, `allOf`,
`$ref`, `description`, `format`, `minimum`, `maximum`, `minLength`, `maxLength`, `pattern`.

## Complete example

A minimal but valid protocol with one discriminated `assess` action and shared components:

```json theme={null}
{
  "$schema": "https://developer.hellgate.io/schemas/link-protocol/v1",
  "$id": "https://acme.example/protocols/risk/v1",
  "name": "Acme Risk",
  "version": "1.0.0",
  "actions": {
    "assess": {
      "method": "POST",
      "description": "Score a transaction for fraud risk.",
      "request": {
        "discriminator": "$.credential.type",
        "variants": {
          "pan": {
            "type": "object",
            "properties": {
              "credential": { "$ref": "#/components/schemas/PanCredential" },
              "transaction": { "$ref": "#/components/schemas/Transaction" }
            },
            "required": ["credential", "transaction"]
          }
        },
        "required_variants": ["pan"]
      },
      "responses": {
        "200": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": { "const": "enum" },
                  "value": { "type": "string", "enum": ["ALLOW", "REVIEW", "BLOCK"] },
                  "backend_reference": { "type": "string" }
                },
                "required": ["type", "value"]
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PanCredential": {
        "type": "object",
        "properties": {
          "type": { "const": "pan" },
          "pan": {
            "type": "object",
            "properties": {
              "value": { "type": "string" },
              "expiry_month": { "type": "integer" },
              "expiry_year": { "type": "integer" }
            },
            "required": ["value"]
          }
        },
        "required": ["type", "pan"]
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "amount": { "type": "integer" },
          "currency": { "type": "string" }
        },
        "required": ["amount", "currency"]
      }
    }
  }
}
```

## Import and validation

Import a protocol you authored on a [Standalone](/products/link/plans-and-access) instance — by URL or by posting
the document directly. Requires `admin:protocols:write`.

```bash theme={null}
# Import by URL
curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols/import \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://acme.example/protocols/risk/v1"}'

# Or create from the document
curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @risk-v1.json
```

At import, Link validates the document against the meta-schema and rejects anything non-conforming. A Standalone
instance also lifts the restriction that limits a Bundled instance to its Specter protocols, so your own `$id` is
accepted. Once imported, Link generates an OpenAPI 3.1 specification for the protocol —
see [Protocols](/products/link/concepts/protocols#generated-openapi).

## Next steps

<CardGroup cols={2}>
  <Card title="Backends" icon="plug" href="/products/link/concepts/backends">
    Bind your protocol to a real provider with mapping rules.
  </Card>

  <Card title="Protocols" icon="file-contract" href="/products/link/concepts/protocols">
    The protocol concept — actions, variants, and versioning.
  </Card>
</CardGroup>
