{"openapi":"3.1.0","info":{"title":"Absofitly API","version":"0.1.0","description":"Money is integer cents (USD). Errors are { error: { code, message } } with stable codes for translation."},"components":{"securitySchemes":{"session":{"type":"apiKey","in":"cookie","name":"absofitly.session_token","description":"Web: the Better Auth session cookie"},"bearer":{"type":"http","scheme":"bearer","description":"Mobile and integrations: the session token from sign-in (set-auth-token header)"}},"schemas":{"Me":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"emailVerified":{"type":"boolean"},"image":{"type":["string","null"]},"locale":{"type":"string","enum":["en","es","pt"]},"phone":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string","enum":["customer","partner","provider","vendor","admin"]}},"partnerLevel":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","name","email","emailVerified","image","locale","phone","roles","partnerLevel","createdAt"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]}},"required":["error"]},"UpdateMe":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"locale":{"type":"string","enum":["en","es","pt"]},"phone":{"type":["string","null"],"pattern":"^\\+?[0-9 ().-]{7,20}$"}},"additionalProperties":false},"OrderPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderSummary"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]},"OrderSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"orderNumber":{"type":"string"},"placedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"financialStatus":{"type":["string","null"]},"fulfillmentStatus":{"type":["string","null"]},"cancelled":{"type":"boolean"},"currency":{"type":"string"},"totalCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refundedCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"itemCount":{"type":"integer"}},"required":["id","orderNumber","placedAt","financialStatus","fulfillmentStatus","cancelled","currency","totalCents","refundedCents","itemCount"]},"OrderDetail":{"allOf":[{"$ref":"#/components/schemas/OrderSummary"},{"type":"object","properties":{"subtotalCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"discountCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"shippingCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"taxCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"statusUrl":{"type":["string","null"]},"lines":{"type":"array","items":{"$ref":"#/components/schemas/OrderLine"}}},"required":["subtotalCents","discountCents","shippingCents","taxCents","statusUrl","lines"]}]},"OrderLine":{"type":"object","properties":{"title":{"type":"string"},"variantTitle":{"type":["string","null"]},"sku":{"type":["string","null"]},"quantity":{"type":"integer"},"unitPriceCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"amountPaidCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"isSubscription":{"type":"boolean"}},"required":["title","variantTitle","sku","quantity","unitPriceCents","amountPaidCents","isSubscription"]},"SubscriptionList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}},"required":["items"]},"Subscription":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["active","paused","cancelled","expired","failed"]},"title":{"type":"string"},"quantity":{"type":"integer"},"priceCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"currency":{"type":"string"},"interval":{"type":"object","properties":{"unit":{"type":"string"},"count":{"type":"integer"}},"required":["unit","count"]},"nextBillingAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"startedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"cancelledAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"manageUrl":{"type":["string","null"]}},"required":["id","status","title","quantity","priceCents","currency","interval","nextBillingAt","startedAt","cancelledAt","manageUrl"]},"BookingList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}}},"required":["items"]},"Booking":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["class","appointment"]},"status":{"type":"string","enum":["requested","confirmed","declined","cancelled","completed","no_show"]},"title":{"type":"string"},"providerName":{"type":["string","null"]},"startsAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"endsAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"timezone":{"type":"string"},"location":{"type":["string","null"]},"joinUrl":{"type":["string","null"]}},"required":["id","kind","status","title","providerName","startsAt","endsAt","timezone","location","joinUrl"]},"ReportList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Report"}}},"required":["items"]},"Report":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["intelligence_report","body_composition","training","nutrition","other"]},"title":{"type":"string"},"provider":{"type":["string","null"]},"url":{"type":"string"},"issuedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","kind","title","provider","url","issuedAt"]},"WalletList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Wallet"}}},"required":["items"]},"Wallet":{"type":"object","properties":{"wallet":{"type":"string","enum":["purchase","commission"]},"balanceCents":{"type":"integer","description":"Integer cents (USD)","example":4187}},"required":["wallet","balanceCents"]},"WalletEntryPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WalletEntry"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]},"WalletEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["credit","debit","hold","release","adjustment"]},"amountCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"reason":{"type":"string"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","kind","amountCents","reason","createdAt"]},"AccessResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/AccessResult"}}},"required":["results"]},"AccessResult":{"type":"object","properties":{"ref":{"type":"string"},"allowed":{"type":"boolean"},"reason":{"type":"string","enum":["public","allowed","admin","sign_in_required","not_entitled"]},"lockedMessage":{"type":["string","null"]},"unlockWith":{"type":"array","items":{"type":"string"}},"unlockAtLevel":{"type":["string","null"]}},"required":["ref","allowed","reason","lockedMessage","unlockWith","unlockAtLevel"]},"AccessRequest":{"type":"object","properties":{"refs":{"type":"array","items":{"type":"string","pattern":"^[a-z]+:[a-z0-9_-]+:[A-Za-z0-9_-]+$"},"minItems":1,"maxItems":100}},"required":["refs"],"example":{"refs":["wp:recipe:4521","cms:courses:nutrition-101"]}},"EntitlementList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Entitlement"}}},"required":["items"]},"Entitlement":{"type":"object","properties":{"key":{"type":"string"},"source":{"type":"string","enum":["order","subscription","booking","manual"]},"startsAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"endsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["key","source","startsAt","endsAt"]},"ContentGate":{"type":"object","properties":{"ref":{"type":"string"},"rule":{"$ref":"#/components/schemas/AccessRule"},"lockedMessage":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["ref","rule","lockedMessage","updatedAt"]},"AccessRule":{"type":"object","properties":{"anyRole":{"type":"array","items":{"type":"string","enum":["customer","partner","provider","vendor","admin"]},"minItems":1},"anyEntitlement":{"type":"array","items":{"type":"string","pattern":"^[a-z][a-z0-9-]*:[a-z0-9][a-z0-9-]*$"},"minItems":1},"minPartnerLevel":{"type":"string","pattern":"^[a-z][a-z0-9-]*$"}},"additionalProperties":false,"description":"Empty object: any signed-in user. Otherwise the user needs one of: a listed role, a listed entitlement, or to be a Partner at minPartnerLevel or above.","example":{"anyEntitlement":["program:weight-loss"],"minPartnerLevel":"bronze"}},"GrantedEntitlement":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"userId":{"type":"string"},"key":{"type":"string"},"source":{"type":"string"},"startsAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"endsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"revokedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","userId","key","source","startsAt","endsAt","revokedAt"]},"ProductAccess":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"productId":{"type":"string"},"key":{"type":"string"},"durationDays":{"type":["integer","null"]}},"required":["id","productId","key","durationDays"]},"PartnerLevel":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"rank":{"type":"integer"}},"required":["id","name","rank"]},"PartnerLevelChange":{"type":"object","properties":{"level":{"allOf":[{"$ref":"#/components/schemas/PartnerLevel"},{"type":["object","null"]}]},"effectiveAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["level","effectiveAt"]},"ExternalApp":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"name":{"type":"string","minLength":1,"maxLength":100},"launchUrl":{"type":["string","null"],"format":"uri"},"provisioning":{"type":"string","enum":["manual","zapier","api"]}},"required":["key","name","launchUrl","provisioning"]},"CatalogItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"type":{"type":"string","enum":["product","credits","content","app","other"]},"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"config":{"type":"object","additionalProperties":{}},"unitCostCents":{"type":["integer","null"]},"costBasis":{"type":"string","enum":["each","per_30_days"]},"active":{"type":"boolean"}},"required":["id","key","type","name","description","config","unitCostCents","costBasis","active"]},"LocalizedText":{"type":"object","properties":{"en":{"type":"string","minLength":1,"maxLength":2000},"es":{"type":"string","minLength":1,"maxLength":2000},"pt":{"type":"string","minLength":1,"maxLength":2000}},"required":["en"],"additionalProperties":false,"example":{"en":"Weight Loss Plan","es":"Plan de pérdida de peso"}},"ItemTypeAndConfig":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["product"]},"config":{"type":"object","properties":{"shopifyProductId":{"type":"string","pattern":"^\\d+$"},"shopifyVariantId":{"type":"string","pattern":"^\\d+$"}},"required":["shopifyProductId"],"additionalProperties":false}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["credits"]},"config":{"type":"object","properties":{"serviceType":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"unit":{"type":"string","enum":["minutes","sessions"]},"expires":{"type":"boolean","default":false},"expiresAfterDays":{"type":"integer","minimum":1,"maximum":3650}},"required":["serviceType","unit"],"additionalProperties":false}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["content"]},"config":{"type":"object","properties":{"entitlementKey":{"type":"string","pattern":"^[a-z][a-z0-9-]*:[a-z0-9][a-z0-9-]*$","example":"content:weight-loss-guides"}},"required":["entitlementKey"],"additionalProperties":false}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["app"]},"config":{"type":"object","properties":{"appKey":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"}},"required":["appKey"],"additionalProperties":false}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["other"]},"config":{"type":"object","properties":{"instructions":{"type":"string","maxLength":1000}},"additionalProperties":false}},"required":["type","config"]}]},"BundleAdmin":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"status":{"type":"string","enum":["draft","active","archived"]},"shopifyProductId":{"type":["string","null"]},"categoryId":{"type":"string"},"commissionable":{"type":"boolean"},"versions":{"type":"array","items":{"$ref":"#/components/schemas/BundleVersion"}}},"required":["id","key","status","shopifyProductId","categoryId","commissionable","versions"]},"BundleVersion":{"type":"object","properties":{"version":{"type":"integer"},"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"priceCents":{"type":"integer"},"commissionOverride":{"$ref":"#/components/schemas/CommissionOverride"},"totalCostCents":{"type":["integer","null"]},"publishedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"components":{"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"key":{"type":"string"},"type":{"type":"string","enum":["product","credits","content","app","other"]},"name":{"type":"string"},"quantity":{"type":"integer"},"unit":{"type":["string","null"]},"durationDays":{"type":["integer","null"]},"expires":{"type":["boolean","null"]}},"required":["itemId","key","type","name","quantity","unit","durationDays","expires"]}}},"required":["version","name","description","priceCents","commissionOverride","totalCostCents","publishedAt","components"]},"CommissionOverride":{"type":["object","null"],"properties":{"basis":{"type":"string","enum":["price","margin"]},"rates":{"type":"object","additionalProperties":{"type":"array","items":{"type":["integer","null"],"minimum":0,"maximum":10000},"maxItems":4}},"payoutCapBps":{"type":["integer","null"],"minimum":0,"maximum":10000},"maxMemberDiscountBps":{"type":"integer","minimum":0,"maximum":10000},"subscriptionDiscountBps":{"type":"integer","minimum":0,"maximum":10000}},"additionalProperties":false,"example":{"basis":"margin","payoutCapBps":6600}},"BundleAnalysis":{"type":"object","properties":{"version":{"type":"integer"},"published":{"type":"boolean"},"priceCents":{"type":"integer"},"lines":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"type":{"type":"string","enum":["product","credits","content","app","other"]},"quantity":{"type":"integer"},"durationDays":{"type":["integer","null"]},"costBasis":{"type":"string","enum":["each","per_30_days"]},"unitCostCents":{"type":["integer","null"]},"costCents":{"type":["integer","null"]}},"required":["key","type","quantity","durationDays","costBasis","unitCostCents","costCents"]}},"totalCostCents":{"type":["integer","null"]},"multiple":{"type":["number","null"]},"planVersion":{"type":["string","null"]},"commissions":{"description":"commission-engine ProductAnalysis, or null without a comp plan"},"problems":{"type":"array","items":{"type":"string"}}},"required":["version","published","priceCents","lines","totalCostCents","multiple","planVersion","problems"]},"CompPlanVersion":{"type":"object","properties":{"version":{"type":"string"},"activeFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"plan":{}},"required":["version","activeFrom","createdAt"]},"Webhook":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["client.added","client.updated","partner.added","partner.updated","plan.started","plan.ended","app_access.granted","app_access.removed","lead.received"]}},"active":{"type":"boolean"},"secretHint":{"type":"string"},"pending":{"type":"integer"},"failed":{"type":"integer"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","name","url","events","active","secretHint","pending","failed","createdAt"]},"WebhookWithSecret":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string"}},"required":["secret"]}]},"WebhookTestResult":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":["integer","null"]},"error":{"type":["string","null"]},"response":{"type":["string","null"]}},"required":["ok","status","error","response"]},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string"},"occurredAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"data":{}},"required":["id","type","occurredAt"]},"status":{"type":"string","enum":["pending","sent","failed"]},"attempts":{"type":"integer"},"nextAttemptAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"lastStatus":{"type":["integer","null"]},"lastError":{"type":["string","null"]},"lastResponse":{"type":["string","null"]},"sentAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","event","status","attempts","nextAttemptAt","lastStatus","lastError","lastResponse","sentAt"]},"PlanPurchaseSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"customer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},"plan":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"]},"orderNumber":{"type":"string"},"status":{"type":"string","enum":["active","revoked"]},"purchasedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"paidCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refundedCents":{"type":"integer","description":"Integer cents (USD)","example":4187}},"required":["id","customer","plan","orderNumber","status","purchasedAt","paidCents","refundedCents"]},"PlanRefunds":{"type":"object","properties":{"purchaseId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["active","revoked"]},"quantity":{"type":"integer"},"paidCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refundedCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"items":{"type":"array","items":{"type":"object","properties":{"componentId":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["product","credits","content","app","other"]},"costCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"quantity":{"type":"integer"},"units":{"type":"integer"},"unitsLeft":{"type":"integer"},"shareCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refundedCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"heldCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"takenBack":{"type":"boolean"}},"required":["componentId","key","name","type","costCents","quantity","units","unitsLeft","shareCents","refundedCents","heldCents","takenBack"]}},"takebacks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","properties":{"componentId":{"type":"string","format":"uuid"},"units":{"type":"integer"},"quotedCents":{"type":["integer","null"],"description":"Integer cents (USD)","example":4187}},"required":["componentId","units","quotedCents"]}},"amountCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"quotedCents":{"type":["integer","null"],"description":"Integer cents (USD)","example":4187},"outstandingCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"reason":{"type":"string"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","items","amountCents","quotedCents","outstandingCents","reason","createdAt"]}}},"required":["purchaseId","status","quantity","paidCents","refundedCents","items","takebacks"]},"PlanItemRefund":{"type":"object","properties":{"takebackId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","properties":{"componentId":{"type":"string","format":"uuid"},"name":{"type":"string"},"units":{"type":"integer"},"quotedCents":{"type":"integer","description":"Integer cents (USD)","example":4187}},"required":["componentId","name","units","quotedCents"]}},"quotedCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refundCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"refunds":{"$ref":"#/components/schemas/PlanRefunds"}},"required":["takebackId","items","quotedCents","refundCents","refunds"]},"ShopifyProduct":{"type":"object","properties":{"shopifyProductId":{"type":"string"},"title":{"type":"string"},"handle":{"type":"string"},"vendor":{"type":["string","null"]},"productType":{"type":["string","null"]},"status":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"imageUrl":{"type":["string","null"]},"shopifyUpdatedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"syncedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"deletedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"variants":{"type":"array","items":{"type":"object","properties":{"shopifyVariantId":{"type":"string"},"title":{"type":"string"},"sku":{"type":["string","null"]},"priceCents":{"type":"integer","description":"Integer cents (USD)","example":4187},"compareAtPriceCents":{"type":["integer","null"],"description":"Integer cents (USD)","example":4187},"costCents":{"type":["integer","null"],"description":"Integer cents (USD)","example":4187},"inventoryQuantity":{"type":["integer","null"]},"missingCost":{"type":"boolean"},"atOrBelowCost":{"type":"boolean"}},"required":["shopifyVariantId","title","sku","priceCents","compareAtPriceCents","costCents","inventoryQuantity","missingCost","atOrBelowCost"]}}},"required":["shopifyProductId","title","handle","vendor","productType","status","tags","imageUrl","shopifyUpdatedAt","syncedAt","deletedAt","variants"]},"VendorCost":{"type":"object","properties":{"vendor":{"type":"string"},"products":{"type":"integer"},"fulfillmentCents":{"type":"integer"},"shippingCents":{"type":"integer"},"shippingBuiltIn":{"type":"boolean"}},"required":["vendor","products","fulfillmentCents","shippingCents","shippingBuiltIn"]},"MyReferral":{"type":"object","properties":{"code":{"type":"string"},"sponsor":{"type":["object","null"],"properties":{"name":{"type":"string"}},"required":["name"]},"directReferrals":{"type":"integer"}},"required":["code","sponsor","directReferrals"]},"ArticleList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ArticleSummary"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]},"ArticleSummary":{"type":"object","properties":{"wpId":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"excerptHtml":{"type":"string"},"categories":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]}},"featuredMediaId":{"type":["integer","null"]},"publishedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"modifiedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"access":{"$ref":"#/components/schemas/ContentAccess"}},"required":["wpId","slug","title","excerptHtml","categories","featuredMediaId","publishedAt","modifiedAt","access"]},"ContentAccess":{"type":"object","properties":{"ref":{"type":"string"},"allowed":{"type":"boolean"},"reason":{"type":"string","enum":["public","allowed","admin","sign_in_required","not_entitled"]},"lockedMessage":{"type":["string","null"]},"unlockWith":{"type":"array","items":{"type":"string"}},"unlockAtLevel":{"type":["string","null"]}},"required":["ref","allowed","reason","lockedMessage","unlockWith","unlockAtLevel"]},"Article":{"allOf":[{"$ref":"#/components/schemas/ArticleSummary"},{"type":"object","properties":{"contentHtml":{"type":["string","null"]}},"required":["contentHtml"]}]},"RecipeList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RecipeSummary"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]},"RecipeSummary":{"type":"object","properties":{"wpId":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string"},"summaryHtml":{"type":"string"},"imageUrl":{"type":["string","null"]},"servings":{"type":["integer","null"]},"servingsUnit":{"type":["string","null"]},"prepMinutes":{"type":["integer","null"]},"cookMinutes":{"type":["integer","null"]},"totalMinutes":{"type":["integer","null"]},"calories":{"type":["integer","null"]},"tags":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"publishedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"modifiedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"access":{"$ref":"#/components/schemas/ContentAccess"}},"required":["wpId","slug","name","summaryHtml","imageUrl","servings","servingsUnit","prepMinutes","cookMinutes","totalMinutes","calories","tags","publishedAt","modifiedAt","access"]},"Recipe":{"allOf":[{"$ref":"#/components/schemas/RecipeSummary"},{"type":"object","properties":{"notesHtml":{"type":["string","null"]},"nutrition":{"type":["object","null"],"additionalProperties":{"type":"number"}},"ingredients":{"type":["array","null"],"items":{"type":"object","properties":{"name":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"amount":{"type":"string"},"unit":{"type":"string"},"name":{"type":"string"},"notes":{"type":"string"}},"required":["amount","unit","name","notes"]}}},"required":["name","items"]}},"instructions":{"type":["array","null"],"items":{"type":"object","properties":{"name":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"text":{"type":"string"}},"required":["name","text"]}}},"required":["name","steps"]}}},"required":["notesHtml","nutrition","ingredients","instructions"]}]},"DirectoryList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DirectoryListing"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]},"DirectoryListing":{"type":"object","properties":{"slug":{"type":"string"},"kind":{"type":"string","enum":["partner","coach","practitioner","vendor"]},"displayName":{"type":"string"},"headline":{"type":"string"},"bio":{"type":"string"},"photoUrl":{"type":["string","null"]},"services":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}},"credentials":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"issuer":{"type":"string"},"year":{"type":["integer","null"]},"verified":{"type":"boolean"}},"required":["name","issuer","year","verified"]}},"city":{"type":["string","null"]},"region":{"type":["string","null"]},"country":{"type":"string"},"offersInPerson":{"type":"boolean"},"offersRemote":{"type":"boolean"},"contactEmail":{"type":["string","null"]},"contactPhone":{"type":["string","null"]},"websiteUrl":{"type":["string","null"]},"bookingUrl":{"type":["string","null"]},"certifiedPartner":{"type":"boolean"}},"required":["slug","kind","displayName","headline","bio","photoUrl","services","credentials","city","region","country","offersInPerson","offersRemote","contactEmail","contactPhone","websiteUrl","bookingUrl","certifiedPartner"]},"DirectoryListingAdmin":{"allOf":[{"$ref":"#/components/schemas/DirectoryListing"},{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"userId":{"type":["string","null"]},"status":{"type":"string","enum":["draft","listed","hidden","taken_down"]},"postalCode":{"type":["string","null"]},"approvedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","userId","status","postalCode","approvedAt","updatedAt"]}]},"DirectoryProfileInput":{"type":"object","properties":{"kind":{"type":"string","enum":["partner","coach","practitioner","vendor"]},"displayName":{"type":"string","minLength":2,"maxLength":120},"headline":{"type":"string","maxLength":160,"default":""},"bio":{"type":"string","maxLength":5000,"default":""},"photoUrl":{"type":["string","null"],"maxLength":500,"format":"uri","default":null,"description":"https only"},"services":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"maxItems":20,"default":[]},"credentials":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":120},"issuer":{"type":"string","maxLength":120,"default":""},"year":{"type":["integer","null"],"minimum":1950,"maximum":2100,"default":null}},"required":["name"],"additionalProperties":false},"maxItems":20,"default":[]},"city":{"type":["string","null"],"maxLength":100,"default":null},"region":{"type":["string","null"],"maxLength":100,"default":null},"country":{"type":"string","pattern":"^[A-Z]{2}$","default":"US"},"postalCode":{"type":["string","null"],"maxLength":20,"default":null},"offersInPerson":{"type":"boolean","default":true},"offersRemote":{"type":"boolean","default":false},"contactEmail":{"type":["string","null"],"maxLength":200,"format":"email","default":null},"contactPhone":{"type":["string","null"],"pattern":"^[+0-9 ().-]{7,25}$","default":null},"websiteUrl":{"type":["string","null"],"maxLength":500,"format":"uri","default":null,"description":"https only"},"bookingUrl":{"type":["string","null"],"maxLength":500,"format":"uri","default":null,"description":"https only"}},"required":["kind","displayName"],"additionalProperties":false},"LeadInput":{"type":"object","properties":{"name":{"type":"string","maxLength":120,"default":""},"email":{"type":"string","maxLength":200,"format":"email"},"phone":{"type":["string","null"],"pattern":"^[+0-9 ().-]{7,25}$","default":null},"message":{"type":"string","maxLength":5000,"default":""},"fields":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"default":{}},"locale":{"type":"string","enum":["en","es","pt"],"default":"en"},"sourceUrl":{"type":["string","null"],"maxLength":500,"format":"uri","default":null},"startedAt":{"type":"integer"},"website":{"type":"string","maxLength":500}},"required":["email"],"additionalProperties":false},"Lead":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"formId":{"type":"string"},"status":{"type":"string","enum":["new","contacted","qualified","closed","spam"]},"ownerUserId":{"type":["string","null"]},"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":["string","null"]},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}},"locale":{"type":"string"},"sourceUrl":{"type":["string","null"]},"referralCode":{"type":["string","null"]},"userId":{"type":["string","null"]},"spamReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","formId","status","ownerUserId","name","email","phone","message","fields","locale","sourceUrl","referralCode","userId","spamReason","createdAt","updatedAt"]},"Bundle":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"priceCents":{"type":"integer"},"yourPrice":{"type":["object","null"],"properties":{"priceCents":{"type":"integer"},"discountBps":{"type":"integer"}},"required":["priceCents","discountBps"]},"shopifyProductId":{"type":["string","null"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/BundleItem"}}},"required":["key","name","description","priceCents","yourPrice","shopifyProductId","items"]},"BundleItem":{"type":"object","properties":{"type":{"type":"string","enum":["product","credits","content","app","other"]},"name":{"type":"string"},"quantity":{"type":"integer"},"unit":{"type":["string","null"]},"durationDays":{"type":["integer","null"]}},"required":["type","name","quantity","unit","durationDays"]},"MyPlans":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string"},"name":{"type":"string"},"purchasedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"status":{"type":"string","enum":["active","revoked"]},"items":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/BundleItem"},{"type":"object","properties":{"status":{"type":["string","null"],"enum":["pending","done","failed","cancelled",null]},"accessEndsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["status","accessEndsAt"]}]}}},"required":["id","key","name","purchasedAt","status","items"]}}},"required":["items"]},"MyCredits":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"serviceType":{"type":"string"},"unit":{"type":"string","enum":["minutes","sessions"]},"balance":{"type":"integer"},"nextExpiry":{"type":["object","null"],"properties":{"amount":{"type":"integer"},"expiresAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["amount","expiresAt"]}},"required":["serviceType","unit","balance","nextExpiry"]}}},"required":["items"]},"MyApps":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"launchUrl":{"type":["string","null"]},"accessEndsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"setup":{"type":"string","enum":["pending","done","failed"]}},"required":["key","name","launchUrl","accessEndsAt","setup"]}}},"required":["items"]}},"parameters":{}},"paths":{"/api/v1/me":{"get":{"tags":["Account"],"summary":"The signed-in person","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Profile and roles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Account"],"summary":"Update name, language or phone","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMe"}}}},"responses":{"200":{"description":"Updated profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/orders":{"get":{"tags":["Orders"],"summary":"Order history, newest first","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"From the previous page's nextCursor"},"required":false,"description":"From the previous page's nextCursor","name":"cursor","in":"query"}],"responses":{"200":{"description":"A page of orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPage"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/orders/{id}":{"get":{"tags":["Orders"],"summary":"One order with its lines","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderDetail"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/subscriptions":{"get":{"tags":["Subscriptions"],"summary":"Autoship subscriptions, active first","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Subscriptions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionList"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/bookings":{"get":{"tags":["Bookings"],"summary":"Classes and appointments","description":"upcoming: requested or confirmed and not yet over, soonest first. past: everything else, latest first.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","enum":["upcoming","past"],"default":"upcoming"},"required":false,"name":"when","in":"query"},{"schema":{"type":"string","enum":["class","appointment"]},"required":false,"name":"kind","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Bookings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingList"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/reports":{"get":{"tags":["Reports"],"summary":"Links to the person's reports, newest first","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Reports","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportList"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/wallets":{"get":{"tags":["Wallets"],"summary":"Wallet balances","description":"Everyone has a Purchase Wallet. The Commission Wallet is listed for Partners, or once it has entries.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Balances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletList"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/wallets/{wallet}/entries":{"get":{"tags":["Wallets"],"summary":"Wallet history, newest first","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","enum":["purchase","commission"]},"required":true,"name":"wallet","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"From the previous page's nextCursor"},"required":false,"description":"From the previous page's nextCursor","name":"cursor","in":"query"}],"responses":{"200":{"description":"A page of entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletEntryPage"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/access":{"post":{"tags":["Content"],"summary":"Can the caller see these content items or features?","description":"Works signed in or not. Pages call this (or decideAccess server-side) before rendering a gated body; the partner app uses feature refs (feature:partner:<name>) to show or hide features. Anything without a gate is public.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequest"}}}},"responses":{"200":{"description":"One result per ref","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/entitlements":{"get":{"tags":["Content"],"summary":"What the signed-in person has access to now","description":"One item per access. endsAt runs through renewals already bought.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Active entitlements","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitlementList"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/content-gates":{"get":{"tags":["Admin"],"summary":"All gated content","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Gates","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ContentGate"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/content-gates/{ref}":{"put":{"tags":["Admin"],"summary":"Gate a content item, or change its gate","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^[a-z]+:[a-z0-9_-]+:[A-Za-z0-9_-]+$","example":"wp:recipe:4521"},"required":true,"name":"ref","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"$ref":"#/components/schemas/AccessRule"},"lockedMessage":{"type":["string","null"],"maxLength":300}},"required":["rule"],"additionalProperties":false}}}},"responses":{"200":{"description":"The gate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentGate"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Admin"],"summary":"Make a content item public again","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^[a-z]+:[a-z0-9_-]+:[A-Za-z0-9_-]+$","example":"wp:recipe:4521"},"required":true,"name":"ref","in":"path"}],"responses":{"204":{"description":"Done"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/users/{userId}/entitlements":{"post":{"tags":["Admin"],"summary":"Give someone access by hand","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64},"required":true,"name":"userId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9-]*:[a-z0-9][a-z0-9-]*$","example":"program:weight-loss"},"endsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["key","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantedEntitlement"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/entitlements/{id}/revoke":{"post":{"tags":["Admin"],"summary":"Take access away","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantedEntitlement"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/product-access":{"get":{"tags":["Admin"],"summary":"Which products unlock which content","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Mappings","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductAccess"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin"],"summary":"Buying this product unlocks this content","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string","pattern":"^\\d+$","description":"Shopify product id"},"key":{"type":"string","pattern":"^[a-z][a-z0-9-]*:[a-z0-9][a-z0-9-]*$"},"durationDays":{"type":["integer","null"],"minimum":1,"maximum":3650}},"required":["productId","key"],"additionalProperties":false}}}},"responses":{"201":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductAccess"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/product-access/{id}":{"delete":{"tags":["Admin"],"summary":"Stop a product unlocking content (existing access is kept)","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Done"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/partner-levels":{"get":{"tags":["Admin"],"summary":"Partner levels, junior to senior","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Levels","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PartnerLevel"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/users/{userId}/partner-level":{"post":{"tags":["Admin"],"summary":"Set someone's Partner level by hand","description":"Makes the person a Partner if they aren't one. Each change is kept in their level history with the reason. Later the level rules (spec §6.7) write here too.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64},"required":true,"name":"userId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"levelId":{"type":"string","minLength":1,"maxLength":40},"reason":{"type":"string","minLength":3,"maxLength":500},"effectiveAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["levelId","reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"Recorded. level is what's in effect now (null if the change is in the future and none applies yet)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerLevelChange"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/users/{userId}/reports":{"post":{"tags":["Admin"],"summary":"Add a report link to someone's account","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64},"required":true,"name":"userId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["intelligence_report","body_composition","training","nutrition","other"]},"title":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":["string","null"],"maxLength":100},"url":{"type":"string","format":"uri"},"issuedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["kind","title","url","issuedAt"],"additionalProperties":false}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/reports/{id}/remove":{"post":{"tags":["Admin"],"summary":"Hide a report link from the account","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"],"additionalProperties":false}}}},"responses":{"204":{"description":"Done"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/apps":{"get":{"tags":["Admin: bundles"],"summary":"Third-party apps a bundle can give access to","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Apps","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ExternalApp"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: bundles"],"summary":"Add a third-party app (e.g. Trainerize through Zapier)","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"name":{"type":"string","minLength":1,"maxLength":100},"launchUrl":{"type":["string","null"],"format":"uri"},"provisioning":{"type":"string","enum":["manual","zapier","api"]}},"required":["key","name","launchUrl","provisioning"],"additionalProperties":false}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalApp"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/apps/{key}":{"patch":{"tags":["Admin: bundles"],"summary":"Edit an app, or change how access is set up in it","description":"Changing provisioning hands everyone's access to the new method. To zapier or api: everyone with access now is sent to the app again (app_access.granted), and open staff setup tasks close once that's done. To manual: nothing is sent; staff take over from the delivery queue.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"required":true,"name":"key","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"launchUrl":{"type":["string","null"],"format":"uri"},"provisioning":{"type":"string","enum":["manual","zapier","api"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalApp"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/catalog-items":{"get":{"tags":["Admin: bundles"],"summary":"Everything that can go in a bundle","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","enum":["product","credits","content","app","other"]},"required":false,"name":"type","in":"query"}],"responses":{"200":{"description":"Items","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: bundles"],"summary":"Create an item: a product, credits, content access, app access or anything else","description":"product: { shopifyProductId, shopifyVariantId? }. credits: { serviceType, unit: minutes|sessions, expires, expiresAfterDays? }. content: { entitlementKey }. app: { appKey }. other: { instructions? } (delivered by staff). The config is fixed once created; make a new item to change it. unitCostCents is what one unit costs Absofitly; costBasis per_30_days is for access priced by time, such as an app licence.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"unitCostCents":{"type":["integer","null"],"minimum":0,"maximum":10000000},"costBasis":{"type":"string","enum":["each","per_30_days"]}},"required":["key","name"]},{"$ref":"#/components/schemas/ItemTypeAndConfig"}]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogItem"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/catalog-items/{id}":{"patch":{"tags":["Admin: bundles"],"summary":"Rename, re-cost, or turn an item off","description":"Bundles already published keep the cost they were published with; a new cost applies to drafts and the next version.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"unitCostCents":{"type":["integer","null"],"minimum":0,"maximum":10000000},"costBasis":{"type":"string","enum":["each","per_30_days"]},"active":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogItem"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles":{"get":{"tags":["Admin: bundles"],"summary":"All bundles with every version","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Bundles","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BundleAdmin"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: bundles"],"summary":"Create a bundle (starts as a draft)","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"priceCents":{"type":"integer","minimum":0,"maximum":10000000},"components":{"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":100000,"default":1},"durationDays":{"type":["integer","null"],"minimum":1,"maximum":3650}},"required":["itemId"],"additionalProperties":false},"maxItems":50},"commissionOverride":{"$ref":"#/components/schemas/CommissionOverride"},"key":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"categoryId":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$","description":"Comp plan category it pays on","example":"supplement"},"commissionable":{"type":"boolean"},"shopifyProductId":{"type":["string","null"],"pattern":"^\\d+$"}},"required":["name","priceCents","components","key","categoryId"],"additionalProperties":false}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles/{id}":{"get":{"tags":["Admin: bundles"],"summary":"One bundle with every version","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Admin: bundles"],"summary":"Link the Shopify product, change the comp plan category, or stop commissions","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopifyProductId":{"type":["string","null"],"pattern":"^\\d+$"},"categoryId":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"commissionable":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles/{id}/draft":{"put":{"tags":["Admin: bundles"],"summary":"Edit the draft (creates one from scratch if there isn't one)","description":"Published versions never change. Buyers get the version on sale when they bought.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/LocalizedText"},"description":{"allOf":[{"$ref":"#/components/schemas/LocalizedText"},{"type":["object","null"]}]},"priceCents":{"type":"integer","minimum":0,"maximum":10000000},"components":{"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":100000,"default":1},"durationDays":{"type":["integer","null"],"minimum":1,"maximum":3650}},"required":["itemId"],"additionalProperties":false},"maxItems":50},"commissionOverride":{"$ref":"#/components/schemas/CommissionOverride"}},"required":["name","priceCents","components"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles/{id}/analysis":{"get":{"tags":["Admin: bundles"],"summary":"The override calculator for a bundle: cost of each item, total cost vs price, commissions and profit","description":"Uses the draft if there is one, else the version on sale. Commissions come from the comp plan in effect, as for a single product (one-time and subscription, direct earnings by level, the most a sale can pay, what's kept, the member discount cap).","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"version","in":"query"}],"responses":{"200":{"description":"Analysis","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAnalysis"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles/{id}/publish":{"post":{"tags":["Admin: bundles"],"summary":"Put the draft on sale. Its contents, price and costs are locked from then on","description":"Refused if it isn't linked to a Shopify product, any item has no cost, or (with a comp plan loaded) it would lose money at maximum payout.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/bundles/{id}/archive":{"post":{"tags":["Admin: bundles"],"summary":"Take a bundle off sale (people who bought it keep what they bought)","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/fulfilments":{"get":{"tags":["Admin: bundles"],"summary":"What still needs delivering or removing (supplements to ship, app access, custom items)","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","enum":["pending","done","failed","cancelled"],"default":"pending"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["product","credits","content","app","other"]},"required":false,"name":"type","in":"query"}],"responses":{"200":{"description":"Queue","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["product","credits","content","app","other"]},"action":{"type":"string","enum":["deliver","remove"]},"status":{"type":"string","enum":["pending","done","failed","cancelled"]},"customer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}},"required":["id","name","email"]},"bundle":{"type":"string"},"item":{"type":"string"},"detail":{},"accessEndsAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"note":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","type","action","status","customer","bundle","item","accessEndsAt","note","createdAt"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/fulfilments/{id}/{outcome}":{"post":{"tags":["Admin: bundles"],"summary":"Mark a delivery done (shipped, app access set up or removed) or failed","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["complete","fail"]},"required":true,"name":"outcome","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","maxLength":500}},"additionalProperties":false}}}},"responses":{"204":{"description":"Saved"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/app-accounts":{"get":{"tags":["Admin: bundles"],"summary":"App accounts the worker is retrying or couldn't sync","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Problems","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"email":{"type":"string"},"app":{"type":"string"},"state":{"type":"string","enum":["none","active","removed"]},"attempts":{"type":"integer"},"failed":{"type":"boolean"},"lastError":{"type":["string","null"]},"nextAttemptAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["userId","email","app","state","attempts","failed","lastError","nextAttemptAt"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/app-accounts/{userId}/{appKey}/retry":{"post":{"tags":["Admin: bundles"],"summary":"Try syncing this person's app access again now","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64},"required":true,"name":"userId","in":"path"},{"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"required":true,"name":"appKey","in":"path"}],"responses":{"204":{"description":"Queued"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/comp-plans":{"get":{"tags":["Admin: comp plan"],"summary":"Every comp plan version, newest first","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Plans","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CompPlanVersion"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: comp plan"],"summary":"Load a new comp plan version","description":"The whole plan: levels, rate charts, categories, card fee, minimum profit, subscription discount, largest member discount. Checked before it's saved; rates beyond a level's depth are refused. It applies to sales from activeFrom (default: now).","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"object","additionalProperties":{}},"activeFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["plan"],"additionalProperties":false}}}},"responses":{"201":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompPlanVersion"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks/events":{"get":{"tags":["Admin: webhooks"],"summary":"Events a webhook can get, with a sample of each","description":"Every request body is { id, type, occurredAt, data }; data looks like the sample. Requests carry X-Absofitly-Event, X-Absofitly-Delivery and X-Absofitly-Signature (t=<unix>,v1=<hex HMAC-SHA256 of \"<t>.<body>\"> with the hook's secret).","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Events","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["client.added","client.updated","partner.added","partner.updated","plan.started","plan.ended","app_access.granted","app_access.removed","lead.received"]},"label":{"type":"string"},"description":{"type":"string"},"sample":{}},"required":["type","label","description"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks":{"get":{"tags":["Admin: webhooks"],"summary":"Webhooks, with how many deliveries are waiting or failed","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: webhooks"],"summary":"Add a webhook","description":"Events that happen from now on are sent to it. The response is the only time the full signing secret is shown.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"url":{"type":"string","maxLength":2000,"format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["client.added","client.updated","partner.added","partner.updated","plan.started","plan.ended","app_access.granted","app_access.removed","lead.received"]},"minItems":1},"active":{"type":"boolean","default":true}},"required":["name","url","events"],"additionalProperties":false}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookWithSecret"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks/{id}":{"patch":{"tags":["Admin: webhooks"],"summary":"Edit a webhook, or turn it off or on","description":"While off, it gets no new events, and deliveries already waiting are held until it's turned back on.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"url":{"type":"string","maxLength":2000,"format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["client.added","client.updated","partner.added","partner.updated","plan.started","plan.ended","app_access.granted","app_access.removed","lead.received"]},"minItems":1},"active":{"type":"boolean","default":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks/{id}/rotate-secret":{"post":{"tags":["Admin: webhooks"],"summary":"Replace the signing secret","description":"Requests are signed with the new secret from now on, retries included.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"New secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookWithSecret"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks/{id}/test":{"post":{"tags":["Admin: webhooks"],"summary":"Send a sample event now","description":"Posts the event's sample, signed like a real one, and returns the receiver's answer. In Zapier, send one while the Catch Hook trigger is waiting for a test, so the Zap learns the fields. Nothing is recorded as a delivery.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","enum":["client.added","client.updated","partner.added","partner.updated","plan.started","plan.ended","app_access.granted","app_access.removed","lead.received"]}},"required":["event"],"additionalProperties":false}}}},"responses":{"200":{"description":"The receiver's answer (ok false if it failed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhooks/{id}/deliveries":{"get":{"tags":["Admin: webhooks"],"summary":"What was sent to a webhook, newest first","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["pending","sent","failed"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Deliveries","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/webhook-deliveries/{id}/retry":{"post":{"tags":["Admin: webhooks"],"summary":"Send a failed or waiting delivery again on the next run","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Queued"},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/plan-purchases":{"get":{"tags":["Admin: bundles"],"summary":"Plans people bought, newest first","description":"Filter by customer id or Shopify order number (e.g. #1001).","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":false,"name":"userId","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"orderNumber","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Purchases","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PlanPurchaseSummary"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/plan-purchases/{id}/refunds":{"get":{"tags":["Admin: bundles"],"summary":"Refund quote and history for a plan","description":"Each item's units and share of what was paid, in proportion to its real cost (the costs locked in when the plan version was published), what's been refunded against it, and item refunds still waiting for their Shopify refund. A Shopify refund pays those first (split across the picked items by cost); any amount not tied to picked items is split across the items still held, by cost.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Refunds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanRefunds"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/plan-purchases/{id}/refund-items":{"post":{"tags":["Admin: bundles"],"summary":"Refund items, or some of their units, from a plan","description":"Staff pick what's refunded, like returning 1, 2 or 3 of 3 in Shopify: `items` with a number of units each (default: all units still held), or `plans` to refund that many whole plans of a purchase bought in quantity. The amount defaults to those units' share of what was paid by real cost (less anything already refunded against them); refundCents overrides it, from 0 up to what's left to refund on the plan. For the units refunded: access gets shorter in proportion and ends with the last unit (app access is then removed unless something else gives it), unused credits are taken back up to the units, and supplements not yet shipped ship fewer or are cancelled. Nothing is returned: shipped supplements get no return or restock task (opened supplements are thrown away). Refund the amount in Shopify; that refund is matched to these units.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"componentId":{"type":"string","format":"uuid"},"units":{"type":"integer","minimum":1}},"required":["componentId"],"additionalProperties":false},"minItems":1},"plans":{"type":"integer","minimum":1},"reason":{"type":"string","minLength":3,"maxLength":500},"refundCents":{"type":"integer","minimum":0,"description":"Integer cents (USD)","example":4187}},"required":["reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Items refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanItemRefund"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/products":{"get":{"tags":["Admin: Shopify"],"summary":"Products synced from Shopify","description":"Shopify's catalog as last synced, with each variant's price and cost per item. Shopify stays the source of truth for these fields. Filter by text (title, handle, SKU), status, or `flag`: `missing_cost` (active products with a variant that has no cost, so commission overrides can't be saved) or `at_or_below_cost`.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","enum":["ACTIVE","DRAFT","ARCHIVED","UNLISTED"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["missing_cost","at_or_below_cost"]},"required":false,"name":"flag","in":"query"},{"schema":{"type":"string","enum":["true","false"],"default":"false"},"required":false,"name":"includeDeleted","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Products","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ShopifyProduct"}}},"required":["total","items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/sync":{"get":{"tags":["Admin: Shopify"],"summary":"When products and orders were last synced","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Sync runs","content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":["object","null"],"properties":{"kind":{"type":"string","enum":["products","orders"]},"trigger":{"type":"string"},"startedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"finishedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"summary":{},"error":{"type":["string","null"]}},"required":["kind","trigger","startedAt","finishedAt","error"]},"orders":{"type":["object","null"],"properties":{"kind":{"type":"string","enum":["products","orders"]},"trigger":{"type":"string"},"startedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"finishedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"summary":{},"error":{"type":["string","null"]}},"required":["kind","trigger","startedAt","finishedAt","error"]},"recent":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["products","orders"]},"trigger":{"type":"string"},"startedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"finishedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"summary":{},"error":{"type":["string","null"]}},"required":["kind","trigger","startedAt","finishedAt","error"]}}},"required":["products","orders","recent"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: Shopify"],"summary":"Sync products and orders from Shopify now","description":"Runs a product sync (the whole catalog) and an order sync (orders Shopify changed since `ordersSince`, default 2 days ago; Shopify only lets the app read the last 60 days). Synced orders are saved exactly as webhooks save them, and orders already up to date are left alone. The worker runs the same syncs on a schedule.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":"boolean","default":true},"orders":{"type":"boolean","default":true},"ordersSince":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"additionalProperties":false}}}},"responses":{"200":{"description":"What each sync did","content":{"application/json":{"schema":{"type":"object","properties":{"products":{},"orders":{}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Not set up on this server","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/vendor-costs":{"get":{"tags":["Admin: Shopify"],"summary":"Each vendor's fulfilment and shipping amounts","description":"Every vendor on a live synced product, with the amounts added to Shopify's cost per item to make landed cost. Shipping counts only when it's built into the price. Vendors with nothing set show zeros.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Vendors","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/VendorCost"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/vendor-costs/{vendor}":{"put":{"tags":["Admin: Shopify"],"summary":"Set a vendor's fulfilment and shipping amounts","description":"Per unit, in cents. A change here doesn't move any approved cost: approve the product's cost again to use it (Admin sees which products changed).","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"vendor","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fulfillmentCents":{"type":"integer","minimum":0},"shippingCents":{"type":"integer","minimum":0},"shippingBuiltIn":{"type":"boolean"}},"required":["fulfillmentCents","shippingCents","shippingBuiltIn"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"vendor":{"type":"string"},"fulfillmentCents":{"type":"integer"},"shippingCents":{"type":"integer"},"shippingBuiltIn":{"type":"boolean"}},"required":["vendor","fulfillmentCents","shippingCents","shippingBuiltIn"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/products/{id}/commission":{"get":{"tags":["Admin: Shopify"],"summary":"A product's cost, terms and profit check","description":"Each live variant's Shopify cost, approved landed cost (and whether it changed since), the terms in effect and their history, and, when a comp plan is loaded, the engine's analysis per variant: commission by level, maximum payout, what's kept, discount cap and the low-margin line, one-time and subscription.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Commission view","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/products/{id}/costs/approve":{"post":{"tags":["Admin: Shopify"],"summary":"Approve a product's landed cost","description":"Records today's Shopify cost per item plus the vendor's amounts as the landed cost commissions use, from `effectiveFrom` (default now), for every live variant or the ones named. Variants with no cost in Shopify are refused. Approving the same cost again records nothing.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"variantIds":{"type":"array","items":{"type":"string","pattern":"^\\d+$"},"minItems":1},"effectiveFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"Approved","content":{"application/json":{"schema":{"type":"object","properties":{"approved":{"type":"integer"},"unchanged":{"type":"integer"}},"required":["approved","unchanged"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/products/{id}/terms":{"post":{"tags":["Admin: Shopify"],"summary":"Set how a product pays commissions","description":"Its comp plan category, whether it pays at all, and an optional override (basis, rates, payout cap, member and subscription discounts), from `effectiveFrom` (default now) to an optional `effectiveTo`. Refused when the category isn't in the comp plan, an override is set on a variant with no approved cost, or any variant would lose money at maximum payout (the lower of one-time and subscription). Earlier terms are kept, so past orders keep their rates.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"categoryId":{"type":"string","minLength":1},"commissionable":{"type":"boolean","default":true},"override":{"$ref":"#/components/schemas/CommissionOverride"},"effectiveFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"effectiveTo":{"type":["string","null"],"format":"date-time","default":null,"example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["categoryId","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Saved, with the checks per variant","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/term-runs/preview":{"post":{"tags":["Admin: Shopify"],"summary":"Preview a bulk override run","description":"Picks products by vendor, tag, status, ids or being below the low-margin line, and shows each one before and after the terms: commission by level, maximum payout, what's kept and discount cap, one-time and subscription. Each gets an outcome: apply, skip_manual (a hand-set override, kept unless replaceManual), skip_not_below_line, blocked_missing_cost or blocked_loses_money. Changes nothing.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"filter":{"type":"object","properties":{"vendor":{"type":"string","minLength":1},"tag":{"type":"string","minLength":1},"status":{"type":"string","enum":["ACTIVE","DRAFT","ARCHIVED","UNLISTED"]},"productIds":{"type":"array","items":{"type":"string","pattern":"^\\d+$"},"maxItems":1000},"belowLine":{"type":"boolean"}},"additionalProperties":false},"terms":{"type":"object","properties":{"categoryId":{"type":"string","minLength":1},"commissionable":{"type":"boolean","default":true},"override":{"$ref":"#/components/schemas/CommissionOverride"}},"required":["categoryId"]},"replaceManual":{"type":"boolean","default":false},"effectiveFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["filter","terms"],"additionalProperties":false}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/shopify/term-runs":{"post":{"tags":["Admin: Shopify"],"summary":"Apply a bulk override run","description":"Same body as the preview, plus a reason. Every product the preview marks `apply` gets the terms from `effectiveFrom`, as one recorded run; each result is a normal override that can be changed on its own.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"filter":{"type":"object","properties":{"vendor":{"type":"string","minLength":1},"tag":{"type":"string","minLength":1},"status":{"type":"string","enum":["ACTIVE","DRAFT","ARCHIVED","UNLISTED"]},"productIds":{"type":"array","items":{"type":"string","pattern":"^\\d+$"},"maxItems":1000},"belowLine":{"type":"boolean"}},"additionalProperties":false},"terms":{"type":"object","properties":{"categoryId":{"type":"string","minLength":1},"commissionable":{"type":"boolean","default":true},"override":{"$ref":"#/components/schemas/CommissionOverride"}},"required":["categoryId"]},"replaceManual":{"type":"boolean","default":false},"effectiveFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["filter","terms","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Applied","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Admin: Shopify"],"summary":"Bulk override runs, newest first","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Runs","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/referral":{"get":{"tags":["Me"],"summary":"Your referral code and the people you've referred","description":"Share the code on a link; a friend's first order that carries it makes you their sponsor. Shows your sponsor (first name only) and how many people you sponsor directly.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Referral","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyReferral"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/users/{id}/referrals":{"get":{"tags":["Admin: partners"],"summary":"Someone's sponsor, upline and first line","description":"Their referral code, the sponsor in effect and every past one (how each was set), the upline a commission run would pay for an order placed now (level and step; no step 0 on a Partner's own purchase), and who they sponsor directly.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Referrals","content":{"application/json":{"schema":{}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/users/{id}/sponsor":{"post":{"tags":["Admin: partners"],"summary":"Change someone's sponsor","description":"By the new sponsor's account id or referral code, from `effectiveAt` (default now), with a reason. Refused when it would put the person in their own upline. Earlier sponsors stay in the history, and orders before the change keep their upline.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sponsorId":{"type":"string","minLength":1},"code":{"type":"string","minLength":1},"effectiveAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Changed","content":{"application/json":{"schema":{"type":"object","properties":{"sponsorId":{"type":"string"},"effectiveAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["sponsorId","effectiveAt"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/level-commitments":{"get":{"tags":["Admin: partners"],"summary":"Monthly sales commitments per level, with their history","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Commitments","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: partners"],"summary":"Change a level's commitment from a date","description":"The monthly sales needed to keep the level, in cents, and how many months of it points can hold. Takes effect from `effectiveFrom`; earlier months keep the amounts they had.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"levelId":{"type":"string","minLength":1},"monthlyCents":{"type":"integer","minimum":0},"capMonths":{"type":"integer","minimum":1,"maximum":60},"effectiveFrom":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["levelId","monthlyCents","capMonths","effectiveFrom","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Saved","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/level-runs/preview":{"post":{"tags":["Admin: partners"],"summary":"Preview a month's level run","description":"For every Partner: first-line sales in the month (own purchases, plus orders by people they sponsor, paid and after refunds), the 3-month average, points before and after, and the level they'd hold from the next month (up, down, kept, regained, none, or grace for a new Partner's first two calendar months). The month must have ended, and months run in order. Changes nothing.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$"}},"required":["month"],"additionalProperties":false}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/level-runs":{"post":{"tags":["Admin: partners"],"summary":"Apply a month's level run","description":"Same as the preview, then records it: points entries, each level change (from the next month's start, with a partner.updated event), and the month's record per Partner. Once per month, in order.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["month","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Applied","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Admin: partners"],"summary":"Applied level runs, newest first","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Runs","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/points":{"get":{"tags":["Partner"],"summary":"Your sales points and how each month went","description":"Points are kept in cents of sales (100 = 1 point = $1). They have no cash value and can't be transferred.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Points","content":{"application/json":{"schema":{}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/orders/{id}/commission-preview":{"get":{"tags":["Admin: commissions"],"summary":"What the comp plan would pay on an order","description":"Line by line, with the product terms, approved cost, comp plan, upline and levels in effect on the order date: commission value, who's paid what and at which step, what's unpaid and why (trimmed by the cap, Basic beyond the first order), lines not set up yet, and when it would become payable after the hold. A preview: nothing is stored, and `assumptions` lists what isn't decided yet.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/commissions/preview":{"post":{"tags":["Admin: commissions"],"summary":"What the comp plan would pay across a period","description":"Totals per Partner across the paid orders placed from `from` up to `to` (at most `limit` orders, default 500), and the products whose lines aren't set up for commissions yet. A preview: nothing is stored.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"to":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"limit":{"type":"integer","minimum":1,"maximum":2000,"default":500}},"required":["from","to"],"additionalProperties":false}}}},"responses":{"200":{"description":"Preview","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/articles":{"get":{"tags":["Content"],"summary":"Articles, newest first","description":"Copied from WordPress. Filter by category slug. Each item says whether the caller can open it (`access`); locked items still list with their title and excerpt.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"required":false,"name":"category","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"From the previous page's nextCursor"},"required":false,"description":"From the previous page's nextCursor","name":"cursor","in":"query"}],"responses":{"200":{"description":"Articles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleList"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/articles/{slug}":{"get":{"tags":["Content"],"summary":"One article","description":"By its WordPress slug, so URLs carry over. `contentHtml` is null when the caller can't open it; `access` says why and what unlocks it.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"The article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Article"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/categories":{"get":{"tags":["Content"],"summary":"Article categories","responses":{"200":{"description":"Categories","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"wpId":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string"}},"required":["wpId","slug","name"]}}},"required":["items"]}}}}}}},"/api/v1/content/recipes":{"get":{"tags":["Content"],"summary":"Recipes, newest first","description":"Copied from WP Recipe Maker. Filter by diet phase (`phase`), `course`, suitable diet (`diet`), at most `maxCalories` per serving, or text in the name. Tag filters match WordPress's term names without regard to case. Locked recipes still list; their ingredients and steps are only in the single-recipe answer, for callers who can open them.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"phase","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"course","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"diet","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"maximum":10000},"required":false,"name":"maxCalories","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"From the previous page's nextCursor"},"required":false,"description":"From the previous page's nextCursor","name":"cursor","in":"query"}],"responses":{"200":{"description":"Recipes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeList"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/recipes/{slug}":{"get":{"tags":["Content"],"summary":"One recipe","description":"By its WordPress slug. Ingredients, steps, notes and full nutrition are null when the caller can't open it; `access` says why and what unlocks it.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"The recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/content/sync":{"get":{"tags":["Admin: content"],"summary":"WordPress sync runs, and what's been copied","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Sync status","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"articles":{"type":"integer"},"recipes":{"type":"integer"},"categories":{"type":"integer"},"recent":{"type":"array","items":{"type":"object","properties":{"mode":{"type":"string","enum":["full","changes","webhook"]},"trigger":{"type":"string"},"startedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"finishedAt":{"type":["string","null"],"format":"date-time","example":"2026-11-06T15:00:00.000Z"},"summary":{},"error":{"type":["string","null"]}},"required":["mode","trigger","startedAt","finishedAt","error"]}}},"required":["configured","articles","recipes","categories","recent"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: content"],"summary":"Sync articles and recipes from WordPress now","description":"`changes` (default) copies what WordPress changed since the last good sync; `full` copies everything and marks items gone from WordPress as deleted (refused if WordPress answers with nothing). The worker runs both on a schedule. Polite to the live site: one request at a time, with pauses.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["full","changes"],"default":"changes"}},"additionalProperties":false}}}},"responses":{"200":{"description":"What the sync did","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Not set up on this server","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/directory":{"get":{"tags":["Directory"],"summary":"Find a coach, practitioner or vendor","description":"Listed profiles, by name. Filter by `service` (a service type id), `kind`, `region` (state) and `city` (both without regard to case), `remote=true` for online sessions, or text in the name or headline.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":60},"required":false,"name":"service","in":"query"},{"schema":{"type":"string","enum":["partner","coach","practitioner","vendor"]},"required":false,"name":"kind","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"region","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"city","in":"query"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"remote","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":24},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":400},"required":false,"name":"cursor","in":"query"}],"responses":{"200":{"description":"Listings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryList"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/directory/service-types":{"get":{"tags":["Directory"],"summary":"The services listings can offer","responses":{"200":{"description":"Service types","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["items"]}}}}}}},"/api/v1/directory/{slug}":{"get":{"tags":["Directory"],"summary":"A profile","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":100},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryListing"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/listing":{"get":{"tags":["Directory"],"summary":"My directory listing","description":"The live profile (null before the first approval), the edit waiting for review, and the last review's decision and note. `canList` says whether this account can have a listing (Partners, providers and vendors).","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"My listing","content":{"application/json":{"schema":{"type":"object","properties":{"canList":{"type":"boolean"},"listing":{"$ref":"#/components/schemas/DirectoryListingAdmin"},"pending":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"submittedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"profile":{"$ref":"#/components/schemas/DirectoryProfileInput"}},"required":["id","submittedAt","profile"]},"lastReview":{"type":["object","null"],"properties":{"decision":{"type":"string","enum":["approved","sent_back","rejected"]},"note":{"type":["string","null"]},"at":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["decision","note","at"]}},"required":["canList","listing","pending","lastReview"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Directory"],"summary":"Submit my profile for review","description":"The whole profile. It goes live once Admin approves it; until then the approved version (if any) stays live. A new submission replaces one still waiting. Credentials show as unverified until Absofitly checks them.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"$ref":"#/components/schemas/DirectoryProfileInput"},"note":{"type":"string","maxLength":1000}},"required":["profile"],"additionalProperties":false}}}},"responses":{"200":{"description":"Submitted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["waiting_for_review"]},"submissionId":{"type":"string","format":"uuid"}},"required":["status","submissionId"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/listing/visibility":{"post":{"tags":["Directory"],"summary":"Show or hide my listing","description":"Takes effect at once. A listing Absofitly took down can't be shown again from here.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"listed":{"type":"boolean"}},"required":["listed"],"additionalProperties":false}}}},"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["listed","hidden"]}},"required":["status"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/listings":{"get":{"tags":["Admin: directory"],"summary":"All listings","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","enum":["draft","listed","hidden","taken_down"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"Listings","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DirectoryListingAdmin"}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin: directory"],"summary":"Add a listing","description":"For a vendor without an account, or on someone's behalf (`userId`). Live at once (or hidden); Admin's own edits need no review.","security":[{"session":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"$ref":"#/components/schemas/DirectoryProfileInput"},"userId":{"type":"string","minLength":1},"status":{"type":"string","enum":["listed","hidden"],"default":"listed"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["profile","reason"],"additionalProperties":false}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryListingAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/listings/{id}":{"put":{"tags":["Admin: directory"],"summary":"Edit a listing's live profile","description":"Goes live at once, with a reason in the audit log. `verifyCredentials` marks credentials Absofitly has checked; unchanged ones stay verified.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"$ref":"#/components/schemas/DirectoryProfileInput"},"verifyCredentials":{"type":"array","items":{"type":"integer","minimum":0,"maximum":19},"maxItems":20,"description":"Positions in `credentials` Admin has checked"},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["profile","reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryListingAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/listings/{id}/status":{"post":{"tags":["Admin: directory"],"summary":"List, hide or take down a listing","description":"Taking a listing down removes it at once and only Admin can list it again (spec §3.2). A reason is required and logged.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["listed","hidden","taken_down"]},"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["status","reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryListingAdmin"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/submissions":{"get":{"tags":["Admin: directory"],"summary":"Edits waiting for review","description":"Oldest first, each with the live profile beside the proposed one. Only the latest edit per listing is shown; a newer one replaces an older one.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Waiting","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"listingId":{"type":"string","format":"uuid"},"slug":{"type":"string"},"status":{"type":"string","enum":["draft","listed","hidden","taken_down"]},"submittedBy":{"type":"string"},"submittedAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"note":{"type":["string","null"]},"live":{"allOf":[{"$ref":"#/components/schemas/DirectoryProfileInput"},{"type":["object","null"]}]},"proposed":{"$ref":"#/components/schemas/DirectoryProfileInput"}},"required":["id","listingId","slug","status","submittedBy","submittedAt","note","live","proposed"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/submissions/{id}/review":{"post":{"tags":["Admin: directory"],"summary":"Approve, send back or reject an edit","description":"Approving puts the edit live (a first approval lists the profile). Sending back or rejecting needs a note to the author. `verifyCredentials` marks credentials Absofitly has checked.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","enum":["approved","sent_back","rejected"]},"note":{"type":"string","maxLength":2000},"verifyCredentials":{"type":"array","items":{"type":"integer","minimum":0,"maximum":19},"maxItems":20,"description":"Positions in `credentials` Admin has checked"}},"required":["decision"],"additionalProperties":false}}}},"responses":{"200":{"description":"Reviewed","content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string"},"listing":{"$ref":"#/components/schemas/DirectoryListingAdmin"}},"required":["decision","listing"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/service-types":{"get":{"tags":["Admin: directory"],"summary":"Service types, including retired ones","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Service types","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"active":{"type":"boolean"}},"required":["id","name","position","active"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/service-types/{id}":{"put":{"tags":["Admin: directory"],"summary":"Add or change a service type","description":"Retire one with `active: false`: it stops showing on profiles and in search, and listings keep it in case it's brought back. Ids are kept for good, so links with `?service=` keep working.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,59}$"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80},"position":{"type":"integer","minimum":0,"maximum":10000,"default":0},"active":{"type":"boolean","default":true}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"position":{"type":"integer"},"active":{"type":"boolean"}},"required":["id","name","position","active"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/directory/partner-listings":{"post":{"tags":["Admin: directory"],"summary":"List Partners who aren't listed yet","description":"Partners are listed automatically by name with the Certified Absofitly Partner badge (the worker does this every minute); this runs it now.","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"}},"required":["added"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/leads/forms":{"get":{"tags":["Leads"],"summary":"The site's forms","description":"Forms that are on, and whether each asks for a phone number.","responses":{"200":{"description":"Forms","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"phoneRequired":{"type":"boolean"}},"required":["id","name","phoneRequired"]}}},"required":["items"]}}}}}}},"/api/v1/leads/{formId}":{"post":{"tags":["Leads"],"summary":"Send a form","description":"Works signed in or not. Send `startedAt` (when the page loaded, ms) and an empty hidden `website` field: they keep bots out. The answer is the same whether or not a submission is judged spam. A referral code the visitor arrived with (the absofitly_ref cookie) is kept with the lead.","parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,40}$"},"required":true,"name":"formId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadInput"}}}},"responses":{"202":{"description":"Received","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/leads":{"get":{"tags":["Admin: leads"],"summary":"The leads inbox","description":"Newest first. Spam is left out unless `status=spam`. Filter by form, status, owner, or text in the name, email or message.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","maxLength":40},"required":false,"name":"form","in":"query"},{"schema":{"type":"string","enum":["new","contacted","qualified","closed","spam"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","maxLength":100},"required":false,"name":"ownerId","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"From the previous page's nextCursor"},"required":false,"description":"From the previous page's nextCursor","name":"cursor","in":"query"}],"responses":{"200":{"description":"Leads","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"nextCursor":{"type":["string","null"]}},"required":["items","nextCursor"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/leads/export.csv":{"get":{"tags":["Admin: leads"],"summary":"Export leads as CSV","description":"The same filters as the inbox, plus `from` and `to` (sent at). At most 10,000 rows, newest first. Cells a spreadsheet would run as a formula are prefixed with an apostrophe.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","maxLength":40},"required":false,"name":"form","in":"query"},{"schema":{"type":"string","enum":["new","contacted","qualified","closed","spam"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","maxLength":100},"required":false,"name":"ownerId","in":"query"},{"schema":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"required":false,"name":"from","in":"query"},{"schema":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"},"required":false,"name":"to","in":"query"}],"responses":{"200":{"description":"CSV","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/leads/{id}":{"get":{"tags":["Admin: leads"],"summary":"A lead and its notes","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The lead","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Lead"},{"type":"object","properties":{"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"authorUserId":{"type":"string"},"body":{"type":"string"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","authorUserId","body","createdAt"]}}},"required":["notes"]}]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Admin: leads"],"summary":"Change a lead's status or owner","description":"Marking a spam lead as anything else sends it to Zapier (lead.received) then.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["new","contacted","qualified","closed","spam"]},"ownerUserId":{"type":["string","null"],"minLength":1}},"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/leads/{id}/notes":{"post":{"tags":["Admin: leads"],"summary":"Add a note to a lead","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":5000}},"required":["body"],"additionalProperties":false}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time","example":"2026-11-06T15:00:00.000Z"}},"required":["id","createdAt"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/lead-forms":{"get":{"tags":["Admin: leads"],"summary":"Forms, including ones turned off","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Forms","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"ownerUserId":{"type":["string","null"]},"active":{"type":"boolean"},"phoneRequired":{"type":"boolean"}},"required":["id","name","ownerUserId","active","phoneRequired"]}}},"required":["items"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/lead-forms/{formId}":{"put":{"tags":["Admin: leads"],"summary":"Add or change a form","description":"Set who new leads go to, whether it asks for a phone, or turn it off.","security":[{"session":[]},{"bearer":[]}],"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,40}$"},"required":true,"name":"formId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80},"ownerUserId":{"type":["string","null"],"minLength":1,"default":null},"active":{"type":"boolean","default":true},"phoneRequired":{"type":"boolean","default":false}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"ownerUserId":{"type":["string","null"]},"active":{"type":"boolean"},"phoneRequired":{"type":"boolean"}},"required":["id","name","ownerUserId","active","phoneRequired"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bundles":{"get":{"tags":["Bundles"],"summary":"Plans and bundles on sale, with what each includes","parameters":[{"schema":{"type":"string","enum":["en","es","pt"],"default":"en"},"required":false,"name":"locale","in":"query"}],"responses":{"200":{"description":"On sale","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Bundle"}}},"required":["items"]}}}}}}},"/api/v1/bundles/{key}":{"get":{"tags":["Bundles"],"summary":"One plan or bundle on sale","parameters":[{"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,62}$"},"required":true,"name":"key","in":"path"},{"schema":{"type":"string","enum":["en","es","pt"],"default":"en"},"required":false,"name":"locale","in":"query"}],"responses":{"200":{"description":"The bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bundle"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/plans":{"get":{"tags":["Bundles"],"summary":"Plans and bundles the person bought, and how each item is going","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Plans","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyPlans"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/credits":{"get":{"tags":["Bundles"],"summary":"Coaching and training credits left","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Balances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyCredits"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/apps":{"get":{"tags":["Bundles"],"summary":"Third-party apps the person has access to","security":[{"session":[]},{"bearer":[]}],"responses":{"200":{"description":"Apps","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyApps"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}