feat(api): 🎉 Add business profile and catalog management
* Implement endpoints for managing business profiles: - Get business profile - Update business profile * Add catalog management features: - List catalogs - List products in a catalog - Send catalog messages - Send single product messages - Send product list messages * Introduce media upload functionality for sending media files. * Add flow management capabilities: - Deprecate flows * Update API documentation to reflect new endpoints and features.
This commit is contained in:
84
pkg/handlers/business_profile.go
Normal file
84
pkg/handlers/business_profile.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/businessapi"
|
||||
)
|
||||
|
||||
// GetBusinessProfile retrieves the business profile for a Business API account.
|
||||
// POST /api/business-profile {"account_id"}
|
||||
func (h *Handlers) GetBusinessProfile(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := baClient.GetBusinessProfile(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, profile)
|
||||
}
|
||||
|
||||
// UpdateBusinessProfile updates the business profile for a Business API account.
|
||||
// POST /api/business-profile/update {"account_id","about","address","description","email","websites":[],"vertical"}
|
||||
func (h *Handlers) UpdateBusinessProfile(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
About string `json:"about,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Websites []string `json:"websites,omitempty"`
|
||||
Vertical string `json:"vertical,omitempty"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
profile := businessapi.BusinessProfileUpdate{
|
||||
About: req.About,
|
||||
Address: req.Address,
|
||||
Description: req.Description,
|
||||
Email: req.Email,
|
||||
Websites: req.Websites,
|
||||
Vertical: req.Vertical,
|
||||
}
|
||||
|
||||
if err := baClient.UpdateBusinessProfile(r.Context(), profile); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
215
pkg/handlers/catalog.go
Normal file
215
pkg/handlers/catalog.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/utils"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/businessapi"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
// ListCatalogs returns product catalogs for a Business API account.
|
||||
// POST /api/catalogs {"account_id"}
|
||||
func (h *Handlers) ListCatalogs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := baClient.ListCatalogs(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
|
||||
// ListProducts returns products in a specific catalog.
|
||||
// POST /api/catalogs/products {"account_id","catalog_id"}
|
||||
func (h *Handlers) ListProducts(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" {
|
||||
http.Error(w, "catalog_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := baClient.ListProducts(r.Context(), req.CatalogID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
|
||||
// SendCatalogMessage sends a catalog message that shares the full product catalog.
|
||||
// POST /api/send/catalog {"account_id","to","body_text","thumbnail_product_retailer_id"}
|
||||
func (h *Handlers) SendCatalogMessage(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
BodyText string `json:"body_text"`
|
||||
ThumbnailProductRetailerID string `json:"thumbnail_product_retailer_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.BodyText == "" {
|
||||
http.Error(w, "body_text is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendCatalogMessage(r.Context(), jid, req.BodyText, req.ThumbnailProductRetailerID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendSingleProduct sends a single-product interactive message.
|
||||
// POST /api/send/product {"account_id","to","catalog_id","product_retailer_id","body_text","footer_text"}
|
||||
func (h *Handlers) SendSingleProduct(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
ProductRetailerID string `json:"product_retailer_id"`
|
||||
BodyText string `json:"body_text"`
|
||||
FooterText string `json:"footer_text"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" || req.ProductRetailerID == "" || req.BodyText == "" {
|
||||
http.Error(w, "catalog_id, product_retailer_id, and body_text are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendSingleProduct(r.Context(), jid, req.CatalogID, req.ProductRetailerID, req.BodyText, req.FooterText); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendProductList sends a multi-product list message (up to 30 products across up to 10 sections).
|
||||
// POST /api/send/product-list {"account_id","to","header_text","body_text","footer_text","catalog_id","sections":[...]}
|
||||
func (h *Handlers) SendProductList(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
HeaderText string `json:"header_text"`
|
||||
BodyText string `json:"body_text"`
|
||||
FooterText string `json:"footer_text"`
|
||||
CatalogID string `json:"catalog_id"`
|
||||
Sections []businessapi.ProductListSection `json:"sections"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CatalogID == "" || req.HeaderText == "" || req.BodyText == "" || len(req.Sections) == 0 {
|
||||
http.Error(w, "catalog_id, header_text, body_text, and sections are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
jid, err := types.ParseJID(utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid phone number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := baClient.SendProductList(r.Context(), jid, req.HeaderText, req.BodyText, req.FooterText, req.CatalogID, req.Sections); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
@@ -193,6 +193,43 @@ func (h *Handlers) PublishFlow(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// DeprecateFlow transitions a PUBLISHED flow to DEPRECATED.
|
||||
// Deprecated flows block new sessions but remain usable by sessions already in progress.
|
||||
// POST /api/flows/deprecate {"account_id","flow_id"}
|
||||
func (h *Handlers) DeprecateFlow(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
FlowID string `json:"flow_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.FlowID == "" {
|
||||
http.Error(w, "flow_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := baClient.DeprecateFlow(r.Context(), req.FlowID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// DeleteFlow permanently removes a flow.
|
||||
// POST /api/flows/delete {"account_id","flow_id"}
|
||||
func (h *Handlers) DeleteFlow(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
52
pkg/handlers/media_upload.go
Normal file
52
pkg/handlers/media_upload.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UploadMedia uploads a media file to Meta's servers and returns the media ID.
|
||||
// Useful for pre-uploading media before referencing the ID in a subsequent send call.
|
||||
// POST /api/media/upload {"account_id","data"(base64),"mime_type"}
|
||||
func (h *Handlers) UploadMedia(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
Data string `json:"data"`
|
||||
MimeType string `json:"mime_type"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Data == "" || req.MimeType == "" {
|
||||
http.Error(w, "data and mime_type are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
baClient, err := h.getBusinessAPIClient(req.AccountID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mediaData, err := base64.StdEncoding.DecodeString(req.Data)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid base64 data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mediaID, err := baClient.UploadMedia(r.Context(), mediaData, req.MimeType)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]string{"media_id": mediaID})
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
# Static Files
|
||||
|
||||
This directory contains the embedded static files for the WhatsHooked landing page.
|
||||
|
||||
## Files
|
||||
|
||||
- `index.html` - Landing page with API documentation
|
||||
- `logo.png` - WhatsHooked logo (from `assets/image/whatshooked_tp.png`)
|
||||
|
||||
## How It Works
|
||||
|
||||
These files are embedded into the Go binary using `go:embed` directive in `static.go`.
|
||||
|
||||
When you build the server:
|
||||
```bash
|
||||
go build ./cmd/server/
|
||||
```
|
||||
|
||||
The files in this directory are compiled directly into the binary, so the server can run without any external files.
|
||||
|
||||
## Updating the Landing Page
|
||||
|
||||
1. **Edit the HTML:**
|
||||
```bash
|
||||
vim pkg/handlers/static/index.html
|
||||
```
|
||||
|
||||
2. **Rebuild the server:**
|
||||
```bash
|
||||
go build ./cmd/server/
|
||||
```
|
||||
|
||||
3. **Restart the server:**
|
||||
```bash
|
||||
./server -config bin/config.json
|
||||
```
|
||||
|
||||
The changes will be embedded in the new binary.
|
||||
|
||||
## Updating the Logo
|
||||
|
||||
1. **Replace the logo:**
|
||||
```bash
|
||||
cp path/to/new-logo.png pkg/handlers/static/logo.png
|
||||
```
|
||||
|
||||
2. **Rebuild:**
|
||||
```bash
|
||||
go build ./cmd/server/
|
||||
```
|
||||
|
||||
## Routes
|
||||
|
||||
- `GET /` - Serves `index.html`
|
||||
- `GET /static/logo.png` - Serves `logo.png`
|
||||
- `GET /static/*` - Serves any file in this directory
|
||||
|
||||
## Development Tips
|
||||
|
||||
- Files are cached with `Cache-Control: public, max-age=3600` (1 hour)
|
||||
- Force refresh in browser: `Ctrl+Shift+R` or `Cmd+Shift+R`
|
||||
- Changes require rebuild - no hot reload
|
||||
- Keep files small - they're embedded in the binary
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
pkg/handlers/
|
||||
├── static.go # Handler with go:embed directive
|
||||
├── static/
|
||||
│ ├── index.html # Landing page
|
||||
│ ├── logo.png # Logo image
|
||||
│ └── README.md # This file
|
||||
```
|
||||
|
||||
## Benefits of Embedded Files
|
||||
|
||||
✅ **Single binary deployment** - No external dependencies
|
||||
✅ **Fast serving** - Files loaded from memory
|
||||
✅ **No file system access** - Works in restricted environments
|
||||
✅ **Portable** - Binary includes everything
|
||||
✅ **Version controlled** - Static assets tracked with code
|
||||
@@ -384,6 +384,154 @@
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/document</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/audio</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/sticker</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/location</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/contacts</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/interactive</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/template</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/flow</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/reaction</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/messages/read</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>📄 Templates</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates/delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🔄 Flows</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/create</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/get</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/publish</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/deprecate</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>📞 Phone Numbers</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers/request-code</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers/verify-code</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🏪 Catalog / Commerce</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/catalogs</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/catalogs/products</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/catalog</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/product</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/product-list</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🏢 Business Profile</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/business-profile</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/business-profile/update</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🗑️ Media Management</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/media/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/media-delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
|
||||
Reference in New Issue
Block a user