{
  "openapi": "3.1.0",
  "info": {
    "title": "Sidewisp API",
    "version": "0.3.0",
    "description": "Sidewisp native consumer identity, store entitlement, public article, and scoped article-publishing contracts. Native consumer sessions, internal browser administration, and agent API keys are separate authorization domains. The public website has no consumer login surface."
  },
  "servers": [
    {
      "url": "https://api.sidewisp.com"
    }
  ],
  "tags": [
    {
      "name": "Public articles"
    },
    {
      "name": "Agent publishing"
    },
    {
      "name": "Native consumer identity"
    },
    {
      "name": "Store access"
    },
    {
      "name": "Public preview"
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "getServiceHealth",
        "summary": "Check Sidewisp API availability",
        "tags": ["Public preview"],
        "responses": {
          "200": {
            "description": "Service is available",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/waitlist": {
      "post": {
        "operationId": "joinWaitlist",
        "summary": "Join the Sidewisp private-preview waitlist",
        "tags": ["Public preview"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "source": {
                    "type": "string"
                  },
                  "agents": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": ["email"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing signup accepted"
          },
          "201": {
            "description": "Waitlist signup recorded"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "getConsumerAccount",
        "summary": "Read linked identities and active sessions",
        "tags": ["Native consumer identity"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": {
            "description": "Consumer account",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConsumerAccount" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "delete": {
        "operationId": "deleteConsumerAccount",
        "summary": "Delete Sidewisp account and revoke sessions and access",
        "description": "This does not cancel an App Store or Google Play subscription.",
        "tags": ["Native consumer identity"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": { "description": "Account deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/auth/sessions": {
      "get": {
        "operationId": "listConsumerSessions",
        "summary": "List current consumer session family devices",
        "tags": ["Native consumer identity"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": { "description": "Consumer sessions" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/store/products": {
      "get": {
        "operationId": "listStoreProductMapping",
        "summary": "List allowed App Store and Google Play product-to-plan mappings",
        "tags": ["Store access"],
        "responses": {
          "200": { "description": "Versioned store product catalog" }
        }
      }
    },
    "/v1/billing": {
      "get": {
        "operationId": "getStoreBillingState",
        "summary": "Read normalized store subscription and entitlement state",
        "description": "Read-only. The website has no purchase, plan-change, cancellation, or refund mutation.",
        "tags": ["Store access"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": {
            "description": "Store-managed access state",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StoreBilling" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/entitlements": {
      "get": {
        "operationId": "getConsumerEntitlements",
        "summary": "Read server-enforced product access",
        "tags": ["Store access"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": { "description": "Normalized active entitlement" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/store/{store}/verify": {
      "post": {
        "operationId": "verifyNativeStorePurchase",
        "summary": "Verify and bind a native App Store or Google Play purchase",
        "description": "Raw store proof is accepted only over authenticated transport and is never returned.",
        "tags": ["Store access"],
        "security": [{ "ConsumerSession": [] }],
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          }
        ],
        "responses": {
          "200": { "description": "Authoritative normalized store state" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "description": "Purchase is owned by another account" }
        }
      }
    },
    "/v1/store/restore": {
      "post": {
        "operationId": "restoreNativeStorePurchases",
        "summary": "Re-verify purchases for the authenticated consumer",
        "tags": ["Store access"],
        "security": [{ "ConsumerSession": [] }],
        "responses": {
          "200": { "description": "Restored normalized store state" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/articles": {
      "get": {
        "operationId": "listPublishedArticles",
        "summary": "List published Sidewisp articles",
        "tags": ["Public articles"],
        "parameters": [
          {
            "$ref": "#/components/parameters/Locale"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Published articles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicArticlePage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/v1/articles/sitemap/entries": {
      "get": {
        "operationId": "listPublishedArticleSitemapEntries",
        "summary": "List article slugs and modification dates for the website sitemap",
        "tags": ["Public articles"],
        "responses": {
          "200": {
            "description": "Published sitemap entries"
          }
        }
      }
    },
    "/v1/articles/{slug}": {
      "get": {
        "operationId": "getPublishedArticle",
        "summary": "Read a published article by slug",
        "tags": ["Public articles"],
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          },
          {
            "$ref": "#/components/parameters/Locale"
          }
        ],
        "responses": {
          "200": {
            "description": "Published article",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicArticleDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/admin/articles": {
      "get": {
        "operationId": "listArticlesForAgent",
        "summary": "List drafts and published articles",
        "description": "Requires articles.read.",
        "tags": ["Agent publishing"],
        "security": [
          {
            "AgentApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Locale"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Article administration page"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createArticleForAgent",
        "summary": "Create an article or draft",
        "description": "Requires articles.write. Creating a published or scheduled article additionally requires articles.publish.",
        "tags": ["Agent publishing"],
        "security": [
          {
            "AgentApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateArticleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Article created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminArticle"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Slug already exists for this locale"
          }
        }
      }
    },
    "/v1/admin/articles/{articleId}": {
      "get": {
        "operationId": "getArticleForAgent",
        "summary": "Read one article including Markdown",
        "description": "Requires articles.read.",
        "tags": ["Agent publishing"],
        "security": [
          {
            "AgentApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ArticleId"
          }
        ],
        "responses": {
          "200": {
            "description": "Article"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateArticleForAgent",
        "summary": "Update an article",
        "description": "Requires articles.write. Any isPublished or publishedAt change additionally requires articles.publish.",
        "tags": ["Agent publishing"],
        "security": [
          {
            "AgentApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ArticleId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateArticleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Article updated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteArticleForAgent",
        "summary": "Delete an article",
        "description": "Requires articles.write.",
        "tags": ["Agent publishing"],
        "security": [
          {
            "AgentApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ArticleId"
          }
        ],
        "responses": {
          "204": {
            "description": "Article deleted"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "AgentApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sw_agent_…",
        "description": "A scoped article API key created by a human administrator. Send it only in the Authorization header."
      },
      "ConsumerSession": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Sidewisp access token",
        "description": "Short-lived native access token issued only after verified Google or Apple identity. Refresh material remains device-bound."
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 12
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "Locale": {
        "name": "locale",
        "in": "query",
        "description": "Article language. Public reads default to English.",
        "schema": {
          "type": "string",
          "enum": ["en", "es", "pt", "de", "fr", "it", "ru", "zh", "ja", "ko"],
          "default": "en"
        }
      },
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        }
      },
      "ArticleId": {
        "name": "articleId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Store": {
        "name": "store",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "enum": ["apple", "google"]
        }
      }
    },
    "schemas": {
      "ConsumerAccount": {
        "type": "object",
        "properties": {
          "userId": { "type": "string" },
          "tenantId": { "type": "string" },
          "identities": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": { "type": "string", "enum": ["google", "apple"] },
                "email": { "type": ["string", "null"], "format": "email" },
                "isPrivateRelay": { "type": "boolean" },
                "lastVerifiedAtMs": { "type": "integer" }
              },
              "required": ["provider", "email", "isPrivateRelay", "lastVerifiedAtMs"]
            }
          },
          "sessions": { "type": "array", "items": { "type": "object" } }
        },
        "required": ["userId", "tenantId", "identities", "sessions"]
      },
      "StoreBilling": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": ["none", "pending", "trialing", "active", "grace", "hold", "paused", "canceled", "expired", "refunded", "revoked", "unknown"]
          },
          "subscription": { "type": ["object", "null"] },
          "entitlement": { "type": ["object", "null"] },
          "management": { "type": ["object", "null"] },
          "canPurchaseOnWeb": { "const": false }
        },
        "required": ["status", "subscription", "entitlement", "management", "canPurchaseOnWeb"]
      },
      "ArticleSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "locale": {
            "type": "string",
            "enum": ["en", "es", "pt", "de", "fr", "it", "ru", "zh", "ja", "ko"]
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": ["string", "null"]
          },
          "headerImageUrl": {
            "type": ["string", "null"]
          },
          "publishedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "locale", "slug", "title", "excerpt", "headerImageUrl", "publishedAt", "createdAt", "updatedAt"]
      },
      "PublicArticleDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ArticleSummary"
          },
          {
            "type": "object",
            "properties": {
              "body": {
                "type": "string",
                "description": "Markdown. Raw HTML is not rendered by the Sidewisp website."
              },
              "translations": {
                "type": "array",
                "description": "Published locale variants for this article slug.",
                "items": {
                  "type": "object",
                  "properties": {
                    "locale": {
                      "type": "string",
                      "enum": ["en", "es", "pt", "de", "fr", "it", "ru", "zh", "ja", "ko"]
                    },
                    "slug": {
                      "type": "string"
                    }
                  },
                  "required": ["locale", "slug"]
                }
              }
            },
            "required": ["body", "translations"]
          }
        ]
      },
      "AdminArticle": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PublicArticleDetail"
          },
          {
            "type": "object",
            "properties": {
              "isPublished": {
                "type": "boolean"
              },
              "createdBy": {
                "$ref": "#/components/schemas/Actor"
              },
              "updatedBy": {
                "$ref": "#/components/schemas/Actor"
              }
            },
            "required": ["isPublished", "createdBy", "updatedBy"]
          }
        ]
      },
      "Actor": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["user", "agent_api_key"]
          },
          "id": {
            "type": "string"
          }
        },
        "required": ["type", "id"]
      },
      "PageInfo": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "hasNextPage": {
            "type": "boolean"
          },
          "hasPreviousPage": {
            "type": "boolean"
          }
        },
        "required": ["limit", "offset", "total", "hasNextPage", "hasPreviousPage"]
      },
      "PublicArticlePage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "pageInfo": {
            "$ref": "#/components/schemas/PageInfo"
          }
        },
        "required": ["items", "pageInfo"]
      },
      "CreateArticleInput": {
        "type": "object",
        "properties": {
          "locale": {
            "type": "string",
            "enum": ["en", "es", "pt", "de", "fr", "it", "ru", "zh", "ja", "ko"],
            "default": "en"
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "maxLength": 96
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "excerpt": {
            "type": ["string", "null"],
            "maxLength": 600
          },
          "headerImageUrl": {
            "type": ["string", "null"],
            "maxLength": 2048
          },
          "body": {
            "type": "string",
            "maxLength": 60000,
            "description": "Markdown"
          },
          "isPublished": {
            "type": "boolean",
            "default": false
          },
          "publishedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        },
        "required": ["slug", "title", "body"]
      },
      "UpdateArticleInput": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "locale": {
            "type": "string",
            "enum": ["en", "es", "pt", "de", "fr", "it", "ru", "zh", "ja", "ko"]
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "maxLength": 96
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "excerpt": {
            "type": ["string", "null"],
            "maxLength": 600
          },
          "headerImageUrl": {
            "type": ["string", "null"],
            "maxLength": 2048
          },
          "body": {
            "type": "string",
            "maxLength": 60000
          },
          "isPublished": {
            "type": "boolean"
          },
          "publishedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "requiredScope": {
            "type": "string"
          }
        },
        "required": ["error"]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key lacks a required scope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Article not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
