Developer API

Build with Vendor IQ™

Embed vendor spend intelligence into your franchise platform, accounting software, or operations stack. REST API, webhook events, shareable reports, and team seat provisioning.

HMAC-signed tokens Cloudflare global edge REST + JSON Webhook events Shareable report URLs
Overview
VIQ exposes a RESTful API over HTTPS. All requests and responses use JSON. The API is divided into two layers: the Cloudflare Worker endpoints (authentication, usage metering, email, Stripe) and the Table API (CRUD operations on your data).
Early Access API. The VIQ API is currently in early access for Growth, Franchisor, and Enterprise plan customers. Partner integrations and white-label access are available — contact hello@vendorintel.ai.
Cloudflare Edge
Worker runs at the edge globally — sub-50ms typical response
🔐
JWT Auth
HMAC-SHA256 signed tokens, 30-day expiry, server-verified
💳
Stripe Webhooks
HMAC-verified — tier upgrades applied server-side only
📊
REST + JSON
Standard REST verbs: GET, POST, PUT, PATCH, DELETE
Authentication
VIQ uses JWT bearer tokens for API authentication. Tokens are issued by the /issue-token Worker endpoint on every login.
Pass your JWT in the Authorization header as a Bearer token: Authorization: Bearer <your_token>
Example Request Header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...
JWT Payload Structure
{
  "sub":        "user_abc123",       // user ID
  "email":      "user@company.com",
  "name":       "Jane Smith",
  "role":       "franchisee",
  "tier":       "growth",
  "company":    "Sunrise Café Group",
  "iat":        1722470400,          // issued at (Unix timestamp)
  "exp":        1725062400           // expires at (Unix timestamp, 30 days)
}
Base URL
All API requests use relative paths when called from the VIQ app, or the full Worker URL for external integrations.
Worker Base URL (external integrations)
https://viq-qb-proxy.danielle-f99.workers.dev
Table API Base URL
https://viq.vendorintel.ai/tables/{table_name}
The Worker enforces CORS — requests from external origins require pre-approval. Contact hello@vendorintel.ai to whitelist your domain.
Rate Limits
The API is rate-limited at the Cloudflare edge.
TierRequests / minuteNotes
Free30 rpmTable API only
Essentials / Growth120 rpmWorker + Table API
Franchisor / Pro300 rpmFull access
EnterpriseCustomContact us

Rate limit headers are returned on all responses: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

POST /issue-token
Issues a signed JWT for an authenticated user. Called automatically on every login. Returns a 30-day token.
Request
POST https://viq-qb-proxy.danielle-f99.workers.dev/issue-token
Content-Type: application/json

{
  "userId":  "user_abc123",
  "email":   "user@company.com",
  "name":    "Jane Smith",
  "role":    "franchisee",
  "tier":    "growth",
  "company": "Sunrise Café Group"
}
Response 200
{
  "token":      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": 1725062400
}
FieldTypeRequiredDescription
userIdstringRequiredUser's unique ID (UUID from viq_users)
emailstringRequiredUser's email address
rolestringRequiredbusiness | franchisee | franchisor | vendor | admin
tierstringRequiredfree | essentials | growth | franchisor_s | franchisor_m | …
companystringOptionalCompany / brand name for display
POST /validate-token
Validates a JWT and returns its decoded payload. Use this to verify tokens in server-side integrations.
Request
POST /validate-token
Content-Type: application/json

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
Response 200
{
  "valid":   true,
  "payload": { "sub": "user_abc123", "email": "...", "tier": "growth", "exp": 1725062400 }
}
Response 401 (invalid / expired)
{ "valid": false, "error": "Token expired" }
POST /check-limit
Server-side usage metering. Returns 200 if the user is within their tier's vendor cap, or 402 if they've exceeded it. Used to gate data writes at the Worker level.
Request
POST /check-limit
Authorization: Bearer <jwt>
Content-Type: application/json

{ "userId": "user_abc123", "currentCount": 4 }
Response 200 (within limit)
{ "allowed": true, "tier": "growth", "cap": 100, "used": 4 }
Response 402 (over limit)
{ "allowed": false, "tier": "free", "cap": 3, "used": 4, "upgrade_url": "https://viq.vendorintel.ai/upgrade.html" }
Table API — Overview
The Table API provides full CRUD access to VIQ's data tables. All responses are paginated JSON. System fields (id, created_at, updated_at) are managed automatically.
TableDescription
viq_usersUser accounts — 27 fields including tier, role, session_count, features_used
viq_vendorsVendor records — VIQ scores, benchmarks, share_public flag, share_view_count
viq_submissionsScoring submissions — raw inputs and scored outputs
viq_team_membersMulti-seat invites — owner/admin/analyst/read_only roles
viq_onboarding_progressActivation checklist state — 4 steps, drip flags
viq_paymentsPayment log — written by Stripe webhook (idempotency record)
viq_accountant_clientsAccountant ↔ client linkage for white-label access
viq_support_ticketsSupport tickets
GET /tables/{table}
List records with pagination and optional search/sort.
Request
GET /tables/viq_vendors?page=1&limit=20&search=Toast&sort=created_at

