View FAQ →

REST API · v1

API Reference

Every API call either reads or advances a quote through this lifecycle. Authentication is tenant-scoped; all monetary values are DECIMAL(14,4). All routes are served under the /v1 prefix (locally, http://localhost:3001/v1).

Base URL

https://api.tackquote.com/v1

Authentication

All requests require a Bearer token obtained from the login endpoint. Each token is scoped to a single tenant — the tenantSlug in your login request determines which data the token can access.

POST /v1/auth/login
POST /v1/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "your-password",
  "tenantSlug": "your-tenant"
}
Response 200
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 900
}

Include the access token on every subsequent request:

All authenticated requests
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Token lifetime. Access tokens expire after 15 minutes (expiresIn: 900). Refresh before expiry with POST /v1/auth/refresh, sending { "refreshToken": "..." } in the body. Refresh tokens last 7 days. New accounts are created via POST /v1/auth/register followed by POST /v1/auth/verify-email.

API Keys

For server-to-server integrations, issue a long-lived API key instead of logging in with a password. Manage keys under /v1/api-keys (or in the seller portal). The key limit depends on your plan — see Plans & Billing.

GET/v1/api-keys
POST/v1/api-keys
DELETE/v1/api-keys/:id
POST /v1/api-keys
POST /v1/api-keys
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "name": "Warehouse sync",
  "scopes": ["quotes:read", "buyers:read"]
}

// Response 201 — the plaintext key is returned ONCE
{
  "id": "9c1e...",
  "key": "tack_sk_a1b2c3d4e5f6...",
  "scopes": ["quotes:read", "buyers:read"]
}
Store it immediately. Keys are prefixed tack_sk_ and shown in full only at creation — TackQuote stores just a bcrypt hash. If you lose a key, revoke it and issue a new one.

Rate Limits

A platform baseline of 100 requests per 60 seconds applies to every tenant, layered with a per-plan per-minute cap 60 (Basic), 120 (Starter), 300 (Pro), and 1,000 (Enterprise) requests per minute. Plans also carry a monthly API-call allowance; see Plans & Billing. Requests over any limit return 429 Too Many Requests. Response headers tell you where you stand:

HeaderMeaning
X-RateLimit-LimitMax requests per minute
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUTC epoch timestamp when the window resets

Quotes API

The core resource. Quotes move through the lifecycle shown above — every state transition writes to an immutable event log. All monetary fields are DECIMAL(14,4).

GET/v1/quotes?page=1&limit=20&status=sent
POST/v1/quotes
GET/v1/quotes/:id
PATCH/v1/quotes/:id
POST/v1/quotes/:id/send

List quotes

Returns paginated results. Filter by status, buyerId, or search (matches quote number and title).

GET /v1/quotes
GET /v1/quotes?page=1&limit=20&status=sent
Authorization: Bearer eyJ...

// Response
{
  "data": [ /* Quote[] */ ],
  "total": 84,
  "page": 1,
  "limit": 20
}

Create quote

POST /v1/quotes
POST /v1/quotes
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "buyerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "title": "Annual SaaS Renewal — Acme Corp",
  "lineItems": [
    {
      "description": "Pro Plan (annual)",
      "sku": "PRO-ANN-001",
      "quantity": 10,
      "unitPrice": 1200.0000,
      "discount": 0.10
    }
  ],
  "expiresAt": "2026-07-20T23:59:59Z",
  "notes": "Volume discount applied."
}

Send quote

Transitions the quote from draft or pending_approval to sent and delivers an email to the buyer. Returns the updated quote object.

POST /v1/quotes/:id/send
POST /v1/quotes/3fa85f64-5717-4562-b3fc-2c963f66afa6/send
Authorization: Bearer eyJ...

Quote object — key fields

FieldTypeDescription
iduuidUnique identifier
tenantIduuidYour tenant
quoteNumberstringHuman-readable — e.g. TK-2026-000142
titlestringQuote title shown to buyer
statusenumdraft · sent · viewed · approved · rejected · expired · converted
buyerIduuidLinked buyer record
lineItemsLineItem[]{description, sku, quantity, unitPrice, discount}
subtotaldecimalSum before discount and tax
discountdecimalTotal discount amount
taxdecimalTax amount
totaldecimalFinal amount payable
expiresAttimestamptzQuote expiry
createdAttimestamptzCreation timestamp

