{
  "schemaVersion": "2026-08-21",
  "name": "SteadyRenew AI tool schema",
  "sourceOpenApi": "api/openapi.yaml",
  "baseUrl": "https://your-site.example",
  "auth": {
    "type": "bearer",
    "header": "Authorization",
    "format": "Bearer <SUBSCRIPTION_MANAGER_API_KEY>",
    "safety": "Treat API keys like passwords. Do not expose them in browser code, public prompts, logs, or repositories."
  },
  "globalConventions": {
    "ownerScope": "All operations are scoped to the authenticated API key owner.",
    "dateFormat": "YYYY-MM-DD",
    "amountFormat": "Major currency units with at most 2 decimal places.",
    "serverManagedFields": [
      "id",
      "createdAt",
      "updatedAt"
    ],
    "writableFields": [
      "amount",
      "billingAnchorDay",
      "category",
      "currency",
      "customDate",
      "isTrial",
      "lastPaymentDate",
      "nextPaymentDate",
      "name",
      "notificationEnabled",
      "period",
      "trialEndsOn"
    ],
    "billingPeriods": [
      "monthly",
      "yearly",
      "custom"
    ],
    "lifecycleStates": [
      "active",
      "paused",
      "cancelled"
    ],
    "currencies": [
      "CNY",
      "USD",
      "EUR",
      "JPY",
      "GBP",
      "AUD",
      "CAD",
      "CHF",
      "HKD",
      "SGD"
    ],
    "fieldSemantics": {
      "period": "The billing cycle. Monthly means the same calendar day each month, not a fixed 30 days. Send monthly, yearly, or custom.",
      "customDate": "The fixed interval in days when period is custom. Use customDate 30 for every 30 days.",
      "nextPaymentDate": "The authoritative upcoming renewal date in YYYY-MM-DD format. For free trials, prefer trialEndsOn; that date is one-shot and is not auto-advanced.",
      "billingAnchorDay": "Original calendar day (1-31) for monthly billing. Short months use month end temporarily, then return to this day.",
      "lastPaymentDate": "Deprecated compatibility input. New tools should send nextPaymentDate instead.",
      "isTrial": "True when the record is a free trial rather than a recurring paid subscription.",
      "trialEndsOn": "The trial end / first-charge date in YYYY-MM-DD format. Required when isTrial is true. Do not roll this date forward after it passes.",
      "status": "Read-only lifecycle state returned on subscription records and accepted as a list filter. The public API does not create, cancel, pause, or resume via status changes; use delete_subscription to remove a tracked record.",
      "notificationEnabled": "Per-subscription reminder toggle on a subscription record. Independent from the global enabled flag in notification settings.",
      "notificationSettings.enabled": "Global reminder master switch. Reminders only send when this is true, Bark is configured in the web app, and the subscription has notificationEnabled true.",
      "notificationSettings.daysBefore": "How many days before renewal/trial end to remind. Allowed values: 1, 3, 7, 14."
    },
    "scopes": "API keys carry scopes. A read key can call list, get, notification settings read, the analytics endpoints, and the audit log. A write key additionally allows create, update, delete, and notification settings updates. Calling a write tool with a read-only key returns 403 insufficient_scope; do not retry, ask the user for a write-scoped key.",
    "auditTrail": "Every successful create, update, and delete is recorded in the audit log with before/after state. Use list_audit_log to show the user what changed or to support an undo.",
    "writeConfirmationPolicy": "Ask for user confirmation before any write (create, update, delete, update_notification_settings). For delete, repeat the target subscription name and id.",
    "outOfScope": "Do not claim you can configure Bark URL/device key, send a test push, change timeZone/locale, or manage the category catalog. Those stay in the web app. If the user asks to paste a Bark URL or run a test push, refuse and point them to Settings → Notifications in the web app, and to the reminders guide at /en/user-guide/reminders (Chinese: /zh-CN/user-guide/reminders). After Bark is configured on the web, you may toggle enabled/daysBefore and per-subscription notificationEnabled."
  },
  "tools": [
    {
      "name": "list_subscriptions",
      "operationId": "listSubscriptions",
      "method": "GET",
      "path": "/api/v1/subscriptions",
      "purpose": "List subscriptions owned by the API key owner, with optional filters and sorting, so an agent can search, summarize, compare, or identify records before writing. Results are paged; the response includes a pagination object (limit, offset, hasMore) and an echo of the applied query.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks to show, search, analyze, or summarize subscriptions.",
        "The user names a subscription but does not provide its UUID.",
        "The user asks which subscriptions renew soon (use status=active with expiringBefore).",
        "The agent needs current state before planning an update or delete."
      ],
      "doNotUseWhen": [
        "The user provides a known UUID and asks for only that exact record.",
        "The user wants computed totals or savings advice (use get_spend_summary or get_optimization_suggestions)."
      ],
      "naturalLanguageExamples": [
        "Show all my subscriptions.",
        "Which subscriptions renew before the end of the month?",
        "Find my Netflix subscription.",
        "List my active subscriptions."
      ],
      "parameters": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "description": "Maximum number of subscriptions to return (1-100, default 50)."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "default": 0,
            "description": "Number of subscriptions to skip from the start. Use offset + limit to fetch the next page while hasMore is true."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "cancelled"
            ],
            "description": "Filter by lifecycle state."
          },
          "category": {
            "type": "string",
            "description": "Exact category match, for example \"Streaming\"."
          },
          "period": {
            "type": "string",
            "enum": [
              "monthly",
              "yearly",
              "custom"
            ],
            "description": "Filter by billing period."
          },
          "q": {
            "type": "string",
            "description": "Case-insensitive search over the subscription name."
          },
          "expiringBefore": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Return subscriptions whose nextPaymentDate is on or before this date. Combine with status=active to find renewals due soon."
          },
          "sort": {
            "type": "string",
            "enum": [
              "createdAt",
              "-createdAt",
              "nextPaymentDate",
              "-nextPaymentDate",
              "amount",
              "-amount",
              "name",
              "-name"
            ],
            "default": "-createdAt",
            "description": "Sort order. Prefix with - for descending, for example -nextPaymentDate."
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "get_subscription",
      "operationId": "getSubscription",
      "method": "GET",
      "path": "/api/v1/subscriptions/{id}",
      "purpose": "Fetch one subscription by UUID when the exact id is already known.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The agent needs to verify exact current state before writing.",
        "The user provides a subscription UUID."
      ],
      "doNotUseWhen": [
        "The user only provides a name or category. List subscriptions first and match the target."
      ],
      "naturalLanguageExamples": [
        "Show details for subscription 33333333-3333-4333-8333-333333333333.",
        "Verify this record before updating it."
      ],
      "parameters": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Subscription id returned by list_subscriptions or create_subscription."
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "create_subscription",
      "operationId": "createSubscription",
      "method": "POST",
      "path": "/api/v1/subscriptions",
      "purpose": "Create a new tracked subscription for the API key owner.",
      "riskLevel": "medium",
      "requiresConfirmation": true,
      "requiredScope": "write",
      "confirmationPrompt": "I will create a subscription named {name} for {amount} {currency}, billed {period}, with next renewal {nextPaymentDate}. Confirm?",
      "useWhen": [
        "The user asks to add, record, or start tracking a subscription that does not already exist."
      ],
      "doNotUseWhen": [
        "The target subscription already exists. Use update_subscription instead.",
        "The user is only asking for analysis or a recommendation."
      ],
      "naturalLanguageExamples": [
        "Add ChatGPT Plus, 20 USD monthly, renewing 2026-07-17.",
        "Record a yearly GitHub subscription for 48 USD."
      ],
      "parameters": {
        "$ref": "#/$defs/subscriptionWrite"
      }
    },
    {
      "name": "update_subscription",
      "operationId": "updateSubscription",
      "method": "PATCH",
      "path": "/api/v1/subscriptions/{id}",
      "purpose": "Update one or more writable fields on an existing subscription. nextPaymentDate is authoritative; monthly schedules preserve billingAnchorDay across short months.",
      "riskLevel": "medium",
      "requiresConfirmation": true,
      "requiredScope": "write",
      "confirmationPrompt": "I will update subscription {id} with these fields: {patch}. Confirm?",
      "useWhen": [
        "The target subscription has been identified and the user asks to change amount, category, currency, period, nextPaymentDate, customDate, name, notificationEnabled, isTrial, or trialEndsOn."
      ],
      "doNotUseWhen": [
        "The user wants to permanently remove the record. Use delete_subscription after explicit confirmation."
      ],
      "naturalLanguageExamples": [
        "Change Netflix to yearly billing.",
        "Update ChatGPT Plus to 20 USD.",
        "Turn off notifications for this subscription."
      ],
      "parameters": {
        "type": "object",
        "required": [
          "id",
          "patch"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Subscription id returned by list_subscriptions or create_subscription."
          },
          "patch": {
            "$ref": "#/$defs/subscriptionPatch"
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "delete_subscription",
      "operationId": "deleteSubscription",
      "method": "DELETE",
      "path": "/api/v1/subscriptions/{id}",
      "purpose": "Permanently delete a tracked subscription record. This cannot be undone and removes audit history for the record. This is the public API removal path; soft cancel/pause via status is not supported.",
      "riskLevel": "high",
      "requiresConfirmation": true,
      "requiredScope": "write",
      "confirmationPrompt": "I will permanently delete subscription {name} ({id}). This cannot be undone. Confirm?",
      "useWhen": [
        "The user explicitly asks to permanently delete or remove a tracked subscription and confirms the exact target."
      ],
      "doNotUseWhen": [
        "The user wants to change fields on a subscription they still track. Use update_subscription instead."
      ],
      "naturalLanguageExamples": [
        "Delete the duplicate Netflix record permanently.",
        "Remove this subscription and don't keep its history."
      ],
      "parameters": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Subscription id returned by list_subscriptions or create_subscription."
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "get_notification_settings",
      "operationId": "getNotificationSettings",
      "method": "GET",
      "path": "/api/v1/notification-settings",
      "purpose": "Read the account's global reminder settings: enabled, daysBefore, read-only timeZone/locale, and whether Bark is configured. Does not return Bark secrets.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks whether reminders are on, how many days in advance they fire, or whether Bark is configured.",
        "The agent needs current global reminder state before changing enabled or daysBefore."
      ],
      "doNotUseWhen": [
        "The user wants to change a single subscription's reminder toggle; use update_subscription with notificationEnabled.",
        "The user wants to set a Bark URL or send a test push; refuse and point to the web reminders guide."
      ],
      "naturalLanguageExamples": [
        "Are my renewal reminders enabled?",
        "How many days before renewal do you remind me?",
        "Is Bark configured on my account?"
      ],
      "parameters": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      }
    },
    {
      "name": "update_notification_settings",
      "operationId": "updateNotificationSettings",
      "method": "PATCH",
      "path": "/api/v1/notification-settings",
      "purpose": "Update global reminder settings. Writable fields are only enabled and daysBefore (1, 3, 7, or 14). timeZone, locale, Bark URL, and test push are not writable.",
      "riskLevel": "medium",
      "requiresConfirmation": true,
      "requiredScope": "write",
      "confirmationPrompt": "I will update your global reminder settings to {patch}. Confirm?",
      "useWhen": [
        "The user asks to turn global reminders on or off, or change the advance reminder window."
      ],
      "doNotUseWhen": [
        "The user wants to paste a Bark URL, change timezone/locale, or send a test push.",
        "Notification settings do not exist yet (API returns 404); tell the user to configure Bark once in the web app first."
      ],
      "naturalLanguageExamples": [
        "Turn on my renewal reminders.",
        "Remind me 7 days before renewals.",
        "Disable global Bark reminders."
      ],
      "parameters": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Global reminder master switch."
          },
          "daysBefore": {
            "type": "integer",
            "enum": [
              1,
              3,
              7,
              14
            ],
            "description": "Days before renewal or trial end to send a reminder."
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "get_spend_summary",
      "operationId": "getSpendSummary",
      "method": "GET",
      "path": "/api/v1/analytics/summary",
      "purpose": "Return computed spend analytics: subscription counts by status, monthly and yearly totals grouped per currency (never converted across currencies), spend by category, and upcoming renewals within a horizon. Use this instead of listing and summing client-side.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks how much they spend, where their money goes, or what renews soon.",
        "The user wants a monthly or yearly spend breakdown."
      ],
      "doNotUseWhen": [
        "The user wants the raw list of records (use list_subscriptions)."
      ],
      "naturalLanguageExamples": [
        "How much am I spending per month?",
        "What renews in the next two weeks?",
        "Break down my spending by category."
      ],
      "parameters": {
        "type": "object",
        "properties": {
          "horizonDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "default": 30,
            "description": "Window in days for upcomingRenewals (default 30)."
          }
        },
        "additionalProperties": false
      }
    },
    {
      "name": "find_duplicate_subscriptions",
      "operationId": "findDuplicateSubscriptions",
      "method": "GET",
      "path": "/api/v1/analytics/duplicates",
      "purpose": "Return groups of non-cancelled subscriptions that share a normalized name, which usually indicates duplicate tracking or double billing.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks whether they are paying for the same thing twice.",
        "The agent is cleaning up the subscription list."
      ],
      "doNotUseWhen": [
        "The user wants overlapping-but-differently-named services; this only matches by name."
      ],
      "naturalLanguageExamples": [
        "Am I paying for anything twice?",
        "Find duplicate subscriptions."
      ],
      "parameters": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      }
    },
    {
      "name": "get_optimization_suggestions",
      "operationId": "getOptimizationSuggestions",
      "method": "GET",
      "path": "/api/v1/analytics/optimizations",
      "purpose": "Return factual optimization candidates: active monthly subscriptions with their current annualized cost (monthlyToAnnual), and subscriptions whose monthly cost is more than twice the per-currency average (aboveAverageInCurrency). No discount rates are invented; present the figures and let the user decide.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks what they could cut or how to save money.",
        "The user asks which subscriptions are expensive."
      ],
      "doNotUseWhen": [
        "The user wants a guaranteed savings number; the API reports current costs, not negotiated discounts."
      ],
      "naturalLanguageExamples": [
        "My subscription spending is too high, what can I cut?",
        "Which subscriptions could save money on annual billing?"
      ],
      "parameters": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      }
    },
    {
      "name": "list_audit_log",
      "operationId": "listAuditLog",
      "method": "GET",
      "path": "/api/v1/audit",
      "purpose": "List the audit trail of write operations performed through the API, newest first, with before/after state in metadata. Use to show the user what changed or to reconstruct state for an undo.",
      "riskLevel": "low",
      "requiresConfirmation": false,
      "useWhen": [
        "The user asks what was changed, added, or removed.",
        "The agent needs prior state to offer an undo."
      ],
      "doNotUseWhen": [
        "The user wants current subscription state (use list_subscriptions)."
      ],
      "naturalLanguageExamples": [
        "What changes have been made to my subscriptions?",
        "Show the history for subscription 33333333-3333-4333-8333-333333333333."
      ],
      "parameters": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "description": "Maximum number of audit entries to return."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "default": 0,
            "description": "Number of entries to skip from the start."
          },
          "subscriptionId": {
            "type": "string",
            "format": "uuid",
            "description": "Only return audit entries for this subscription."
          }
        },
        "additionalProperties": false
      }
    }
  ],
  "errorRecovery": {
    "shape": {
      "error": {
        "code": "invalid_subscription",
        "message": "Human-readable error",
        "field": "period",
        "suggestedFix": "Use one of the supported billing periods: monthly, yearly, custom.",
        "allowedValues": [
          "monthly",
          "yearly",
          "custom"
        ]
      },
      "requestId": "request-..."
    },
    "agentBehavior": [
      "If field and suggestedFix are present, revise the payload and retry only when the user's intent is unchanged.",
      "If the failed operation is create, update, delete, or update_notification_settings, ask for confirmation again before retrying when the effective write changes.",
      "On 403 insufficient_scope, do not retry: the API key is read-only. Tell the user a write-scoped key is required for this action.",
      "On 400 invalid_query, correct the filter or sort parameter named in field and retry.",
      "Validation errors (400) are rejected before the hourly quota is consumed, so fixing the payload and retrying does not cost rate-limit budget.",
      "On 429, wait the number of seconds in the Retry-After header before retrying.",
      "Use requestId when reporting unresolved API errors.",
      "On 404 notification_settings_not_found, do not invent Bark credentials. Tell the user to open the web app Settings → Notifications and follow /en/user-guide/reminders (Chinese: /zh-CN/user-guide/reminders), then retry.",
      "If the user asks to configure a Bark URL or send a test push, refuse: those are web-only. Point them to the reminders guide links above."
    ]
  },
  "$defs": {
    "dateOnly": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date in YYYY-MM-DD format."
    },
    "subscriptionWrite": {
      "type": "object",
      "required": [
        "name",
        "category",
        "amount",
        "currency",
        "period",
        "nextPaymentDate"
      ],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "category": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        },
        "amount": {
          "type": "number",
          "minimum": 0,
          "maximum": 999999.99,
          "multipleOf": 0.01
        },
        "currency": {
          "type": "string",
          "enum": [
            "CNY",
            "USD",
            "EUR",
            "JPY",
            "GBP",
            "AUD",
            "CAD",
            "CHF",
            "HKD",
            "SGD"
          ]
        },
        "period": {
          "type": "string",
          "enum": [
            "monthly",
            "yearly",
            "custom"
          ]
        },
        "nextPaymentDate": {
          "$ref": "#/$defs/dateOnly"
        },
        "billingAnchorDay": {
          "type": "integer",
          "minimum": 1,
          "maximum": 31
        },
        "customDate": {
          "type": "string",
          "pattern": "^[1-9]\\d*$",
          "description": "Required only when period is custom. Omit for monthly or yearly subscriptions."
        },
        "notificationEnabled": {
          "type": "boolean",
          "default": true
        },
        "isTrial": {
          "type": "boolean",
          "default": false
        },
        "trialEndsOn": {
          "$ref": "#/$defs/dateOnly"
        }
      },
      "additionalProperties": false
    },
    "subscriptionPatch": {
      "type": "object",
      "minProperties": 1,
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "category": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        },
        "amount": {
          "type": "number",
          "minimum": 0,
          "maximum": 999999.99,
          "multipleOf": 0.01
        },
        "currency": {
          "type": "string",
          "enum": [
            "CNY",
            "USD",
            "EUR",
            "JPY",
            "GBP",
            "AUD",
            "CAD",
            "CHF",
            "HKD",
            "SGD"
          ]
        },
        "period": {
          "type": "string",
          "enum": [
            "monthly",
            "yearly",
            "custom"
          ]
        },
        "nextPaymentDate": {
          "$ref": "#/$defs/dateOnly"
        },
        "billingAnchorDay": {
          "type": "integer",
          "minimum": 1,
          "maximum": 31
        },
        "customDate": {
          "type": "string",
          "pattern": "^[1-9]\\d*$"
        },
        "notificationEnabled": {
          "type": "boolean"
        },
        "isTrial": {
          "type": "boolean"
        },
        "trialEndsOn": {
          "$ref": "#/$defs/dateOnly"
        }
      },
      "additionalProperties": false
    }
  }
}
