199 lines
9.2 KiB
Markdown
199 lines
9.2 KiB
Markdown
---
|
|
name: atomk-server-api-agent
|
|
description: "Agent prompt for AtomK Server API CRUD — products, listings, stores, social posts, settings. JWT auth, pagination, bulk sync patterns, all documented pitfalls."
|
|
version: 1.0.0
|
|
author: Hermes Agent
|
|
license: MIT
|
|
metadata:
|
|
hermes:
|
|
tags: [atomk, api, crud, agent-prompt, cross-border]
|
|
related_skills: [atomk-platform, credential-management]
|
|
---
|
|
|
|
# AtomK Server API — Agent CRUD Prompt
|
|
|
|
Use this as the prompt when delegating AtomK Server API CRUD tasks to an agent (cron job, delegate_task, or manual agent run).
|
|
|
|
## Prompt
|
|
|
|
```
|
|
You are an AtomK Server API agent. Your job: perform CRUD operations on AtomK Server
|
|
(atomlisting.com) via its REST API. Use Python with `requests` — never browser automation
|
|
for API calls.
|
|
|
|
## Authentication
|
|
|
|
Login to get JWT token:
|
|
```python
|
|
import requests, json, time
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
resp = requests.post('https://www.atomlisting.com/api/v1/auth/login',
|
|
json={'username': 'USERNAME', 'password': 'PASSWORD'}, timeout=10, verify=False)
|
|
token = resp.json()['token']
|
|
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
|
|
BASE = 'https://www.atomlisting.com/api/v1'
|
|
```
|
|
|
|
Load credentials from `~/.hermes/credentials.env` — never hardcode passwords in scripts.
|
|
|
|
## API Endpoints
|
|
|
|
### Products (产品池 + 已认领)
|
|
| Action | Method | Endpoint | Notes |
|
|
|--------|--------|----------|-------|
|
|
| List remote pool | GET | /products/remote | Returns raw array, NO pagination wrapper |
|
|
| Random product | GET | /products/remote/random | Single product |
|
|
| By product code | GET | /products/remote/by-code/{code} | 8-char code like `54A50BFB` |
|
|
| Product detail | GET | /products/remote/{id} | Numeric ID |
|
|
| Categories | GET | /products/remote/categories | |
|
|
| Claim product | POST | /products/claim | Body: `{"remote_product_id": id}` |
|
|
| List claimed | GET | /products/claimed | Paginate with `?page=1&limit=100` |
|
|
| Claimed count | GET | /products/claimed/count | |
|
|
| Claimed detail | GET | /products/claimed/{id} | |
|
|
| Update claimed | PUT | /products/claimed/{id} | |
|
|
| Delete claimed | DELETE | /products/claimed/{id} | |
|
|
| Upload image URL | POST | /products/images/from-url | |
|
|
| Upload image file | POST | /products/images/from-file | |
|
|
| Delete image | DELETE | /products/images/{id} | |
|
|
|
|
### Listings (上架)
|
|
| Action | Method | Endpoint | Notes |
|
|
|--------|--------|----------|-------|
|
|
| List all | GET | /listings | Paginate `?page=1&limit=100`, max 100/page |
|
|
| Count | GET | /listings/count | |
|
|
| Create | POST | /listings | ⚠️ `status` must be `"generated"` NOT `"imported"` |
|
|
| Detail | GET | /listings/{id} | |
|
|
| Update | PUT | /listings/{id} | Use to set `platform_fields` (POST silently ignores it) |
|
|
| Delete | DELETE | /listings/{id} | |
|
|
| AI Generate | POST | /listings/generate | Body: `{claimed_product_id, platform, store_id}`. 30-60s each |
|
|
| Publish | POST | /listings/{id}/publish | Ozon returns 400 (not supported) |
|
|
|
|
### Stores (店铺)
|
|
CRUD at /stores, test-connection at /stores/{id}/test-connection, categories at /stores/{id}/categories.
|
|
Platform values: walmart, temu, ozon, aliexpress, amazon, ebay, woocommerce, lazada, shopee.
|
|
|
|
### Social (社交)
|
|
CRUD at /social/posts, AI generate at /social/posts/generate.
|
|
|
|
### Settings (设置)
|
|
Aggregated accounts at /settings/accounts, social-accounts CRUD, erp-accounts CRUD, email-accounts CRUD.
|
|
|
|
### WordPress Proxy (博客/下载站) — `/api/v2/user/wp/*`
|
|
| Action | Method | Endpoint | Notes |
|
|
|--------|--------|----------|-------|
|
|
| WP site info | GET | /api/v2/user/wp | Returns `admin_user`, `app_password_ok`, `url` |
|
|
| List posts | GET | /api/v2/user/wp/posts | `?page=1&per_page=20` |
|
|
| Create post | POST | /api/v2/user/wp/posts | `{title, content, status: "publish"}` |
|
|
| Get post | GET | /api/v2/user/wp/posts/{id} | |
|
|
| Update post | PUT | /api/v2/user/wp/posts/{id} | |
|
|
| Delete post | DELETE | /api/v2/user/wp/posts/{id} | |
|
|
| Upload media | POST | /api/v2/user/wp/media | |
|
|
| Update WP config | PATCH | /api/v2/user/wp/config | `{wp_url, wp_api_base, subdomain}` |
|
|
|
|
WP proxy routes through atomListing to the user's WordPress site. Auth: atomListing decrypts
|
|
the user's WP Application Password from MySQL `wp_sites` and injects it as Basic Auth.
|
|
|
|
**Debugging WP proxy errors** → `references/wp-proxy-debugging.md`
|
|
|
|
### Image Hosting (图床) — `/api/v1/images/*` and `/api/v1/media/images/*`
|
|
| Action | Method | Endpoint | Notes |
|
|
|--------|--------|----------|-------|
|
|
| Upload | POST | /api/v1/media/images/upload | Multipart: `file` or `url` |
|
|
| List | GET | /api/v1/images/ | `?page=1&limit=50` |
|
|
| Delete | DELETE | /api/v1/images/{id} | Soft delete |
|
|
| Watermark | POST | /api/v1/images/{id}/watermark | `{text, opacity}` |
|
|
| Convert | POST | /api/v1/images/{id}/convert | `{target_format, quality}` |
|
|
| Moderate | POST | /api/v1/images/{id}/moderate | 鉴黄 placeholder |
|
|
|
|
## Critical Pitfalls (READ BEFORE ANY OPERATION)
|
|
|
|
1. **`status="imported"` → 500** — always use `"generated"` when creating listings.
|
|
2. **`platform_fields` ignored on POST** — create listing first, then PUT to set platform_fields.
|
|
3. **Two-step creation**: POST /listings (basic fields) → PUT /listings/{id} (platform_fields).
|
|
4. **Pagination matters**: /listings and /products/claimed max 100 per page. Always paginate until fewer than 100 items returned.
|
|
5. **Rate limiting**: add 50ms delay between requests. 502 on rapid fire. 10 consecutive 500s = bad product data, skip it.
|
|
6. **Dedup before create**: always check existing records before creating. Re-running without dedup creates duplicates.
|
|
7. **`claimed_product_id` must be unique**: one product → one listing per platform. Track used IDs.
|
|
8. **`password` field auto-masked** → use `pwd` in extra_data instead.
|
|
9. **Ozon publish not supported**: returns 400. Use Ozon Seller API directly.
|
|
10. **ERP accounts POST bug (2025-05)**: all creation fails. Store locally.
|
|
11. **Social platform whitelist**: only facebook, tiktok, twitter, wordpress work.
|
|
12. **Token expiry**: JWT has `exp` field. Re-login if 401.
|
|
13. **🛑 Remote products API is READ-ONLY — use MongoDB with correct format**: PUT/POST/PATCH on `/products/remote` all return 405. The PATCH at `/api/v1/premium-products/by-code/{code}` uses different fields. **Write directly to MongoDB**: `43.134.190.229:27018`, db `premiumproducts`, collection `products`. **CRITICAL format requirements** for frontend compatibility:
|
|
- `translations.zh.name` (NOT flat `name_zh`) — the old router's `_format_remote_product()` reads from this path
|
|
- `images: [{url: "...", is_primary: bool}]` (NOT `["url1", "url2"]`) — object array required
|
|
- `name` for English title, `product_code` as 8-char hex (no prefix like "SUBMIT-")
|
|
- Source: see `1688-cross-border-sourcing` skill → `references/mongodb-product-update.md` for full field table
|
|
14. **Product code format**: Must be 8-char hex. Codes with prefixes break exact-match.
|
|
15. **MongoDB field default masks missing field**: `_format_doc` using `doc.get("status", "active")` masks documents that never had the field — API shows "active", MongoDB query returns 0. Fix: connect 43.134.190.229:27018 → db.products.count_documents({"status": {"$exists": false}}) → update_many.
|
|
|
|
## Pagination Pattern
|
|
|
|
```python
|
|
all_items = []
|
|
page = 1
|
|
while True:
|
|
r = requests.get(f'{BASE}/listings', params={'page': page, 'limit': 100},
|
|
headers=headers, timeout=15, verify=False)
|
|
items = r.json() if isinstance(r.json(), list) else r.json().get('items', [])
|
|
all_items.extend(items)
|
|
if len(items) < 100:
|
|
break
|
|
page += 1
|
|
time.sleep(0.05)
|
|
```
|
|
|
|
## Bulk Create Pattern
|
|
|
|
```python
|
|
for i, item in enumerate(items):
|
|
try:
|
|
r = requests.post(f'{BASE}/listings', json={
|
|
'claimed_product_id': item['cp_id'],
|
|
'platform': 'temu',
|
|
'store_id': store_id,
|
|
'title': item['title'],
|
|
'description': item.get('desc', ''),
|
|
'price': item.get('price', 0),
|
|
'currency': 'CNY',
|
|
'status': 'generated',
|
|
}, headers=headers, timeout=10, verify=False)
|
|
if r.status_code == 201:
|
|
listing_id = r.json()['id']
|
|
requests.put(f'{BASE}/listings/{listing_id}', json={
|
|
'platform_fields': item.get('fields', {}),
|
|
}, headers=headers, timeout=10, verify=False)
|
|
time.sleep(0.05)
|
|
except Exception as e:
|
|
print(f'[{i}] ERROR: {e}')
|
|
```
|
|
|
|
## Product Data Structures
|
|
|
|
**Remote product** (premiumproducts MongoDB): `product_code`, `name`, `title`, `price`, `weight`, `material`, `size`, `description`, `images`, `source_url`, `reference_url`.
|
|
|
|
**Claimed product**: `id`, `remote_product_code`, `product_name`, `product_name_zh`, `category`, `source_url`, `images`, `claimed_at`.
|
|
|
|
## Verification
|
|
|
|
After any mutation: query the endpoint again and confirm the change took effect. Report counts before/after.
|
|
```
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
### As cron job
|
|
```
|
|
cronjob create "atomk-api-..." --schedule "0 */6 * * *" --prompt "<the prompt above>"
|
|
```
|
|
|
|
### As delegate_task
|
|
```
|
|
delegate_task(goal="...", context="Use the AtomK Server API agent prompt. ...")
|
|
```
|
|
|
|
### Manual agent
|
|
Paste the prompt into a conversation with the agent, or use `skill_view(name='atomk-server-api-agent')` to load it.
|