Buyers API

Buyers are the contacts and companies you quote. Each buyer belongs to your tenant and can be linked to multiple quotes across their lifetime.

GET/v1/buyers
POST/v1/buyers
GET/v1/buyers/:id
PATCH/v1/buyers/:id

Create buyer

POST /v1/buyers
POST /v1/buyers
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "[email protected]",
  "company": "Acme Corp",
  "phone": "+1-555-0100"    // optional
}

Products / Catalog

The product catalog backs the line-item picker in the quote builder. Products sync automatically from connected BigCommerce or Shopify stores, and can also be managed directly via API.

GET/v1/catalog/products?search=term
POST/v1/catalog/products

Create product

POST /v1/catalog/products
POST /v1/catalog/products
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "name": "Pro Plan — Annual",
  "sku": "PRO-ANN-001",
  "description": "Unlimited users, SSO, priority support.",
  "unitPrice": 1200.0000,
  "category": "Subscriptions"
}

Webhooks

Subscribe to quote lifecycle events. TackQuote posts to your URL within seconds of each state change — use this to trigger CRM updates, notify Slack, or start downstream workflows.

Register a webhook

POST /v1/webhooks
POST /v1/webhooks
Authorization: Bearer eyJ...
Content-Type: application/json

{
  "url": "https://your-app.com/tack/webhook",
  "secret": "whsec_at_least_16_chars",
  "events": [
    "quote.sent",
    "quote.viewed",
    "quote.approved",
    "quote.rejected",
    "quote.converted"
  ]
}

Event payload

POST → your endpoint
{
  "event": "quote.converted",
  "timestamp": "2026-06-20T12:00:00Z",
  "data": {
    "quoteId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "quoteNumber": "TK-2026-000142",
    "buyerId": "b1c2d3e4-1234-5678-abcd-ef0123456789"
  }
}

Available events

quote.created
quote.sent
quote.viewed
quote.approved
quote.rejected
quote.converted
quote.expired
Verify every delivery. Each webhook includes an X-TackQuote-Signature header of the form sha256=<hex> — the HMAC-SHA256 of the raw body using the secret you set when registering the webhook. The event name is also sent in X-TackQuote-Event. Reject requests where the signature doesn't match.

Signature verification — Node.js

verify-tack-webhook.ts
import crypto from 'crypto'

// header = req.headers['x-tack-signature']  // "sha256=<hex>"
function verifyTackWebhook(
  rawBody: string,
  header: string,
  secret: string
): boolean {
  const signature = header.replace(/^sha256=/, '')
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex')
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex')
  )
}

Error Codes

All errors return JSON with a message field. Validation errors include an errors array with per-field detail.

CodeStatusMeaning
400Bad RequestThe request body is malformed or missing required fields.
401UnauthorizedNo Bearer token provided, or the token has expired.
403ForbiddenYour token doesn't have permission to access this resource.
404Not FoundThe resource does not exist.
422Validation ErrorWell-formed request that fails field-level validation. Check the errors array.
429Rate LimitYou exceeded the platform (100/60s) or your per-plan per-minute request limit.
500Server ErrorSomething went wrong on our end. If it repeats, contact support.
Example — 422 Validation Error
{
  "statusCode": 422,
  "message": "Validation failed",
  "errors": [
    { "field": "lineItems[0].unitPrice", "message": "Must be greater than 0" },
    { "field": "expiresAt",              "message": "Must be a future date" }
  ]
}

SDKs & Tools

Swagger UI

Interactive OpenAPI explorer at /api/docs in non-production environments (e.g. localhost:3001/api/docs).

Non-production only
Postman Collection

Pre-built collection for all endpoints, ready to import.

Coming soon
Node.js SDK

Type-safe client with full IntelliSense. Built on tRPC v11.

Coming soon