This file documents current runtime API contracts and the target additive Screenprinting API contracts.
For database contracts, read DATA_MODEL.md. For vocabulary, read GLOSSARY.md.
- Success responses include
ok: true. - Error responses include
ok: falseanderror. - Tenant runtime routes use
[slug]as the organization slug. - Tenant-mutating routes must require tenant-session access.
- Tenant data returned by API routes must be scoped by
organization_idresolved from the slug. - Provider secrets must never be returned in API responses.
Purpose: runtime health and environment readiness.
Response:
{
"ok": true,
"runtime": "supabase-first",
"supabaseConfigured": true,
"connectorSecretEnvelopeConfigured": true,
"checkedAt": "2026-04-27T00:00:00.000Z"
}Cache: private, max-age=15, stale-while-revalidate=60.
Purpose: organization runtime snapshot.
Success response:
{
"ok": true,
"snapshot": {
"organization": "Organization",
"integrations": ["IntegrationInstallation"],
"recentSyncJobs": ["SyncJob"],
"syncCursors": ["SyncCursor"],
"syncJobStatusCounts": [{ "status": "queued", "count": 1 }],
"recentAuditEvents": ["AuditEvent"]
}
}Errors:
404 { "ok": false, "error": "organization_not_found" }
Cache: private, max-age=10, stale-while-revalidate=60.
Purpose: account detail runtime payload.
Success response:
{
"ok": true,
"detail": {
"organization": "Organization",
"account": {
"id": "uuid",
"name": "Account name",
"status": "active",
"leadStatus": null,
"latitude": 40.0,
"longitude": -73.0,
"customFields": {}
},
"identities": ["AccountIdentity"],
"contacts": ["AccountContact"],
"activities": ["AccountActivity"],
"recentOrders": ["AccountOrder"],
"allOrders": ["AccountOrder"],
"orderSummary": {
"totalOrders": 12,
"totalRevenue": 12345.67,
"nonTransferOrders": 12,
"lastOrderDate": "2026-04-23",
"customerSinceDate": "2024-01-15"
}
}
}Errors:
404 { "ok": false, "error": "account_not_found" }
Cache: private, max-age=10, stale-while-revalidate=45.
Purpose: create tenant account activity note.
Auth: tenant-session required.
Request:
{
"note": "Met with customer and confirmed reorder timing."
}Success response:
{
"ok": true,
"activity": {
"id": "uuid",
"activityType": "check_in",
"summary": "Met with customer and confirmed reorder timing.",
"occurredAt": "2026-04-27T00:00:00.000Z"
}
}Errors:
401when tenant login is missing400 { "ok": false, "error": "note_required" }404 { "ok": false, "error": "account_not_found" }
Cache: no-store.
Purpose: calculate PICC savings report.
Request:
{
"year": 2026
}Success response:
{
"ok": true,
"report": "PppSavingsReport"
}Errors:
404 { "ok": false, "error": "account_not_found" }
Cache: no-store.
Purpose: filtered territory dashboard and pins.
Query parameters:
| Param | Meaning |
|---|---|
q |
Search text. |
flag |
One of missing_referral_source, missing_sample_delivery, no_address_available, dnc_flagged. |
rep |
Sales rep facet value. |
status |
Account status facet value. |
referralSource |
Referral/source facet value. |
vendorDayStatus |
Vendor day facet value. |
leadGrade |
FraterniTees lead grade facet value. |
Success response:
{
"ok": true,
"counts": {
"accounts": 100,
"geocodedPins": 90,
"orders": 250,
"contacts": 80,
"territoryBoundaries": 3,
"territoryMarkers": 4,
"noReferralSource": 10,
"noLastSampleDeliveryDate": 12,
"noAddressAvailable": 5,
"dncFlagged": 2
},
"appliedFilters": {
"search": null,
"flag": null,
"rep": null,
"status": null,
"referralSource": null,
"vendorDayStatus": null,
"leadGrade": null
},
"repFacets": [{ "name": "Rep", "count": 10 }],
"statusFacets": [{ "name": "active", "count": 10 }],
"referralSourceFacets": [],
"vendorDayFacets": [],
"leadGradeFacets": [],
"pins": ["TerritoryAccountPin"]
}Errors:
404 { "ok": false, "error": "organization_not_found" }
Cache: private, max-age=10, stale-while-revalidate=45.
Purpose: tenant territory boundaries and markers.
Success response:
{
"ok": true,
"boundaries": ["TerritoryBoundaryRuntime"],
"markers": ["TerritoryMarkerRuntime"]
}Errors:
404 { "ok": false, "error": "organization_not_found" }
Cache: private, max-age=10, stale-while-revalidate=45.
Purpose: browser map provider config for a tenant.
Success response:
{
"ok": true,
"mapProvider": "google_maps",
"browserApiKey": "browser-safe-key-or-null",
"tileUrlTemplate": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
"tileAttribution": "© OpenStreetMap contributors",
"configured": true,
"upgraded": true
}Errors:
404 { "ok": false, "error": "organization_not_found" }
Cache: private, max-age=300, stale-while-revalidate=900.
Purpose: sync operations visibility.
Success response:
{
"ok": true,
"organization": "Organization",
"statusCounts": [{ "status": "success", "count": 10 }],
"currentSyncErrors": 0,
"cursors": ["SyncCursor"],
"jobs": ["SyncJob"]
}Errors:
404 { "ok": false, "error": "organization_not_found" }
Cache: private, max-age=5, stale-while-revalidate=20.
Purpose: self-serve connector install/update.
Auth: tenant-session required.
Request:
{
"provider": "printavo",
"fields": {
"email": "ops@example.com",
"apiKey": "secret"
}
}Success response:
{
"ok": true,
"integration": {
"id": "uuid",
"provider": "printavo",
"displayName": "Printavo",
"externalAccountId": "ops@example.com",
"status": "active",
"updatedAt": "2026-04-27T00:00:00.000Z"
}
}Errors:
401when tenant login is missing400for invalid provider, disabled connector, or missing required fields403for guided-only connectors404for missing organization
Purpose: start tenant-facing Meta/Instagram OAuth for owned Instagram account connection.
Auth: tenant-session required.
Query:
mode=instagram_business_login | facebook_login_business
Behavior:
- Resolves tenant connection mode preferences and platform-owned Meta app credentials from backend env.
- Redirects to Instagram Login for
instagram_business_login. - Redirects to Facebook Login for Business for
facebook_login_business. - Returns to the global callback route with signed tenant state.
Errors redirect back to /integrations?org=<slug>&meta_oauth_error=<message> when platform app credentials or tenant state are missing.
Purpose: complete Meta/Instagram OAuth, store encrypted token, and attempt owned-account discovery.
Auth: signed OAuth state; tenant session is normally present from the start route.
Behavior:
- Exchanges OAuth code for an access token using the selected Meta mode.
- Stores the tenant authorization token in
integration_secret. - Updates the
metaintegration config with auth mode, Graph API version, scopes, and OAuth status. - Runs owned-account scan when required scopes permit it.
- Redirects to
/screenprinting?org=<slug>&module=social&social=accounts.
Errors redirect back to /integrations?org=<slug>&meta_oauth_error=<message>.
Purpose: legacy slug-scoped callback compatibility route.
Behavior: verifies that signed OAuth state matches the slug, then uses the same callback completion path as the global route. New Meta app configuration should register the global callback URL only.
Purpose: read tenant plugin capability settings.
Success response:
{
"ok": true,
"plugins": {
"routePlanning": { "enabled": true },
"printavoSync": { "enabled": true },
"runtimeDiagnostics": { "enabled": true }
}
}Purpose: update tenant plugin capability setting.
Auth: tenant-session required.
Request:
{
"plugin": "printavoSync",
"enabled": true
}Valid plugin keys today: routePlanning, printavoSync, runtimeDiagnostics.
Success response:
{
"ok": true,
"plugins": {}
}Errors:
401when tenant login is missing400for invalid plugin or missing boolean404for missing organization
Purpose: geocode missing account coordinates under tenant geocoding policy.
Auth: tenant-session required.
Request:
{
"limit": 25
}Success response:
{
"ok": true,
"summary": {
"attempted": 25,
"updated": 20,
"skipped": 5,
"errors": []
}
}Errors:
401when tenant login is missing404when organization is missing502for provider/geocode failures
Purpose: queue dirty Notion page sync work.
Auth: tenant-session required.
Request:
{
"pageIds": ["notion-page-id"],
"reason": "manual_refresh"
}Rules: pageIds must include 1 to 100 non-empty strings. reason must be 3 to 120 characters and defaults to manual_refresh.
Success response:
{
"ok": true,
"queued": true,
"job": "SyncJob"
}Status: 202.
Errors:
401when tenant login is missing400 { "ok": false, "error": "invalid_request", "issues": [] }500for unexpected failures
Cache: no-store.
Purpose: read Printavo sync status for a tenant with Printavo enabled.
Auth: tenant-session required.
Success response:
{
"ok": true,
"status": {
"cursor": "SyncCursor or null",
"lastSuccessfulSyncAt": "2026-04-27T00:00:00.000Z",
"lastError": null
}
}Errors:
401when tenant login is missing400when Printavo is not enabled for the tenant workspace404for missing organization502for sync status failure
Purpose: run read-only Printavo sync.
Auth: tenant-session required.
Request:
{
"email": "ops@example.com",
"apiKey": "secret",
"mode": "latest",
"pageLimit": 3,
"pageSize": 25,
"reset": false
}Rules:
modeislatestorbackfill; defaultlatest.latestpage limit is clamped from 1 to 5.backfillpage limit is clamped from 1 to 20.pageSizeis clamped from 1 to 25.- email/api key are optional when a saved tenant connector secret is available.
Success response:
{
"ok": true,
"mode": "latest",
"ordersFetched": 75,
"accountsUpserted": 10,
"contactsUpserted": 5,
"ordersUpserted": 75,
"warnings": []
}Errors:
401when tenant login is missing400when Printavo is not enabled for the tenant workspace404for missing organization502for sync failure
Purpose: preview Printavo statuses and sample lead scores without saving credentials.
Auth: tenant-session required.
Request:
{
"email": "ops@example.com",
"apiKey": "secret"
}Success response:
{
"ok": true,
"statuses": [],
"statusFilters": {},
"sampleOrderCount": 25,
"scores": []
}Errors:
401when tenant login is missing400when email/api key are missing502for provider failure
Purpose: read tenant Printavo daily sync automation settings.
Auth: tenant-session required.
Success response:
{
"ok": true,
"automation": {
"enabled": true,
"hourUtc": 8
}
}Errors:
401when tenant login is missing404for missing organization
Purpose: update tenant Printavo daily sync automation settings.
Auth: tenant-session required.
Request:
{
"enabled": true,
"hourUtc": 8
}Success response:
{
"ok": true,
"automation": {
"enabled": true,
"hourUtc": 8
}
}Errors:
401when tenant login is missing400whenenabledis not a boolean404for missing organization
Purpose: list tenant change requests.
Auth: tenant-session required.
Success response:
{
"ok": true,
"requests": ["ChangeRequest"]
}Errors:
401when tenant login is missing404for missing organization
Purpose: create tenant change request with optional attachments and capture context.
Auth: tenant-session required.
Content type: multipart/form-data.
Fields:
| Field | Required | Notes |
|---|---|---|
title |
yes | Non-empty. |
problem |
yes | Non-empty. |
requestedOutcome |
yes | Non-empty. |
currentUrl |
no | Page URL. |
surface |
no | Surface/module label. |
classification |
no | config, package, primitive, or core. |
businessContext |
no | Context. |
acceptanceCriteria |
no | User-visible done condition. |
captureContext |
no | JSON capture context. |
attachments |
no | One or more files. |
Success response:
{
"ok": true,
"request": "ChangeRequest",
"warnings": []
}Errors:
401when tenant login is missing400when title/problem/requested outcome are missing or service validation fails404for missing organization
Purpose: update tenant change request.
Auth: tenant-session required.
Content type: multipart/form-data.
Fields:
| Field | Required | Notes |
|---|---|---|
title |
yes | Non-empty. |
problem |
yes | Non-empty. |
requestedOutcome |
yes | Non-empty. |
businessContext |
no | Context. |
acceptanceCriteria |
no | Done condition. |
status |
no | queued, resolved, declined, stale, requires_additional_feedback. |
attachments |
no | Additional files. |
Success response:
{
"ok": true,
"request": "ChangeRequest",
"warnings": []
}Errors:
401when tenant login is missing400for missing required fields or service validation failure404for missing organization
Purpose: delete tenant change request.
Auth: tenant-session required.
Success response:
{ "ok": true }Errors:
401when tenant login is missing404for missing organization or request
Purpose: resolve tenant access from an email and write tenant-session cookies.
Request:
{
"email": "person@example.com"
}Success response:
{
"ok": true,
"access": {
"slug": "fraternitees",
"templateId": "fraternity-sales"
}
}Errors:
400 { "ok": false, "error": "Enter a valid work email address." }
Purpose: clear tenant-session cookies.
Success response:
{ "ok": true }Purpose: create a new organization from a tenant workspace seed and write tenant-session cookies.
Request:
{
"templateId": "fraternity-sales",
"email": "owner@example.com",
"companyName": "Example Screenprinter",
"slug": "example-screenprinter"
}Validation:
templateId: non-empty string matching a workspace seed id.email: valid email.companyName: 2 to 120 characters.slug: 2 to 48 lowercase letters, numbers, and hyphens.- guided-only tenant types require an allowed email domain.
Success response:
{
"ok": true,
"organization": {
"id": "uuid",
"slug": "example-screenprinter",
"name": "Example Screenprinter"
},
"redirectTo": "/o/example-screenprinter/integrations"
}Errors:
400for invalid payload or unknown tenant type/workspace seed403for guided setup domain restriction409when slug already exists
These routes should be added when implementing the full Screenprinting tenant type. They must remain read-only to Printavo in the first implementation. Mutations write only product-owned configuration, opportunities, drafts, social links, alerts, dashboards, or review state.
Purpose: return compiled Screenprinting config.
Response:
{
"ok": true,
"config": {
"statusMappings": [],
"tagMappings": [],
"fieldTrust": [],
"categories": [],
"reorderRules": [],
"emailTemplates": [],
"socialAccountCategories": [],
"alertRules": [],
"dashboards": [],
"featureFlags": {}
},
"source": {
"tenantTypeVersion": 1,
"workspaceVersion": 1,
"organizationOverridesVersion": 1
}
}Acceptance check: changing a saved config value changes downstream Sales/Social list filters without code edits.
Purpose: update tenant-owned Screenprinting configuration.
Auth: tenant-session admin/owner required when role enforcement exists; tenant-session required until roles are enforced.
Request:
{
"section": "statusMappings",
"changes": [
{
"sourceValue": "Quote Sent",
"targetBucket": "quoted",
"trustLevel": "trusted",
"enabled": true
}
],
"previewToken": "optional-impact-preview-token"
}Response:
{
"ok": true,
"config": {},
"impact": {
"affectedOrders": 42,
"affectedAccounts": 17,
"affectedDashboards": ["sales_overview"]
}
}Purpose: preview impact before saving mapping changes.
Request:
{
"section": "statusMappings",
"draftChanges": []
}Response:
{
"ok": true,
"previewToken": "token",
"impact": {
"affectedOrders": 42,
"quotedCountDelta": 12,
"completedCountDelta": -2,
"dirtyRecords": 4,
"warnings": []
}
}Purpose: Sales dashboard metrics.
Query parameters: from, to, dateRange, rep, team, category, compare.
Response:
{
"ok": true,
"metrics": {
"revenue": 378900,
"averageOrderValue": 3643,
"bulkOrders": 104,
"personalSales": 104,
"repeatRevenue": 1800000,
"dueReorders": 82
},
"ticker": [],
"managerPerformance": [],
"periodPerformance": [],
"dirtyDataWarnings": []
}Purpose: read-only Printavo-derived order list.
Query parameters: q, statusBucket, paymentBucket, productionFrom, productionTo, customerDueFrom, customerDueTo, rep, team, synced, savedView, page, pageSize.
Response:
{
"ok": true,
"orders": [
{
"id": "uuid",
"orderNumber": "6538",
"customerName": "Education Bar Crawl",
"status": "JOB COMPLETE",
"statusBucket": "completed",
"paymentBucket": "paid",
"orderTotal": 183.12,
"productionDate": "2026-04-23",
"customerDueDate": "2026-04-23",
"managerName": "Jenna",
"teamName": "Greek",
"sourceUrl": "https://..."
}
],
"facets": {},
"savedViews": [],
"pagination": {}
}Purpose: list tenant-scoped order saved views stored in dashboard_definition.
Query parameters: module (defaults to sales_orders).
Response:
{
"ok": true,
"savedViews": []
}Purpose: create a product-owned saved view. No Printavo write-back.
Request:
{
"module": "sales_orders",
"name": "Unpaid Greek orders",
"filters": {
"paymentBucket": "unpaid",
"teamName": "Greek"
},
"columns": ["customer", "job", "total", "status"],
"sort": { "key": "orderCreatedAt", "direction": "desc" }
}Response:
{
"ok": true,
"savedView": {}
}Purpose: order detail and line items.
Response:
{
"ok": true,
"order": {
"id": "uuid",
"source": "printavo",
"sourceUrl": "https://...",
"totals": {
"total": 183.12,
"cost": null,
"margin": null
},
"lineItems": [],
"customer": {},
"sourcePayloadAvailable": true
}
}Purpose: read persisted monthly manager goals with current Printavo-derived actuals.
Query parameters: period as YYYY-MM.
Response:
{
"ok": true,
"period": "2026-04",
"goals": [],
"source": "persisted"
}Purpose: save monthly manager goals in product-owned tenant storage.
Request:
{
"period": "2026-04",
"goals": [
{
"managerName": "Jenna Koss",
"revenueGoal": 50000,
"ordersGoal": 20,
"storesGoal": 3
}
]
}Response:
{
"ok": true,
"period": "2026-04",
"goals": []
}Purpose: opportunity board/list.
Query parameters: pipeline, stage, owner, source, accountId, view.
Response:
{
"ok": true,
"pipelines": [
{
"key": "sales_pipeline",
"stages": [
{
"key": "new_lead",
"label": "New Lead",
"count": 2,
"value": 1000,
"opportunities": []
}
]
}
]
}Purpose: create product-owned opportunity.
Request:
{
"accountId": "uuid",
"title": "Spring reorder",
"pipelineKey": "sales_pipeline",
"stageKey": "new_lead",
"value": 1000,
"sourceType": "reorder",
"dueAt": "2026-05-01T15:00:00.000Z"
}Response:
{
"ok": true,
"opportunity": {}
}Purpose: update product-owned opportunity stage, owner, value, status, due date, or links.
Response:
{
"ok": true,
"opportunity": {}
}Purpose: reorder signals and follow-up state.
Query parameters: bucket, owner, category, from, to, accountId.
Response:
{
"ok": true,
"buckets": {
"overdue": [],
"due": [],
"upcoming": [],
"snoozed": []
},
"counts": {
"overdue": 3,
"due": 82,
"upcoming": 114
}
}Purpose: snooze a reorder signal.
Request:
{
"snoozedUntil": "2026-05-15",
"reason": "Customer asked to revisit after finals"
}Response:
{
"ok": true,
"reorderSignal": {}
}Purpose: render a draft-only email from an editable tenant-owned email template.
Request:
{
"templateKey": "reorder_follow_up",
"accountId": "uuid",
"contactId": "uuid",
"opportunityId": "uuid",
"reorderSignalId": "uuid"
}Response:
{
"ok": true,
"draft": {
"to": "customer@example.com",
"subject": "Re-order time?",
"body": "Draft body",
"copyText": "Full email text"
}
}No email is sent.
Purpose: manually record that a draft was sent outside the app.
Response:
{
"ok": true,
"activity": {}
}Purpose: Social overview.
Query parameters: from, to, accountCategory, platform, ownership.
Response:
{
"ok": true,
"metrics": {
"trackedAccounts": 154,
"activeAccounts": 153,
"totalPosts": 166,
"newPosts": 157,
"unreadAlerts": 16,
"totalEngagement": 98600
},
"activity": {
"posts": [],
"likes": [],
"comments": [],
"views": []
},
"recentAlerts": [],
"coverageBreakdown": []
}Purpose: Meta/Instagram connector readiness and capability state.
Response:
{
"ok": true,
"connection": {
"provider": "meta",
"platform": "instagram",
"graphApiVersion": "v24.0",
"configured": true,
"preferredMode": "instagram_business_login",
"permissionState": {
"requiredScopes": ["instagram_business_basic", "instagram_business_manage_insights"],
"optionalScopes": ["instagram_business_manage_comments", "instagram_business_manage_messages", "instagram_business_content_publish"],
"missingPermissions": []
},
"capabilities": {
"ownedAccountDiscovery": true,
"watchedAccountManualImport": true,
"watchedAccountApiEnrichment": true,
"readPosts": true,
"readInsights": true,
"replyToComments": true,
"replyToMessages": true,
"publishPosts": true
}
}
}Purpose: owned/watched social account registry.
Query parameters: q, platform, priority, category, status, ownership, kind, schoolOrOrg, linkedAccountId.
Response:
{
"ok": true,
"accounts": [
{
"id": "uuid",
"platform": "instagram",
"handle": "example.store",
"displayName": "Example Store",
"ownership": "owned",
"category": "partner",
"priority": "medium",
"status": "active",
"followerCount": 7900,
"postCount": 34,
"lastSyncedAt": "2026-04-27T00:00:00.000Z",
"linkedAccount": null
}
],
"facets": {},
"pagination": {}
}Purpose: manual social account import/create.
Request:
{
"platform": "instagram",
"handle": "example.store",
"ownership": "watched",
"category": "customer",
"priority": "high",
"accountId": "uuid",
"externalAccountId": "178414...",
"metaPageId": "123456789",
"metaBusinessId": "987654321",
"profileUrl": "https://www.instagram.com/example.store/",
"followerCount": 7900
}Response:
{
"ok": true,
"socialAccount": {}
}Purpose: scan connected Meta-owned Instagram accounts when API credentials permit it.
Request:
{
"provider": "instagram",
"installationId": "uuid"
}Response:
{
"ok": true,
"provider": "meta",
"connection": {},
"discovered": [{ "handle": "example.store", "ownership": "owned" }],
"created": 0,
"updated": 0,
"warnings": []
}Purpose: social account detail with connection state, metrics, posts, sync history, and organization links.
Response:
{
"ok": true,
"socialAccount": {},
"weeklyTrends": [],
"posts": [],
"syncHistory": [],
"linkedEntities": []
}Purpose: update tenant-owned social account category, priority, ownership, status, links, or owner.
Response:
{
"ok": true,
"socialAccount": {}
}Purpose: post/media monitoring list.
Query parameters: platform, socialAccountId, postType, status, seen, from, to, campaignId.
Response:
{
"ok": true,
"posts": [
{
"id": "uuid",
"postType": "story",
"status": "published",
"caption": "Post caption",
"metrics": {
"views": 470,
"reach": 399,
"replies": 0,
"navigation": 429,
"profileVisits": 0,
"newFollows": 0,
"likes": 52,
"comments": 1,
"shares": 0,
"engagementRate": 0.007
}
}
],
"pagination": {}
}Purpose: create a product-owned draft social post for an owned/watched tenant account. Live publishing is a separate feature-gated Meta action.
Request:
{
"socialAccountId": "uuid",
"postType": "post",
"caption": "Draft caption",
"mediaUrl": "https://cdn.example.com/post.png",
"scheduledFor": "2026-05-01T15:00",
"location": "Champaign, IL",
"collaborators": ["athlete_handle"],
"tags": ["drop", "illinois"]
}Response:
{
"ok": true,
"post": {
"status": "draft"
}
}Purpose: publish a draft/planned post to an owned Instagram professional account through Meta Graph API.
Required:
- tenant session
- Meta connector with encrypted access token
- owned
social_account social_account.external_account_idor metadata Instagram user ID- public
mediaUrl - required read scopes
instagram_business_content_publishorinstagram_content_publishsocial_publishingfeature flag
Response:
{
"ok": true,
"post": {
"status": "published",
"externalPostId": "180..."
}
}Purpose: post detail, metrics, comments, account stats, and links.
Response:
{
"ok": true,
"post": {},
"comments": [],
"linkedCampaign": null,
"linkedAccount": null,
"permissions": {
"canReply": true,
"reason": null
}
}Purpose: reply/comment from an owned account where API permissions allow it.
Request:
{
"commentId": "18006022967821076",
"message": "Thanks for the comment."
}Response:
{
"ok": true,
"comment": {},
"activity": {}
}If permissions are unavailable, return 403 with a clear reason and do not pretend the action succeeded.
Purpose: content calendar and campaign planning.
Query parameters: from, to, owner, status, campaignId, platform.
Response:
{
"ok": true,
"items": [
{
"id": "uuid",
"type": "planned_post",
"title": "Launch post",
"status": "planned",
"scheduledFor": "2026-05-01T14:00:00.000Z",
"owner": null,
"campaign": null,
"socialAccount": null,
"assets": []
}
]
}Calendar records are product-owned. Publishing happens through the explicit publish route when Meta permissions and feature flags allow it.
Purpose: campaign list with linked social/sales outcomes.
Response:
{
"ok": true,
"campaigns": []
}Purpose: create product-owned campaign.
Request:
{
"name": "Finals Week Drop",
"campaignType": "seasonal",
"status": "planned",
"startsOn": "2026-05-01",
"endsOn": "2026-05-10",
"goal": "Drive reorder conversations"
}Response:
{
"ok": true,
"campaign": {}
}Purpose: social alert inbox.
Query parameters: status, severity, owner, eventType, socialAccountId, from, to.
Response:
{
"ok": true,
"alerts": [
{
"id": "uuid",
"eventType": "engagement_spike",
"title": "Engagement spike detected",
"severity": "medium",
"status": "unread",
"metadata": {
"threshold": "3%",
"postsAboveThreshold": 29
}
}
],
"counts": {
"unread": 16
}
}Purpose: mark read, assign, resolve, or dismiss social alert.
Request:
{
"status": "read",
"ownerMemberId": "uuid"
}Response:
{
"ok": true,
"alert": {}
}Purpose: mark filtered or all social alerts read.
Request:
{
"filters": {
"eventType": "new_post"
}
}Response:
{
"ok": true,
"updated": 16
}Purpose: comments, messages, and manual social threads linked to customers/orgs.
Query parameters: status, owner, platform, socialAccountId, accountId, contactId, opportunityId.
Response:
{
"ok": true,
"threads": []
}Purpose: manual social thread/log creation when API access is unavailable or incomplete.
Request:
{
"platform": "instagram",
"threadType": "manual",
"participantHandle": "customer.handle",
"accountId": "uuid",
"summary": "Customer asked about rush shirts."
}Response:
{
"ok": true,
"thread": {},
"activity": {}
}Purpose: send a reply through a connected owned Instagram account.
Required:
- tenant session
- Meta connector with encrypted access token
- owned
social_account - Instagram user ID for the owned account
- Instagram-scoped recipient ID from the thread or request body
- required read scopes
instagram_business_manage_messagesorinstagram_manage_messagesmessagesfeature flag
Request:
{
"message": "Can you send sizes and a due date?",
"recipientId": "IGSID"
}Response:
{
"ok": true,
"thread": {
"status": "replied"
}
}Purpose: list custom tenant dashboards backed by dashboard_definition.
Response:
{
"ok": true,
"dashboards": []
}Purpose: create a custom dashboard from approved Screenprinting widget primitives.
Request:
{
"name": "Sales operator board",
"widgets": ["sales_pulse", "reorder_queue", "social_alerts"]
}Response:
{
"ok": true,
"dashboard": {}
}Purpose: non-destructive merge/link suggestion queue.
Query parameters: status, sourceType, targetType, confidenceMin.
Response:
{
"ok": true,
"suggestions": [
{
"id": "uuid",
"sourceType": "instagram_account",
"sourceRef": {},
"targetType": "account",
"targetId": "uuid",
"status": "suggested",
"confidence": 0.92,
"reason": "Handle and organization name match"
}
]
}Purpose: confirm, reject, ignore, or mark a suggestion needs review.
Request:
{
"status": "confirmed",
"note": "Verified by sales owner"
}Response:
{
"ok": true,
"suggestion": {},
"createdLinks": []
}No source record is destroyed.
- Every route that reads tenant data resolves the slug to one
organization_id. - Every mutation requires tenant-session access.
- Admin/config mutations are role-gated when role enforcement exists; until then they must still be tenant-session gated and auditable.
- Printavo routes remain read-only to Printavo during the first Screenprinting implementation.
- Email routes create drafts and activity records only; they do not send email.
- Social publishing and reply routes must remain feature-flagged and permission-gated by Meta connector state.
- Error responses are JSON and actionable.
npm run verifypasses after route changes.
The Screenprinting API contract is implemented under /api/runtime/organizations/[slug]/screenprinting/*.
Current behavior:
- read routes resolve the workspace slug and require
tenantType.id=screenprinting - mutating routes require tenant-session access
- config mutation writes tenant-owned organization settings and records audit events
- Printavo-derived order routes are read-only
- email draft routes render or record draft state only and do not send email
- social publish/comment/message routes call Meta only when connector token, scopes, owned-account IDs, and feature flags are present; otherwise they return permission/setup errors
- fixture fallback is available for Screenprinting tenant seeds without live product rows, including
second-screenprinter