{
  "openapi": "3.1.0",
  "info": {
    "title": "eWasl V1 Public API",
    "version": "1.0.0",
    "summary": "Multi-tenant social media scheduling, analytics, and ads management.",
    "description": "Programmatic access to eWasl. Authenticate every request with `Authorization: Bearer ewasl_<token>`. Generate API keys at https://app.ewasl.com/settings?tab=api-keys (Pro/Enterprise plan required). Every key carries a scope set (`posts:read`, `posts:create`, `ads:read`, `ads:write`, `copilot`); requests outside the granted scope return 403 FORBIDDEN. Responses share a `{ data, meta?, error? }` envelope. Pagination uses `offset` + `limit` query params and a `meta.next` cursor URL. Rate limits surface as `X-RateLimit-Remaining` / `X-RateLimit-Reset` response headers; a 429 includes `Retry-After`.",
    "contact": { "name": "eWasl Support", "email": "support@ewasl.com", "url": "https://app.ewasl.com/contact" },
    "termsOfService": "https://app.ewasl.com/terms",
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://app.ewasl.com/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Posts", "description": "Create, schedule, and manage posts across connected social accounts." },
    { "name": "Connections", "description": "List connected social accounts." },
    { "name": "Analytics", "description": "Optimal-time recommendations and engagement data." },
    { "name": "Webhooks", "description": "Outbound event notifications for post lifecycle events." },
    { "name": "Ads", "description": "Multi-platform ads management (Meta + Google) with anomaly detection, audit, and gated writes." }
  ],
  "paths": {
    "/posts": {
      "get": {
        "tags": ["Posts"],
        "summary": "List posts",
        "description": "List the API key owner's posts. Filter by status or platform. Paginated.",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "PUBLISHED", "FAILED"] } },
          { "name": "platform", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Paginated post list", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedPosts" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        },
        "security": [{ "bearerAuth": ["posts:read"] }]
      },
      "post": {
        "tags": ["Posts"],
        "summary": "Create or publish a post",
        "description": "Create a draft, scheduled, or immediately-published post across one or more connected social accounts.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatePostInput" } } } },
        "responses": {
          "201": { "description": "Post created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PostEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        },
        "security": [{ "bearerAuth": ["posts:create"] }]
      }
    },
    "/posts/schedule": {
      "post": {
        "tags": ["Posts"],
        "summary": "Schedule a post",
        "description": "Convenience endpoint that requires `scheduledAt` and behaves identically to `POST /posts` with `publishNow=false`.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SchedulePostInput" } } } },
        "responses": {
          "201": { "description": "Post scheduled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PostEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        },
        "security": [{ "bearerAuth": ["posts:create"] }]
      }
    },
    "/posts/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "get": {
        "tags": ["Posts"],
        "summary": "Get a post",
        "responses": {
          "200": { "description": "Post detail", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PostEnvelope" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        },
        "security": [{ "bearerAuth": ["posts:read"] }]
      },
      "patch": {
        "tags": ["Posts"],
        "summary": "Update a post",
        "description": "Update a draft / scheduled / failed post. Already-published posts are immutable.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdatePostInput" } } } },
        "responses": {
          "200": { "description": "Post updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PostEnvelope" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        },
        "security": [{ "bearerAuth": ["posts:create"] }]
      },
      "delete": {
        "tags": ["Posts"],
        "summary": "Delete a draft / scheduled post",
        "responses": {
          "200": { "description": "Deleted" },
          "404": { "$ref": "#/components/responses/NotFound" }
        },
        "security": [{ "bearerAuth": ["posts:create"] }]
      }
    },
    "/connections": {
      "get": {
        "tags": ["Connections"],
        "summary": "List connected social accounts",
        "responses": {
          "200": { "description": "Connection list", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConnectionList" } } } }
        },
        "security": [{ "bearerAuth": ["posts:read"] }]
      }
    },
    "/analytics/optimal-times": {
      "get": {
        "tags": ["Analytics"],
        "summary": "Get optimal publish times",
        "parameters": [
          { "name": "platform", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "timezone", "in": "query", "schema": { "type": "string", "default": "UTC" } }
        ],
        "responses": {
          "200": { "description": "Recommended slots" }
        },
        "security": [{ "bearerAuth": ["posts:read"] }]
      }
    },
    "/webhooks": {
      "get": {
        "tags": ["Webhooks"],
        "summary": "List webhook endpoints",
        "responses": {
          "200": { "description": "Webhook list" }
        },
        "security": [{ "bearerAuth": ["posts:read"] }]
      },
      "post": {
        "tags": ["Webhooks"],
        "summary": "Register a webhook endpoint",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterWebhookInput" } } } },
        "responses": {
          "201": { "description": "Webhook registered" }
        },
        "security": [{ "bearerAuth": ["posts:create"] }]
      }
    },
    "/webhooks/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "delete": {
        "tags": ["Webhooks"],
        "summary": "Delete a webhook endpoint",
        "responses": { "200": { "description": "Deleted" } },
        "security": [{ "bearerAuth": ["posts:create"] }]
      }
    },
    "/ads/accounts": {
      "get": {
        "tags": ["Ads"],
        "summary": "List connected ad accounts",
        "responses": { "200": { "description": "Ad accounts" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/accounts/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "patch": {
        "tags": ["Ads"],
        "summary": "Update an ad account (e.g. set autoPilotEnabled)",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "autoPilotEnabled": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Updated" } },
        "security": [{ "bearerAuth": ["ads:write"] }]
      }
    },
    "/ads/brand-profile": {
      "get": {
        "tags": ["Ads"],
        "summary": "Get brand profile",
        "responses": { "200": { "description": "Brand profile or null" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/insights": {
      "get": {
        "tags": ["Ads"],
        "summary": "KPI summary",
        "parameters": [
          { "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 7, "minimum": 1, "maximum": 90 } },
          { "name": "adAccountId", "in": "query", "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": { "200": { "description": "KPI summary" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/reports": {
      "get": {
        "tags": ["Ads"],
        "summary": "List recent AI ads reports",
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10, "maximum": 50 } }],
        "responses": { "200": { "description": "Reports" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/reports/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "get": {
        "tags": ["Ads"],
        "summary": "Get a single ads report",
        "responses": { "200": { "description": "Report" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/compare": {
      "get": {
        "tags": ["Ads"],
        "summary": "Compare two periods",
        "parameters": [
          { "name": "currentDays", "in": "query", "schema": { "type": "integer", "default": 7 } },
          { "name": "previousDays", "in": "query", "schema": { "type": "integer", "default": 7 } },
          { "name": "adAccountId", "in": "query", "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": { "200": { "description": "Comparison" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/top-performers": {
      "get": {
        "tags": ["Ads"],
        "summary": "Top or bottom performing campaigns",
        "parameters": [
          { "name": "metric", "in": "query", "schema": { "type": "string", "enum": ["roas", "spend", "conversions", "revenue", "ctr"], "default": "roas" } },
          { "name": "direction", "in": "query", "schema": { "type": "string", "enum": ["top", "bottom"], "default": "top" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 5, "maximum": 20 } },
          { "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 7, "maximum": 90 } },
          { "name": "adAccountId", "in": "query", "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": { "200": { "description": "Ranked campaigns" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/breakdown": {
      "get": {
        "tags": ["Ads"],
        "summary": "Spend breakdown by ad platform",
        "parameters": [{ "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 7 } }],
        "responses": { "200": { "description": "Breakdown by platform" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/activity": {
      "get": {
        "tags": ["Ads"],
        "summary": "List ad-action audit events",
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 100 } }],
        "responses": { "200": { "description": "Audit list" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/audit-verify": {
      "get": {
        "tags": ["Ads"],
        "summary": "Verify the tamper-evident hash chain",
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 500, "maximum": 1000 } }],
        "responses": { "200": { "description": "Verification result" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/campaigns": {
      "get": {
        "tags": ["Ads"],
        "summary": "List campaigns",
        "parameters": [
          { "name": "adAccountId", "in": "query", "schema": { "type": "string", "format": "uuid" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 500 } }
        ],
        "responses": { "200": { "description": "Campaigns" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/campaigns/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "get": {
        "tags": ["Ads"],
        "summary": "Get campaign drill-down",
        "parameters": [{ "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 30, "maximum": 90 } }],
        "responses": { "200": { "description": "Campaign + insights" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/campaigns/{id}/pause": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "post": {
        "tags": ["Ads"],
        "summary": "Pause a campaign (dry-run by default)",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" }, "dryRun": { "type": "boolean", "default": true } } } } } },
        "responses": { "200": { "description": "Diff (dry-run) or commit result" } },
        "security": [{ "bearerAuth": ["ads:write"] }]
      }
    },
    "/ads/campaigns/{id}/resume": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
      "post": {
        "tags": ["Ads"],
        "summary": "Resume a campaign (dry-run by default)",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" }, "dryRun": { "type": "boolean", "default": true } } } } } },
        "responses": { "200": { "description": "Diff (dry-run) or commit result" } },
        "security": [{ "bearerAuth": ["ads:write"] }]
      }
    },
    "/ads/anomalies": {
      "get": {
        "tags": ["Ads"],
        "summary": "Detect or list anomalies",
        "parameters": [
          { "name": "fresh", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "When true, run live detection. When false, return cached results from the cron." },
          { "name": "baselineDays", "in": "query", "schema": { "type": "integer", "default": 14, "minimum": 7, "maximum": 30 } }
        ],
        "responses": { "200": { "description": "Anomaly findings" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/creative-fatigue": {
      "get": {
        "tags": ["Ads"],
        "summary": "Detect creative fatigue",
        "parameters": [{ "name": "adAccountId", "in": "query", "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "Fatigue findings" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/budget-shift": {
      "post": {
        "tags": ["Ads"],
        "summary": "Propose a cross-platform budget shift (read-only)",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["totalDailyBudget"], "properties": { "totalDailyBudget": { "type": "number" }, "adAccountIds": { "type": "array", "items": { "type": "string", "format": "uuid" } }, "windowDays": { "type": "integer", "default": 14 } } } } } },
        "responses": { "200": { "description": "Proposal — does not commit" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/policy-check": {
      "post": {
        "tags": ["Ads"],
        "summary": "Pre-publish policy check on proposed creative",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["creative"], "properties": { "creative": { "type": "string", "maxLength": 3000 }, "imageDescription": { "type": "string" }, "targetPlatform": { "type": "string", "enum": ["META", "GOOGLE", "BOTH"], "default": "BOTH" }, "market": { "type": "string" } } } } } },
        "responses": { "200": { "description": "ALLOW / WARN / BLOCK with reasons" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/dialect-eval": {
      "get": {
        "tags": ["Ads"],
        "summary": "Arabic dialect quality leaderboard",
        "parameters": [{ "name": "sinceDays", "in": "query", "schema": { "type": "integer", "default": 30, "maximum": 90 } }],
        "responses": { "200": { "description": "Leaderboard" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/signals": {
      "get": {
        "tags": ["Ads"],
        "summary": "Recent ad webhook signals",
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } }],
        "responses": { "200": { "description": "Signal list" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/memory": {
      "get": {
        "tags": ["Ads"],
        "summary": "Search or list user memory",
        "description": "Provide `query` for semantic search; omit it for chronological listing.",
        "parameters": [
          { "name": "query", "in": "query", "schema": { "type": "string" } },
          { "name": "memoryType", "in": "query", "schema": { "type": "string", "enum": ["brand_voice", "preference", "baseline", "failed_action", "note"] } },
          { "name": "k", "in": "query", "schema": { "type": "integer", "default": 5, "maximum": 50 } },
          { "name": "minSimilarity", "in": "query", "schema": { "type": "number", "minimum": 0, "maximum": 1, "default": 0 } }
        ],
        "responses": { "200": { "description": "Memory entries" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/incremental-pilot": {
      "get": {
        "tags": ["Ads"],
        "summary": "Incremental pilot proposal (read-only)",
        "parameters": [{ "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 7, "minimum": 3, "maximum": 30 } }],
        "responses": { "200": { "description": "Allocation proposal" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    },
    "/ads/winning-creatives": {
      "get": {
        "tags": ["Ads"],
        "summary": "Top-K winning creatives",
        "parameters": [
          { "name": "k", "in": "query", "schema": { "type": "integer", "default": 5, "maximum": 20 } },
          { "name": "windowDays", "in": "query", "schema": { "type": "integer", "default": 90, "minimum": 7, "maximum": 365 } }
        ],
        "responses": { "200": { "description": "Winning creatives" } },
        "security": [{ "bearerAuth": ["ads:read"] }]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ewasl_<token>",
        "description": "Generate at https://app.ewasl.com/settings?tab=api-keys. Tokens carry one or more scopes: posts:read, posts:create, ads:read, ads:write, copilot."
      }
    },
    "parameters": {
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 100 } },
      "Offset": { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0 } }
    },
    "responses": {
      "Unauthorized": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "Forbidden": { "description": "API key lacks the required scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "BadRequest": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "NotFound": { "description": "Resource not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "RateLimited": { "description": "Rate limit exceeded — see Retry-After header", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "examples": ["UNAUTHORIZED", "FORBIDDEN", "RATE_LIMITED", "PLAN_REQUIRED", "VALIDATION_ERROR", "NOT_FOUND", "INTERNAL_ERROR"] },
              "message": { "type": "string" },
              "details": {}
            }
          }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "required": ["total", "offset", "limit", "next"],
        "properties": {
          "total": { "type": "integer" },
          "offset": { "type": "integer" },
          "limit": { "type": "integer" },
          "next": { "type": ["string", "null"], "description": "Next-page URL or null when at the end." }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "content": { "type": "string" },
          "status": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "PUBLISHED", "FAILED"] },
          "scheduledAt": { "type": ["string", "null"], "format": "date-time" },
          "publishedAt": { "type": ["string", "null"], "format": "date-time" },
          "mediaUrls": { "type": "array", "items": { "type": "string" } },
          "platforms": { "type": "array", "items": { "type": "string" } },
          "platformCaptions": { "type": "object", "additionalProperties": { "type": "string" } },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "PaginatedPosts": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Post" } },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        }
      },
      "PostEnvelope": {
        "type": "object",
        "required": ["data"],
        "properties": { "data": { "$ref": "#/components/schemas/Post" } }
      },
      "CreatePostInput": {
        "type": "object",
        "required": ["content", "accountIds"],
        "properties": {
          "content": { "type": "string", "minLength": 1, "maxLength": 5000 },
          "accountIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1 },
          "publishNow": { "type": "boolean", "default": false },
          "isDraft": { "type": "boolean", "default": false },
          "scheduledAt": { "type": "string", "format": "date-time" },
          "mediaUrls": { "type": "array", "items": { "type": "string", "format": "uri" } },
          "platformCaptions": { "type": "object", "additionalProperties": { "type": "string" } }
        },
        "additionalProperties": false
      },
      "SchedulePostInput": {
        "allOf": [
          { "$ref": "#/components/schemas/CreatePostInput" },
          { "type": "object", "required": ["scheduledAt"] }
        ]
      },
      "UpdatePostInput": {
        "type": "object",
        "properties": {
          "content": { "type": "string" },
          "scheduledAt": { "type": "string", "format": "date-time" },
          "isDraft": { "type": "boolean" },
          "mediaUrls": { "type": "array", "items": { "type": "string", "format": "uri" } },
          "accountIds": { "type": "array", "items": { "type": "string", "format": "uuid" } },
          "platformCaptions": { "type": "object", "additionalProperties": { "type": "string" } }
        },
        "additionalProperties": false
      },
      "Connection": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "platform": { "type": "string" },
          "displayName": { "type": "string" },
          "status": { "type": "string", "enum": ["ACTIVE", "EXPIRED", "REVOKED"] },
          "connectedAt": { "type": "string", "format": "date-time" }
        }
      },
      "ConnectionList": {
        "type": "object",
        "required": ["data"],
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Connection" } }
        }
      },
      "RegisterWebhookInput": {
        "type": "object",
        "required": ["url", "events"],
        "properties": {
          "url": { "type": "string", "format": "uri", "pattern": "^https://" },
          "events": { "type": "array", "items": { "type": "string", "enum": ["post.published", "post.failed", "post.partially_published", "post.scheduled"] }, "minItems": 1 },
          "description": { "type": "string", "maxLength": 200 }
        },
        "additionalProperties": false
      }
    }
  }
}
