{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/aimeat-io/json-schema/aimeat-io-schedule-create-schema.json",
  "title": "ScheduleCreate",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/aimeat-io-openapi.json#/components/schemas/ScheduleCreate",
  "type": "object",
  "required": [
    "kind",
    "cron",
    "display_name"
  ],
  "properties": {
    "kind": {
      "type": "string",
      "enum": [
        "ai",
        "agent_task",
        "extension",
        "eco-capability",
        "connections-publish",
        "refinery"
      ]
    },
    "cron": {
      "type": "string"
    },
    "display_name": {
      "type": "string"
    },
    "timezone": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "purpose": {
      "type": "string"
    },
    "agent_name": {
      "type": "string",
      "description": "Target agent (required for agent_task; optional association for ai/extension)."
    },
    "constraints": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/ScheduleConstraint"
      }
    },
    "prompt": {
      "type": "string",
      "description": "ai: instruction applied to the input memory values."
    },
    "input_keys": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "ai: owner memory keys fed in as context. Each one goes to the model provider, so a key naming a record this node keeps for itself (an AI key, a payout setting, a spend limit) is refused with 403 RESERVED_KEY."
    },
    "input_namespaces": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "system_prompt": {
      "type": "string"
    },
    "model": {
      "type": "string"
    },
    "output_key": {
      "type": "string",
      "description": "ai: memory key for the result (auto-generated if omitted). A key naming a record this node keeps for itself is refused with 403 RESERVED_KEY."
    },
    "output_visibility": {
      "type": "string",
      "enum": [
        "private",
        "owner",
        "public"
      ]
    },
    "task_template": {
      "type": "object",
      "description": "agent_task: the task materialised each fire.",
      "properties": {
        "title": {
          "type": "string"
        },
        "description": {
          "type": "string"
        }
      }
    },
    "extension_name": {
      "type": "string"
    },
    "action_id": {
      "type": "string"
    },
    "instance_id": {
      "type": "string"
    },
    "app": {
      "type": "string",
      "description": "eco-capability: the connected ecosystem app (GEAI) short name (e.g. \"zendesk\")."
    },
    "capability_id": {
      "type": "string",
      "description": "eco-capability: the capability to invoke each fire; must be declared in the app's manifest."
    },
    "input": {
      "type": "object",
      "description": "Kind-specific input payload (extension scheduler input, or the eco-capability invoke input).\n\nFor `refinery` it is `{ prefix }`: one batch of the mail refinery definition\n`<prefix>.config` on each fire. The creator needs connections:read-through, ai:use,\norganism:rows and memory:write; the schedule runs as its creator, so an agent's\nrefinery schedule reads only a mailbox that agent connected.\n\nFor `connections-publish` it is the post itself:\n`{ connection_id, caption?, storage_key?, params?, ref? }`. The connection must be one the\nCALLER connected — a connection belongs to the person who attached it, and absent /\nnot-yours answer with the same 404. Requires the `connections:use` scope.\n\nEverything checkable is checked at CREATE time (the connection is yours and active, a\nnamed file exists), because discovering either when it fires is a post that never appears\nwith nobody awake to see why.\n\nA one-shot (\"publish on Tuesday at 09:00\") is this kind plus\n`constraints: [{ type: max_runs, enabled: true, params: { limit: 1 } }]` — the scheduler\nauto-disables the job after the fire, and also after a failed fire, which is not retried\n(ScheduleConstraint). There is deliberately no separate publish queue:\nthe durable row, the DST-correct `timezone`, the run log and `/occurrences` are all this\nsame machinery. The fire goes through the SAME idempotency gate an immediate\n`POST /v1/connections/publish` does, so a schedule racing an impatient human produces one\npost rather than two.\n\nA FIRE THAT PUBLISHED NOTHING IS RECORDED AS `skipped`, NOT AS A SUCCESSFUL RUN, and the\nrun log carries the reason. That covers the gate answering \"this exact message is already\npublished\" and the `held` / `queued` waiting states. Returning success for those made the\nscheduler count the run, log it green and — for a one-shot — disable the schedule and tell\nthe owner it had finished, for a post that never left. Read the runs from\n`GET /v1/schedules/{id}`; a skip deliberately leaves `runCount` and the one-shot budget\nuntouched, so it does not appear in `lastRunResult`.\n\n`ref` is opaque — stored and handed back, never parsed or authorized against.\n"
    }
  },
  "$defs": {
    "ScheduleConstraint": {
      "type": "object",
      "description": "An opt-in budget/run guard on a schedule. type 'max_runs' or 'daily_limit' in v1. max_runs counts successful runs; with limit 1 the schedule is a one-shot, which is turned off after its run whether the run succeeded or failed. A failed one-shot is not retried: it keeps lastRunResult 'error' and lastRunError, has no nextRunAt, and its owner gets one notification.\n",
      "required": [
        "type",
        "enabled",
        "params"
      ],
      "properties": {
        "type": {
          "type": "string"
        },
        "enabled": {
          "type": "boolean"
        },
        "params": {
          "type": "object"
        },
        "state": {
          "type": "object"
        }
      }
    }
  }
}