Files
dashback/docs/api/openapi.json

2132 lines
55 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"openapi": "3.0.3",
"info": {
"title": "Dash API",
"version": "1.0.0",
"description": "Backend API for Dash — Google SSO auth, MSSQL, session cookies.\n\nBrowser clients must send cookies (`credentials: 'include'`). Sessions use an HTTP-only `token` cookie (`Secure`, `SameSite=Strict`, 24h); HTTPS is required for login."
},
"servers": [
{
"url": "http://localhost:4208/api",
"description": "Local default (PORT / .env)"
}
],
"tags": [
{
"name": "Auth",
"description": "Google SSO and session"
},
{
"name": "Health",
"description": "Connectivity checks"
},
{
"name": "Article",
"description": "Articles under categories"
},
{
"name": "Category",
"description": "Category tree and membership"
},
{
"name": "Company",
"description": "Company CRUD"
},
{
"name": "Image",
"description": "Content-addressed AVIF by fullHash"
}
],
"paths": {
"/image/{fullHash}": {
"get": {
"tags": [
"Image"
],
"summary": "Full image by hash",
"description": "Returns the full AVIF by `pic.hash` (SHA-256 hex).\nAuth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "image/get.md",
"parameters": [
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
},
"description": "SHA-256 hex of the full image bytes"
}
],
"responses": {
"200": {
"description": "AVIF image bytes",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "private, max-age=31536000, immutable"
}
},
"ETag": {
"schema": {
"type": "string"
}
}
},
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"304": {
"description": "Not modified (If-None-Match matched ETag)"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/thumb/{fullHash}": {
"get": {
"tags": [
"Image"
],
"summary": "Thumb image by hash",
"description": "Returns the thumb AVIF for `pic.hash` (fullHash — not thumbhash).\nAuth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "thumb/get.md",
"parameters": [
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
},
"description": "SHA-256 hex of the full image bytes"
}
],
"responses": {
"200": {
"description": "AVIF thumb bytes",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"example": "private, max-age=31536000, immutable"
}
},
"ETag": {
"schema": {
"type": "string"
}
}
},
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"304": {
"description": "Not modified (If-None-Match matched ETag)"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/health": {
"get": {
"tags": [
"Health"
],
"summary": "Database health check",
"description": "Verifies the database client can establish a connection.",
"security": [],
"x-markdown": "health.md",
"responses": {
"200": {
"description": "Database reachable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"example": true
}
}
}
}
}
},
"503": {
"description": "Database unreachable",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"example": false
},
"error": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/company": {
"get": {
"tags": [
"Company"
],
"summary": "List companies",
"description": "Lists companies for the authenticated user.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/list.md",
"responses": {
"200": {
"description": "Company list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Company"
}
}
}
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Company"
],
"summary": "Create company",
"description": "Creates a company and selects it for the current user.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"iso639",
"name",
"taxrate",
"reducedtaxrate"
],
"properties": {
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
},
"example": {
"iso639": "de",
"name": "Acme GmbH",
"taxrate": 19,
"reducedtaxrate": 7
}
}
}
}
},
"responses": {
"201": {
"description": "Created; selected company updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"company": {
"$ref": "#/components/schemas/Company"
},
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Company name not available",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company/{id}": {
"put": {
"tags": [
"Company"
],
"summary": "Update company",
"description": "Updates a company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"iso639",
"name",
"taxrate",
"reducedtaxrate"
],
"properties": {
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"company": {
"$ref": "#/components/schemas/Company"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Company name not available",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Company"
],
"summary": "Delete company",
"description": "Deletes a company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "company/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/tree": {
"get": {
"tags": [
"Category"
],
"summary": "Category tree",
"description": "Returns the category tree for the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/tree.md",
"responses": {
"200": {
"description": "Tree and articles for the selected company",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"languageId": {
"type": "integer"
},
"tree": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
},
"articles": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category": {
"post": {
"tags": [
"Category"
],
"summary": "Create category",
"description": "Creates a category under an authorized parent.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"parentId",
"name"
],
"properties": {
"parentId": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"example": {
"parentId": 1,
"name": "New category"
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"category": {
"$ref": "#/components/schemas/Category"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Parent not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/{id}/articles": {
"get": {
"tags": [
"Category"
],
"summary": "Articles in category",
"description": "Returns articles in a category the user is authorized for.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/articles.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"responses": {
"200": {
"description": "Articles list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"articles": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
}
},
"400": {
"description": "Invalid id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/category/{id}": {
"delete": {
"tags": [
"Category"
],
"summary": "Delete category",
"description": "Deletes a category the user is authorized for (not the company root).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "category/delete.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id"
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid id or cannot delete root",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article": {
"post": {
"tags": [
"Article"
],
"summary": "Create article",
"description": "Creates an article under an authorized category (`parentId`).",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/create.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"parentId",
"name"
],
"properties": {
"parentId": {
"type": "integer",
"description": "Category id"
},
"name": {
"type": "string"
}
},
"example": {
"parentId": 10,
"name": "New article"
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleSummary"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized for parent category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Parent not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}": {
"get": {
"tags": [
"Article"
],
"summary": "Get article by SKU",
"description": "Returns article detail by SKU if the user may access its category.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/get.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"200": {
"description": "Article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Missing sku",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"patch": {
"tags": [
"Article"
],
"summary": "Update article by SKU",
"description": "Updates an article name by SKU; returns the same detail shape as GET.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/update.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
},
"example": {
"name": "Updated name"
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"delete": {
"tags": [
"Article"
],
"summary": "Delete article by SKU",
"description": "Deletes the article when it has no child articles. Cascades related rows;\nnulls `manifestarticle.article_id` and appends a delete comment.\nAuth like GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/delete.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Article has children",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/prices": {
"put": {
"tags": [
"Article"
],
"summary": "Replace POS prices by SKU",
"description": "Replace-all prices for the company POS customer group.\nEach entry: `price` (net), `tax` (rate, ensured via tax.ensure), optional `minmultiplier` (default 1).\nEmpty `prices` clears all POS prices. Returns the same shape as GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/set-prices.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"prices"
],
"properties": {
"prices": {
"type": "array",
"items": {
"type": "object",
"required": [
"price",
"tax"
],
"properties": {
"price": {
"type": "number",
"description": "Net price"
},
"tax": {
"type": "number",
"description": "Tax rate (e.g. 19)"
},
"minmultiplier": {
"type": "number",
"description": "Defaults to 1; must be unique per request"
}
}
}
}
},
"example": {
"prices": [
{
"price": 10.5,
"tax": 19
},
{
"price": 9.9,
"tax": 19,
"minmultiplier": 10
}
]
}
}
}
}
},
"responses": {
"200": {
"description": "Updated article detail",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"article": {
"$ref": "#/components/schemas/ArticleDetail"
}
}
}
}
}
},
"400": {
"description": "Invalid prices or POS customer group required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/image": {
"post": {
"tags": [
"Article"
],
"summary": "Add article image",
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`).\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.\nAuth like GET by SKU.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/add-image.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
},
"description": "Article SKU"
}
],
"requestBody": {
"required": true,
"content": {
"image/avif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"responses": {
"201": {
"description": "Image stored and linked",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"image": {
"$ref": "#/components/schemas/ArticleImage"
}
}
}
}
}
},
"400": {
"description": "Missing body or invalid image",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/image/{fullHash}": {
"delete": {
"tags": [
"Article"
],
"summary": "Delete article image",
"description": "Removes `articlepic` and the `pic` row for this article (`fullHash`). Auth like GET by SKU.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/delete-image.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "path",
"name": "fullHash",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
}
}
],
"responses": {
"204": {
"description": "Deleted"
},
"400": {
"description": "Invalid hash",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article or image not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/article/{sku}/images/order": {
"put": {
"tags": [
"Article"
],
"summary": "Reorder article images",
"description": "Sets `articlepic.sort` to `0..n-1` in the given order.\n`fullHashes` must list every image of the article exactly once.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "article/order-images.md",
"parameters": [
{
"in": "path",
"name": "sku",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"fullHashes"
],
"properties": {
"fullHashes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-fA-F0-9]{64}$"
}
}
},
"example": {
"fullHashes": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
}
}
}
}
},
"responses": {
"200": {
"description": "New order",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"fullHashes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"400": {
"description": "Invalid image order",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Article not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/auth/selected-company": {
"put": {
"tags": [
"Auth"
],
"summary": "Set selected company",
"description": "Sets the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "auth/selected-company.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"companyId"
],
"properties": {
"companyId": {
"type": "integer",
"minimum": 1
}
},
"example": {
"companyId": 1
}
}
}
}
},
"responses": {
"200": {
"description": "Updated user",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Invalid companyId",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/auth/me": {
"get": {
"tags": [
"Auth"
],
"summary": "Current user",
"description": "Returns the authenticated user from the session cookie and the Google client ID. Unauthenticated calls still receive `googleClientId` with 401.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "auth/me.md",
"responses": {
"200": {
"description": "Authenticated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/User"
},
"googleClientId": {
"type": "string"
}
}
}
}
}
},
"401": {
"description": "Unauthorized — still includes `googleClientId` for the frontend",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"googleClientId": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/auth/logout": {
"post": {
"tags": [
"Auth"
],
"summary": "Logout",
"description": "Clears the HTTP-only `token` cookie set at login.",
"security": [],
"x-markdown": "auth/logout.md",
"responses": {
"200": {
"description": "Logged out",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Logged out successfully"
}
}
}
}
}
}
}
}
},
"/auth/login": {
"post": {
"tags": [
"Auth"
],
"summary": "Login with Google ID token",
"description": "Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`), and sets an HTTP-only `token` cookie.",
"security": [],
"x-markdown": "auth/login.md",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"idToken"
],
"properties": {
"idToken": {
"type": "string",
"description": "Google ID token from Google Identity Services"
}
},
"example": {
"idToken": "<google-id-token>"
}
}
}
}
},
"responses": {
"200": {
"description": "Login successful; `token` cookie set",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Login successful"
},
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Missing token or Google client not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid Google token or account",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Email not on allowlist",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"cookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "token",
"description": "HTTP-only session cookie set by POST /auth/login"
}
},
"schemas": {
"Error": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "string"
}
}
},
"User": {
"type": "object",
"required": [
"id",
"email",
"selectedCompanyId"
],
"properties": {
"id": {
"type": "integer"
},
"email": {
"type": "string",
"format": "email"
},
"selectedCompanyId": {
"type": "integer",
"nullable": true
},
"picture": {
"type": "string",
"format": "uri"
}
}
},
"Company": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"iso639": {
"type": "string"
},
"name": {
"type": "string"
},
"taxrate": {
"type": "number"
},
"reducedtaxrate": {
"type": "number"
}
}
},
"Category": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"parentId": {
"type": "integer",
"nullable": true
},
"sort": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"ArticleSummary": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"parentId": {
"type": "integer"
},
"name": {
"type": "string"
},
"sku": {
"type": "string"
}
}
},
"ArticleDetail": {
"type": "object",
"description": "Full article payload (unit, brand, categoryIds, photos, eans, prices, …)",
"additionalProperties": true
},
"ArticleImage": {
"type": "object",
"required": [
"picId",
"sort",
"full",
"thumb",
"hash",
"thumbHash"
],
"properties": {
"picId": {
"type": "integer"
},
"sort": {
"type": "integer"
},
"full": {
"type": "object",
"required": [
"w",
"h",
"size"
],
"properties": {
"w": {
"type": "integer"
},
"h": {
"type": "integer"
},
"size": {
"type": "integer",
"description": "Byte length of full AVIF (as-is when already AVIF ≤1000×1000)"
}
}
},
"thumb": {
"type": "object",
"required": [
"w",
"h"
],
"properties": {
"w": {
"type": "integer"
},
"h": {
"type": "integer"
}
}
},
"hash": {
"type": "string",
"description": "SHA-256 hex of full image bytes"
},
"thumbHash": {
"type": "string",
"description": "Base64 thumbhash of thumb preview"
}
}
}
}
}
}