{
  "openapi": "3.1.0",
  "info": {
    "title": "Freshness Validator API",
    "version": "1.0.0",
    "description": "Scores AI agent context for freshness and monitors public source URLs. Operator endpoints are intentionally omitted.",
    "contact": {
      "email": "hello@myuiaf.com"
    }
  },
  "servers": [
    {
      "url": "https://api.myuiaf.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key of the form fv1.<agent_id>.<key_id>.<secret>"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        }
      },
      "ValidateRequest": {
        "type": "object",
        "required": [
          "context"
        ],
        "properties": {
          "agent_id": {
            "type": "string",
            "description": "Optional. Must match the key's agent."
          },
          "context": {
            "type": "string",
            "maxLength": 50000
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "description": "Public https URL. Register it first for change detection and content matching."
          },
          "last_validated": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "properties": {
              "content_type": {
                "type": "string",
                "enum": [
                  "verbatim",
                  "paraphrase"
                ],
                "default": "verbatim"
              },
              "freshness_policy": {
                "type": "string",
                "example": "24h",
                "description": "1h to 8760h, or 1d to 365d. Default 24h."
              }
            }
          }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "properties": {
          "freshness_score": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "is_stale": {
            "type": "boolean",
            "description": "True when the score is below 70."
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "source_match": {
            "type": "object",
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "exact",
                  "overlap",
                  "keyword",
                  "none"
                ]
              },
              "overlap": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "flagged_issues": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "suggestions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source_status": {
            "type": "string",
            "enum": [
              "unmonitored",
              "monitored",
              "error"
            ]
          },
          "source_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_last_checked": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "source_last_changed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "context_hash": {
            "type": "string"
          },
          "validated_at": {
            "type": "string",
            "format": "date-time"
          },
          "credits_deducted": {
            "type": "number"
          },
          "remaining_balance": {
            "type": "number"
          }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": [
          "source_url"
        ],
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "check_frequency": {
            "type": "string",
            "enum": [
              "1h",
              "6h",
              "24h",
              "7d",
              "30d"
            ],
            "default": "24h"
          }
        }
      },
      "Source": {
        "type": "object",
        "properties": {
          "source_id": {
            "type": "string"
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "check_frequency": {
            "type": "string"
          },
          "last_checked": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_changed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_check": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "monitored",
              "error"
            ]
          },
          "change_count": {
            "type": "integer"
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RegisterResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Source"
          },
          {
            "type": "object",
            "properties": {
              "created": {
                "type": "boolean"
              },
              "credits_charged": {
                "type": "number"
              },
              "remaining_balance": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "Wallet": {
        "type": "object",
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "enum": [
              "early_testing",
              "growing_product",
              "scale_revenue",
              "enterprise"
            ]
          },
          "balance_usdc": {
            "type": "number",
            "description": "Dollar-denominated credits"
          },
          "total_credited": {
            "type": "number"
          },
          "total_spent": {
            "type": "number"
          },
          "total_refunded": {
            "type": "number"
          },
          "calls_this_month": {
            "type": "integer"
          },
          "calls_per_month_limit": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_per_call": {
            "type": "number"
          },
          "transactions": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/validate": {
      "post": {
        "summary": "Validate agent context for freshness",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Freshness result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits. The freshness result is still returned, with credits_deducted 0.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "403": {
            "description": "agent_id does not match the key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body or context too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit, monthly allowance, or temporary block. Rate limits include a Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/source/register": {
      "post": {
        "summary": "Register a public https URL for monitoring",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Source registered (or its frequency updated)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegisterResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits; nothing registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "agent_mismatch or source_limit_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The source could not be fetched or used; nothing charged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit, monthly allowance, or temporary block. Rate limits include a Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sources": {
      "get": {
        "summary": "List monitored sources",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sources",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sources": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Source"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sources/{source_id}": {
      "delete": {
        "summary": "Stop monitoring a source and delete its stored text",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "source_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/wallet/{agent_id}": {
      "get": {
        "summary": "Get the agent's balance, usage, and recent transactions",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Wallet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key belongs to another agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "Model Context Protocol endpoint (JSON-RPC 2.0)",
        "description": "initialize, ping, and tools/list are public. tools/call requires a bearer key. Tools: validate, register_source.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification accepted"
          },
          "401": {
            "description": "tools/call without a valid key (JSON-RPC error -32001)"
          }
        }
      }
    }
  }
}
