API & Webhooks
Soryk's API is the same one our admin and agent UIs use — there's no separate "public API" to maintain. This page lists what's available and how to call it. For deep integrations or custom workflows, the recommended pattern is: read directly from Shopify metaobjects (your data), use Soryk endpoints only for actions that need server logic (create payment, fix attribution, etc.).
API stability. The endpoints below are considered stable and follow semver conventions for breaking changes. We won't deprecate without notice.
Authentication
Two authentication modes:
Admin endpoints — Shopify session token
Embedded admin requests use the Shopify App Bridge session token. From inside the embedded app you don't need to do anything — App Bridge attaches the token automatically. From outside (rare):
curl -X GET https://your-shop.myshopify.com/apps/soryk/api/admin/agents \
-H "Authorization: Bearer <session-token>"
Agent + buyer endpoints — JWT cookie
After magic-link or Google SSO sign-in, the JWT is in a HttpOnly secure cookie named soryk_session. Subsequent fetches include it automatically. Don't try to extract or forge it — sessions only work from the actual signed-in browser.
Available endpoints
Admin
| Method | Path | Purpose |
|---|---|---|
| GET / POST | /api/admin/agents | List or create agents. |
| GET / PATCH / DELETE | /api/admin/agents/[id] | Read, update, delete an agent. |
| POST | /api/admin/agents/bulk-import | CSV bulk import. |
| POST | /api/admin/agents/magic-link | Send magic-link invite. |
| GET / POST | /api/admin/companies | List companies, create draft company. |
| GET / PATCH / DELETE | /api/admin/commissions/rules/[id] | Manage commission rules. |
| GET | /api/admin/commissions/earnings | Computed earnings (paid + pending). |
| GET / POST | /api/admin/commissions/payments | List or record payouts. Idempotent on (agentId, orderIds). |
| GET / POST | /api/admin/commissions/overrides | List or create attribution overrides. |
| GET | /api/admin/commissions/export | CSV export. ?dataset=rules|due|paid. |
| GET / PATCH | /api/admin/shop-config | Read or update settings. |
| GET | /api/admin/audit-log | Combined metaobject + Redis mirror audit log. |
| POST | /api/admin/territory-audit | Trigger Claude conflict analysis. |
Agent
| Method | Path | Purpose |
|---|---|---|
| POST | /api/agent/request-login | Send OTP to agent email. Rate-limited. |
| POST | /api/agent/verify-login | Verify OTP, set JWT cookie. |
| GET / PATCH | /api/agent/profile | Read or update locale. |
| GET | /api/agent/companies | List assigned companies (territory-filtered if enabled). |
| GET | /api/agent/catalog | Browse catalog with filters. |
| POST | /api/agent/cart-preview | Compute tax + shipping preview. |
| POST | /api/agent/draft-order | Create draft order with sanitized tags. |
| POST / PATCH / DELETE | /api/agent/quotes/[id] | Quote CRUD; PATCH on draft AND pending. |
| POST | /api/agent/quotes/[id]/send | Send quote email. |
| POST | /api/agent/quotes/[id]/convert | Convert accepted quote to draft order. |
| GET | /api/agent/payment-terms | List Shopify PaymentTermsTemplate + label overrides. |
| GET / POST / DELETE | /api/agent/push/subscribe | Push subscription management. |
Buyer (when buyerPortalEnabled)
| Method | Path | Purpose |
|---|---|---|
| POST | /api/buyer/[shop]/request-login | Send OTP to buyer email. |
| GET | /api/buyer/catalog | Catalog scoped to session companyId. Verifies location ownership. |
| GET | /api/buyer/orders | Orders for the buyer's company. |
| POST | /api/buyer/draft-order | Place order. Tagged soryk_buyer:<email>. |
| GET | /api/buyer/shipments | Fulfillment events. |
| GET | /api/buyer/analytics | Consumption metrics for the buyer's company. |
Public
| GET / POST | /quotes/[token] + /api/public/quotes/[token]/accept|reject | Token-based public quote flow. No auth. |
Webhooks
Soryk subscribes to a small set of Shopify webhooks at install time. You don't need to configure anything.
| Topic | What we do |
|---|---|
refunds/create | Check if any of the refunded order's lines were already in a paid commission. If yes, create soryk_commission_alert. Bust the analytics cache for the shop. |
orders/paid | Used in some flows to trigger downstream computation (commission attribution refresh). |
app/uninstalled | Mark the shop as uninstalled internally; queue Upstash purge. |
customers/data_request | GDPR data subject access — return what we hold (transient, almost nothing). |
customers/redact / shop/redact | GDPR redaction handlers — delete any traceable PII from Upstash. |
Rate limits
| Bucket | Limit |
|---|---|
| Login (per email) | 5 / 5 min |
| Login (per IP) | 30 / 5 min |
| Admin payments | 60 / 1 min / shop |
| Other endpoints | Inherited from Shopify Admin API limits (40 RPS REST, ~50 cost/s GraphQL). |
Excess returns 429 with Retry-After.
Examples
Curl — list agents
curl https://your-shop.myshopify.com/apps/soryk/api/admin/agents \
-H "Authorization: Bearer <session-token>"
Curl — create a commission rule
curl -X POST https://your-shop.myshopify.com/apps/soryk/api/admin/commissions/rules \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{
"label": "Premium wines bonus",
"scope": "collection",
"scopeTarget": "gid://shopify/Collection/1234567890",
"base": "no_tax_no_shipping",
"ratePercent": 8,
"priority": 10,
"deductOnRefund": true
}'
JavaScript — record a payment
const res = await fetch('/api/admin/commissions/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: 'gid://shopify/Metaobject/...',
orderIds: ['gid://shopify/Order/1', 'gid://shopify/Order/2'],
amount: 250.00,
currency: 'EUR',
method: 'bank_transfer',
note: 'April payout',
paidAt: '2026-04-25'
})
});
if (res.status === 409) {
const { duplicateOrderIds, existingPaymentId } = await res.json();
console.warn('Already paid', duplicateOrderIds, 'in payment', existingPaymentId);
}
Error codes
All error responses follow the same shape:
{
"code": "INVALID_INPUT",
"error": "Human-readable message"
}
Standard codes:
| Code | HTTP | Meaning |
|---|---|---|
NOT_AUTHENTICATED | 401 | Missing or invalid session. |
FORBIDDEN | 403 | Authenticated but not allowed (e.g. cross-tenant buyer request). |
NOT_FOUND | 404 | Resource doesn't exist. |
SHOP_NOT_CONNECTED | 400 | Shop reference present but no valid Shopify install. |
INVALID_INPUT | 400 | Validation failed. |
COMPANY_HAS_NO_CONTACT | 400 | Trying to send to a company without a primary contact email. |
RATE_LIMITED | 429 | Too many requests; retry after the header. |
DUPLICATE_PAYMENT | 409 | Bulk-pay rejected because some orders already in another payment. |
INTERNAL | 500 | Server-side error. Check status page or contact support. |
The agent UI translates codes via useErrorMessage() — keys live in lib/i18n/agent-dict.ts as error.<CODE> in EN/IT.