{
  "openapi": "3.1.0",
  "info": {
    "title": "OK Agent API",
    "version": "1.0.0",
    "description": "Human approval for AI agents. Create a request, the human approves or rejects it from KakaoTalk, act only on `approved`.",
    "contact": { "email": "appmonster.kr@gmail.com", "url": "https://okagent.kr" }
  },
  "servers": [{ "url": "https://okagent.kr" }],
  "security": [{ "bearer": [] }],
  "components": {
    "securitySchemes": { "bearer": { "type": "http", "scheme": "bearer", "description": "API key from https://okagent.kr/dashboard (starts with ok_)" } },
    "schemas": {
      "Request": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "req_RwZ-q7PiJxFN" },
          "status": { "type": "string", "enum": ["pending", "approved", "rejected", "expired"] },
          "title": { "type": "string" },
          "action": { "type": "string" },
          "amount": { "type": ["number", "null"] },
          "currency": { "type": ["string", "null"] },
          "source": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "decidedAt": { "type": ["string", "null"], "format": "date-time" },
          "note": { "type": ["string", "null"], "description": "Optional note the human left when deciding" },
          "approveUrl": { "type": "string", "description": "Present while pending. The human already received it on KakaoTalk." }
        },
        "required": ["id", "status", "title", "action", "createdAt", "expiresAt"]
      },
      "Usage": {
        "type": "object",
        "properties": {
          "plan": { "type": "string", "enum": ["free", "pro"] },
          "month": { "type": "string", "example": "2026-10" },
          "since": { "type": "string", "format": "date-time" },
          "used": { "type": "integer" },
          "free": { "type": "integer", "description": "Requests included in the current period" },
          "overage": { "type": "integer" }
        }
      },
      "Error": { "type": "object", "properties": { "error": { "type": "string" }, "message": { "type": "string" }, "upgradeUrl": { "type": "string" } } }
    }
  },
  "paths": {
    "/api/v1/requests": {
      "post": {
        "operationId": "createApprovalRequest",
        "summary": "Ask the human before doing something irreversible",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["title", "action"],
            "properties": {
              "title": { "type": "string", "maxLength": 120, "description": "What the human reads first, e.g. 린넨 셔츠 (L) 재고 0 → 30" },
              "action": { "type": "string", "maxLength": 120, "description": "Exact tool/command name, e.g. naver_update_stock" },
              "description": { "type": "string", "maxLength": 2000 },
              "payload": { "description": "Exact arguments, shown to the human on request" },
              "amount": { "type": "number" },
              "currency": { "type": "string", "default": "KRW" },
              "source": { "type": "string", "maxLength": 80, "description": "Which agent is asking" },
              "expiresIn": { "type": "integer", "minimum": 30, "maximum": 604800, "default": 600, "description": "Seconds until auto-reject" },
              "callbackUrl": { "type": "string", "format": "uri", "description": "Receives POST {id,status,note,decidedAt,action} when decided" }
            }
          } } }
        },
        "responses": {
          "201": { "description": "Created; the human has been notified", "headers": { "X-OKAgent-Usage": { "schema": { "type": "string" }, "description": "used/included; month=YYYY-MM; overage=N" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Request" } } } },
          "401": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": { "description": "Free plan allowance used up (error=quota_exceeded, upgradeUrl)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "get": {
        "operationId": "listPendingRequests",
        "summary": "Pending requests for this API key",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "pending": { "type": "array", "items": { "$ref": "#/components/schemas/Request" } } } } } } } }
      }
    },
    "/api/v1/requests/{id}": {
      "get": {
        "operationId": "getApprovalRequest",
        "summary": "Check the decision. Act only when status is approved.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Request" } } } }, "404": { "description": "Not found for this key" } }
      }
    },
    "/api/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Requests used in the current period",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Usage" } } } } }
      }
    }
  }
}
