Files
cannaiq/backend/openapi.yaml
2025-11-28 19:45:44 -07:00

736 lines
18 KiB
YAML

openapi: 3.0.3
info:
title: Dutchie Analytics API
description: |
Comprehensive REST API for Arizona dispensary data aggregation and analytics.
## Features
- Product data with high-resolution images (2000x2000)
- Brand tracking and market share analysis
- Daily specials grouped by date
- Price history and trend analysis
- Inventory tracking (in stock/out of stock)
- Real-time scraping with proxy management
## Authentication
All endpoints require JWT authentication via Bearer token.
## Rate Limiting
- Standard users: 100 requests/minute
- Admin users: 500 requests/minute
## Image Storage
All images are stored in MinIO (S3-compatible) and served via CDN.
version: 1.0.0
contact:
name: API Support
email: support@dutchieanalytics.com
license:
name: Proprietary
servers:
- url: http://localhost:3010/api
description: Development server
- url: https://api.dutchieanalytics.com/api
description: Production server
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Store:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: "Curaleaf - Phoenix"
slug:
type: string
example: "curaleaf-az-phoenix"
dutchie_url:
type: string
format: uri
example: "https://dutchie.com/dispensary/curaleaf-phoenix"
logo_url:
type: string
format: uri
nullable: true
active:
type: boolean
example: true
scrape_enabled:
type: boolean
example: true
product_count:
type: integer
example: 345
category_count:
type: integer
example: 12
last_scraped_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Product:
type: object
properties:
id:
type: integer
example: 1
dutchie_product_id:
type: string
example: "12345"
store_id:
type: integer
example: 1
category_id:
type: integer
nullable: true
example: 5
name:
type: string
example: "Blue Dream 3.5g"
brand:
type: string
nullable: true
example: "Cresco Labs"
description:
type: string
nullable: true
price:
type: number
format: float
nullable: true
example: 45.00
weight:
type: string
nullable: true
example: "3.5g"
thc_percentage:
type: number
format: float
nullable: true
example: 24.5
cbd_percentage:
type: number
format: float
nullable: true
example: 0.5
strain_type:
type: string
nullable: true
enum: [indica, sativa, hybrid]
example: "hybrid"
in_stock:
type: boolean
example: true
image_url:
type: string
format: uri
nullable: true
description: "Original Dutchie image URL"
image_url_full:
type: string
format: uri
nullable: true
description: "High-resolution image from MinIO (2000x2000)"
local_image_path:
type: string
nullable: true
description: "MinIO storage path"
thumbnail_path:
type: string
nullable: true
description: "Thumbnail image path (300x300)"
medium_path:
type: string
nullable: true
description: "Medium image path (800x800)"
full_path:
type: string
nullable: true
description: "Full resolution image path (2000x2000)"
dutchie_url:
type: string
format: uri
nullable: true
store_name:
type: string
example: "Curaleaf - Phoenix"
category_name:
type: string
nullable: true
example: "Flower"
last_seen_at:
type: string
format: date-time
created_at:
type: string
format: date-time
Category:
type: object
properties:
id:
type: integer
example: 1
store_id:
type: integer
example: 1
parent_id:
type: integer
nullable: true
example: null
name:
type: string
example: "Flower"
slug:
type: string
example: "flower"
dutchie_url:
type: string
format: uri
nullable: true
display_order:
type: integer
example: 1
product_count:
type: integer
example: 145
parent_name:
type: string
nullable: true
created_at:
type: string
format: date-time
Brand:
type: object
properties:
name:
type: string
example: "Cresco Labs"
Special:
type: object
properties:
id:
type: integer
store_id:
type: integer
product_id:
type: integer
nullable: true
name:
type: string
example: "$5 OFF Blue Dream"
description:
type: string
nullable: true
discount_amount:
type: number
format: float
nullable: true
discount_percentage:
type: number
format: float
nullable: true
valid_date:
type: string
format: date
product_name:
type: string
nullable: true
product_image:
type: string
format: uri
nullable: true
Error:
type: object
properties:
error:
type: string
example: "Resource not found"
security:
- bearerAuth: []
paths:
/auth/login:
post:
tags:
- Authentication
summary: Login
description: Authenticate user and receive JWT token
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
format: email
example: "admin@example.com"
password:
type: string
format: password
example: "password123"
responses:
'200':
description: Login successful
content:
application/json:
schema:
type: object
properties:
token:
type: string
user:
type: object
properties:
id:
type: integer
email:
type: string
role:
type: string
enum: [superadmin, admin, user]
'401':
description: Invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/stores:
get:
tags:
- Stores
summary: Get all stores
description: Retrieve list of all dispensary stores with product and category counts
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
stores:
type: array
items:
$ref: '#/components/schemas/Store'
post:
tags:
- Stores
summary: Create store
description: Add a new dispensary store (admin only)
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
- slug
- dutchie_url
properties:
name:
type: string
slug:
type: string
dutchie_url:
type: string
format: uri
active:
type: boolean
default: true
scrape_enabled:
type: boolean
default: true
responses:
'201':
description: Store created
content:
application/json:
schema:
$ref: '#/components/schemas/Store'
'401':
description: Unauthorized
/stores/{id}:
get:
tags:
- Stores
summary: Get single store
description: Retrieve detailed information about a specific store
parameters:
- name: id
in: path
required: true
schema:
type: integer
example: 1
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/Store'
'404':
description: Store not found
put:
tags:
- Stores
summary: Update store
description: Update store information (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
slug:
type: string
dutchie_url:
type: string
active:
type: boolean
scrape_enabled:
type: boolean
responses:
'200':
description: Store updated
content:
application/json:
schema:
$ref: '#/components/schemas/Store'
delete:
tags:
- Stores
summary: Delete store
description: Delete a store (superadmin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Store deleted
/stores/{id}/brands:
get:
tags:
- Stores
summary: Get store brands
description: Get list of all brands available at a store
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
brands:
type: array
items:
type: string
example: ["Cresco Labs", "Select", "Timeless"]
/stores/{id}/specials:
get:
tags:
- Stores
summary: Get store specials
description: Get daily specials for a store on a specific date
parameters:
- name: id
in: path
required: true
schema:
type: integer
- name: date
in: query
required: false
schema:
type: string
format: date
description: Date for specials (defaults to today)
example: "2025-01-15"
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
specials:
type: array
items:
$ref: '#/components/schemas/Special'
date:
type: string
format: date
/stores/{id}/scrape:
post:
tags:
- Stores
summary: Trigger store scrape
description: Start scraping process for a store (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
content:
application/json:
schema:
type: object
properties:
parallel:
type: integer
default: 3
description: Number of parallel scrapers
userAgent:
type: string
description: Custom user agent (optional)
responses:
'200':
description: Scrape started
content:
application/json:
schema:
type: object
properties:
message:
type: string
parallel:
type: integer
userAgent:
type: string
/stores/{id}/discover-categories:
post:
tags:
- Stores
summary: Discover categories
description: Discover and import categories from Dutchie menu (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Category discovery started
/products:
get:
tags:
- Products
summary: Get products
description: |
Retrieve products with filtering, searching, and pagination.
## Field Selection
Use the `fields` parameter to select specific fields:
- `fields=id,name,price` - Only return specified fields
- Reduces payload size and improves performance
parameters:
- name: store_id
in: query
schema:
type: integer
description: Filter by store ID
- name: category_id
in: query
schema:
type: integer
description: Filter by category ID
- name: in_stock
in: query
schema:
type: boolean
description: Filter by stock availability
- name: search
in: query
schema:
type: string
description: Search by product name or brand
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 1000
description: Number of results per page
- name: offset
in: query
schema:
type: integer
default: 0
description: Pagination offset
- name: fields
in: query
schema:
type: string
description: Comma-separated list of fields to return
example: "id,name,price,brand,in_stock,image_url_full"
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
products:
type: array
items:
$ref: '#/components/schemas/Product'
total:
type: integer
description: Total number of products matching filters
limit:
type: integer
offset:
type: integer
/products/{id}:
get:
tags:
- Products
summary: Get single product
description: Retrieve detailed information about a specific product
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
product:
$ref: '#/components/schemas/Product'
/categories:
get:
tags:
- Categories
summary: Get categories
description: Retrieve flat list of categories
parameters:
- name: store_id
in: query
schema:
type: integer
description: Filter by store ID
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
categories:
type: array
items:
$ref: '#/components/schemas/Category'
/categories/tree:
get:
tags:
- Categories
summary: Get category tree
description: Retrieve hierarchical category structure for a store
parameters:
- name: store_id
in: query
required: true
schema:
type: integer
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
tree:
type: array
items:
allOf:
- $ref: '#/components/schemas/Category'
- type: object
properties:
children:
type: array
items:
$ref: '#/components/schemas/Category'
tags:
- name: Authentication
description: User authentication endpoints
- name: Stores
description: Dispensary store management
- name: Products
description: Product catalog and inventory
- name: Categories
description: Product categories and hierarchy