Authorization: Bearer <jwt>
Response 200
{
  "data":   [ { "id": "uuid", "vendor_name": "Toast POS", "viq_score": 72, ... } ],
  "total":  47,
  "page":   1,
  "limit":  20,
  "table":  "viq_vendors",
  "schema": { ... }
}
Query ParamTypeDescription
pagenumberOptionalPage number, default 1
limitnumberOptionalRecords per page, default 100, max 500
searchstringOptionalFull-text search across all text fields
sortstringOptionalField name to sort by (default: updated_at DESC)
POST /tables/{table}
Create a new record. Returns HTTP 201 with the created record including system fields.
JavaScript Example
const response = await fetch('tables/viq_vendors', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    user_id:        'user_abc123',
    vendor_name:    'Toast POS',
    vendor_category: 'Point of Sale',
    monthly_spend:  1450,
    viq_score:      72,
    viq_status:     'watch',
    share_public:   false
  })
});
const vendor = await response.json();
console.log(vendor.id); // → "550e8400-e29b-41d4-a716-446655440000"
PUT / PATCH /tables/{table}/{id}
Update a record. PUT replaces all fields; PATCH updates only the fields provided.
PATCH Example — update score only
PATCH /tables/viq_vendors/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{ "viq_score": 85, "viq_status": "safe" }
Webhooks — Stripe Events
VIQ's Worker receives and processes Stripe webhook events. Signatures are verified with HMAC-SHA256 before any action is taken.
Webhook endpoint: https://viq-qb-proxy.danielle-f99.workers.dev/stripe-webhook
checkout.session.completed
Fired when a Stripe Checkout session completes. Upgrades the user's tier, logs to viq_payments, sends confirmation email.
checkout.session.async_payment_succeeded
Fired for delayed payment methods. Same flow as checkout.session.completed.
customer.subscription.deleted
Fired when a subscription is cancelled. Downgrades user tier to 'free'. (Planned — not yet in Worker.)
Stripe Event Payload (checkout.session.completed)
{
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id":                    "cs_test_abc123",
      "customer_email":        "user@company.com",
      "client_reference_id":   "user_abc123_growth",
      "metadata":              { "plan": "growth" },
      "payment_status":        "paid"
    }
  }
}
Shareable Report URLs
VIQ supports public, shareable vendor score cards via a simple URL structure. Great for embedding in lender packages, board decks, or accountant reports.
URL Format
https://viq.vendorintel.ai/report.html?v={vendor_record_id}
Reports are private by default. Set share_public: true on a viq_vendors record to make its report URL publicly accessible. Each view increments share_view_count.
Enable sharing for a vendor record
PATCH /tables/viq_vendors/{vendor_id}
Content-Type: application/json

{ "share_public": true }
Tier Caps Reference
Vendor record limits enforced by /check-limit.
TierVendor CapTeam Seats
free31 (owner only)
essentials201
growth1005
franchisor_s10 locations10
franchisor_m50 locations25
franchisor_lUnlimited100
accountant_starter5 clients3
accountant_pro20 clients10
accountant_enterpriseUnlimited50
vendor_intel / vendor_proUnlimited3–5
adminUnlimitedUnlimited
System Fields
Automatically managed on every record — never set these manually.
FieldTypeDescription
idstring (UUID)Unique record identifier — UUID v4
gs_project_idstringProject identifier (internal)
gs_table_namestringTable name (internal)
created_atnumber (ms)Creation timestamp in milliseconds
updated_atnumber (ms)Last update timestamp in milliseconds
Error Codes
All errors return JSON with an error field.
HTTP StatusMeaningCommon Cause
200OKSuccess
201CreatedRecord created (POST)
204No ContentRecord deleted (DELETE)
400Bad RequestMissing required fields or invalid JSON
401UnauthorizedMissing, invalid, or expired JWT
402Payment RequiredTier vendor cap exceeded (/check-limit)
403ForbiddenInsufficient role or tier for this action
404Not FoundRecord ID does not exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorWorker exception — contact support
Error Response Format
{ "error": "Token expired", "code": 401 }

Ready to integrate?

Start with a free account — no credit card required. API access on Growth and above.

Start Free Contact for Enterprise API