{
  "components": {
    "schemas": {
      "Action": {
        "additionalProperties": false,
        "description": "What the agent proposes to do, in the customer's own vocabulary.",
        "properties": {
          "amount": {
            "anyOf": [
              {
                "minimum": 0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Amount"
          },
          "currency": {
            "anyOf": [
              {
                "pattern": "^[A-Z]{3}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Currency"
          },
          "description": {
            "anyOf": [
              {
                "maxLength": 2000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Description"
          },
          "parameters": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Parameters"
          },
          "target": {
            "anyOf": [
              {
                "maxLength": 256,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Target"
          },
          "type": {
            "pattern": "^[a-z][a-z0-9_.:-]{0,63}$",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "Action",
        "type": "object"
      },
      "ActionSummary": {
        "description": "The action as Gate understood it: the caller's type on the structured path, the\nidentified type and its confidence on the intake path.",
        "properties": {
          "confidence": {
            "default": 1.0,
            "maximum": 1,
            "minimum": 0,
            "title": "Confidence",
            "type": "number"
          },
          "identified": {
            "default": true,
            "title": "Identified",
            "type": "boolean"
          },
          "type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Type"
          }
        },
        "required": [
          "type"
        ],
        "title": "ActionSummary",
        "type": "object"
      },
      "Actor": {
        "additionalProperties": false,
        "description": "Who proposes the action.",
        "properties": {
          "name": {
            "maxLength": 128,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "permissions": {
            "items": {
              "type": "string"
            },
            "maxItems": 256,
            "title": "Permissions",
            "type": "array"
          },
          "type": {
            "default": "ai_agent",
            "enum": [
              "ai_agent",
              "human",
              "system"
            ],
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "Actor",
        "type": "object"
      },
      "Decision": {
        "description": "The public decision object.",
        "properties": {
          "action": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ActionSummary"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "confidence": {
            "maximum": 1,
            "minimum": 0,
            "title": "Confidence",
            "type": "number"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "decision": {
            "enum": [
              "allow",
              "review",
              "deny",
              "more_information"
            ],
            "title": "Decision",
            "type": "string"
          },
          "decision_id": {
            "title": "Decision Id",
            "type": "string"
          },
          "intake": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IntakeSummary"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "language": {
            "title": "Language",
            "type": "string"
          },
          "latency_ms": {
            "title": "Latency Ms",
            "type": "integer"
          },
          "missing": {
            "items": {
              "type": "string"
            },
            "title": "Missing",
            "type": "array"
          },
          "missing_details": {
            "items": {
              "$ref": "#/components/schemas/MissingDetail"
            },
            "title": "Missing Details",
            "type": "array"
          },
          "policy_results": {
            "items": {
              "$ref": "#/components/schemas/PolicyResult"
            },
            "title": "Policy Results",
            "type": "array"
          },
          "policy_version": {
            "default": "v1",
            "title": "Policy Version",
            "type": "string"
          },
          "reason_codes": {
            "items": {
              "type": "string"
            },
            "title": "Reason Codes",
            "type": "array"
          },
          "review": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Review"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "risk": {
            "additionalProperties": {
              "type": "number"
            },
            "title": "Risk",
            "type": "object"
          },
          "semantic": {
            "additionalProperties": {
              "type": "number"
            },
            "title": "Semantic",
            "type": "object"
          }
        },
        "required": [
          "decision_id",
          "decision",
          "confidence",
          "reason_codes",
          "risk",
          "semantic",
          "policy_results",
          "language",
          "latency_ms",
          "created_at"
        ],
        "title": "Decision",
        "type": "object"
      },
      "Error": {
        "properties": {
          "details": {
            "description": "Where the schema was not met: the fields and why"
          },
          "error": {
            "description": "A stable code, for example invalid_api_key",
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "message"
        ],
        "type": "object"
      },
      "EvaluateRequest": {
        "additionalProperties": false,
        "properties": {
          "action": {
            "$ref": "#/components/schemas/Action"
          },
          "actor": {
            "$ref": "#/components/schemas/Actor"
          },
          "context": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "items": {},
                "type": "array"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Context"
          },
          "options": {
            "$ref": "#/components/schemas/Options"
          }
        },
        "required": [
          "action",
          "actor"
        ],
        "title": "EvaluateRequest",
        "type": "object"
      },
      "Health": {
        "properties": {
          "env": {
            "type": "string"
          },
          "service": {
            "const": "tyoaly-gate",
            "type": "string"
          },
          "status": {
            "const": "ok",
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "status",
          "service",
          "version",
          "env"
        ],
        "type": "object"
      },
      "IntakeSummary": {
        "properties": {
          "contradictions": {
            "items": {
              "type": "string"
            },
            "title": "Contradictions",
            "type": "array"
          },
          "input_quality": {
            "additionalProperties": {
              "type": "number"
            },
            "title": "Input Quality",
            "type": "object"
          },
          "path": {
            "default": "structured",
            "enum": [
              "structured",
              "intake"
            ],
            "title": "Path",
            "type": "string"
          },
          "version": {
            "default": "v1",
            "title": "Version",
            "type": "string"
          }
        },
        "title": "IntakeSummary",
        "type": "object"
      },
      "MissingDetail": {
        "description": "A field the caller can supply and ask again, with the reason it is needed.",
        "properties": {
          "field": {
            "title": "Field",
            "type": "string"
          },
          "reason": {
            "title": "Reason",
            "type": "string"
          }
        },
        "required": [
          "field",
          "reason"
        ],
        "title": "MissingDetail",
        "type": "object"
      },
      "Options": {
        "additionalProperties": false,
        "properties": {
          "language": {
            "default": "auto",
            "pattern": "^(auto|[a-z]{2})$",
            "title": "Language",
            "type": "string"
          },
          "mode": {
            "default": "standard",
            "enum": [
              "standard",
              "deep"
            ],
            "title": "Mode",
            "type": "string"
          }
        },
        "title": "Options",
        "type": "object"
      },
      "PolicyResult": {
        "properties": {
          "effect": {
            "enum": [
              "allow",
              "review",
              "deny",
              "more_information"
            ],
            "title": "Effect",
            "type": "string"
          },
          "matched": {
            "title": "Matched",
            "type": "boolean"
          },
          "missing": {
            "items": {
              "type": "string"
            },
            "title": "Missing",
            "type": "array"
          },
          "policy": {
            "title": "Policy",
            "type": "string"
          },
          "reason_code": {
            "title": "Reason Code",
            "type": "string"
          }
        },
        "required": [
          "policy",
          "matched",
          "effect",
          "reason_code"
        ],
        "title": "PolicyResult",
        "type": "object"
      },
      "Review": {
        "description": "The person's decision about a Gate decision: the ground truth of advisory mode, where Gate\ndecides, the decision is logged, and the person still acts.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "decision_id": {
            "title": "Decision Id",
            "type": "string"
          },
          "note": {
            "anyOf": [
              {
                "maxLength": 2000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Note"
          },
          "outcome": {
            "enum": [
              "approved",
              "denied",
              "changed"
            ],
            "title": "Outcome",
            "type": "string"
          },
          "reviewer": {
            "maxLength": 256,
            "minLength": 1,
            "title": "Reviewer",
            "type": "string"
          }
        },
        "required": [
          "decision_id",
          "outcome",
          "reviewer",
          "created_at"
        ],
        "title": "Review",
        "type": "object"
      },
      "ReviewRequest": {
        "additionalProperties": false,
        "properties": {
          "note": {
            "anyOf": [
              {
                "maxLength": 2000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Note"
          },
          "outcome": {
            "enum": [
              "approved",
              "denied",
              "changed"
            ],
            "title": "Outcome",
            "type": "string"
          },
          "reviewer": {
            "maxLength": 256,
            "minLength": 1,
            "title": "Reviewer",
            "type": "string"
          }
        },
        "required": [
          "outcome",
          "reviewer"
        ],
        "title": "ReviewRequest",
        "type": "object"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "description": "An API key of the organisation's environment: tyo_test_… or tyo_live_…",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "email": "hei@tyoaly.fi",
      "name": "Työäly",
      "url": "https://tyoaly.com/gate/"
    },
    "description": "Base URL https://api.tyoaly.com. Authentication: `Authorization: Bearer tyo_test_…` or `tyo_live_…`; the key selects the organisation's environment. Everything under /v1 only gains fields. The reference for people is docs/api.md in the tyoaly/gate repository and https://docs.tyoaly.com/gate/api/.",
    "summary": "The decision layer for AI agents: an agent proposes, Gate decides, a person stays in charge.",
    "title": "Työäly Gate",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/v1/decisions/{decision_id}": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "decision_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            },
            "description": "The decision as it was answered, with `review` when a person has given a verdict"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No, malformed, revoked or unknown API key"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No decision with that id in this environment"
          }
        },
        "summary": "The audit record of a decision, own organisation only",
        "tags": [
          "decisions"
        ]
      }
    },
    "/v1/decisions/{decision_id}/review": {
      "post": {
        "description": "Advisory mode's ground truth from an integration that knows what happened: `approved` (the person carried the action out), `denied` (refused it) or `changed` (did something else). The latest review of a decision is the one kept. People without an integration answer the daily digest mail or the console instead.",
        "parameters": [
          {
            "in": "path",
            "name": "decision_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Review"
                }
              }
            },
            "description": "The review as stored"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No, malformed, revoked or unknown API key"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No decision with that id in this environment"
          },
          "413": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "A review may be at most 16 KB"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The body does not match the schema"
          }
        },
        "summary": "What the person did about the decision",
        "tags": [
          "decisions"
        ]
      }
    },
    "/v1/gate/evaluate": {
      "post": {
        "description": "Send the version-1 request (`action`, `actor`, optional `context` and `options`) as JSON. When the environment's Intake is on, any other JSON object or array, or plain text, is accepted too: Gate finds the proposal in it and answers the same decision object, asking for what it could not find in `missing`. The answer is `allow`, `review`, `deny` or `more_information`, with a confidence, reason codes, risk scores and the audit id.",
        "parameters": [
          {
            "description": "1 to 128 characters. The same key with the same body returns the same decision for 24 hours; the same key with a different body is refused (409).",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 128,
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Intake hint `agent`: the name of the agent that proposes, when the input does not say. At most 128 characters.",
            "in": "header",
            "name": "X-Tyoaly-Agent",
            "required": false,
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          },
          {
            "description": "Intake hint `action_hint`: the action type the agent means, when the input does not say. At most 128 characters.",
            "in": "header",
            "name": "X-Tyoaly-Action-Hint",
            "required": false,
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          },
          {
            "description": "Intake hint `resource_hint`: the record or resource the action is about. At most 128 characters.",
            "in": "header",
            "name": "X-Tyoaly-Resource-Hint",
            "required": false,
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          },
          {
            "description": "Intake hint `language_hint`: the language of the text, when detection should not guess. At most 128 characters.",
            "in": "header",
            "name": "X-Tyoaly-Language-Hint",
            "required": false,
            "schema": {
              "maxLength": 128,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/EvaluateRequest"
                  },
                  {
                    "description": "Any JSON about one proposed action (Intake on)",
                    "type": [
                      "object",
                      "array"
                    ]
                  }
                ]
              }
            },
            "message/rfc822": {
              "schema": {
                "description": "A mail message about one proposed action; its subject, sender domain, body text and attachment names are read (Intake on)",
                "type": "string"
              }
            },
            "text/html": {
              "schema": {
                "description": "An HTML document about one proposed action; its visible text is read (Intake on)",
                "type": "string"
              }
            },
            "text/plain": {
              "schema": {
                "description": "The text about one proposed action (Intake on)",
                "type": "string"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            },
            "description": "The decision; also returned for an Idempotency-Key seen before"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No, malformed, revoked or unknown API key"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The Idempotency-Key was used with a different body"
          },
          "413": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The body is larger than the environment allows (256 KB)"
          },
          "415": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "A media type Gate does not read"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The body does not match the schema, or Intake is off and the body is not a version-1 request"
          },
          "429": {
            "description": "Throttled at the gateway; retry with backoff"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Gate could not record the decision; nothing unrecorded is answered"
          }
        },
        "summary": "One proposed action in, one decision out",
        "tags": [
          "decisions"
        ]
      }
    },
    "/v1/health": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "description": "The service answers"
          }
        },
        "security": [],
        "summary": "Liveness and build info",
        "tags": [
          "service"
        ]
      }
    }
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "servers": [
    {
      "url": "https://api.tyoaly.com"
    }
  ],
  "tags": [
    {
      "name": "decisions"
    },
    {
      "name": "service"
    }
  ]
}
