A deliberately narrow API.

Authenticated catalog, idempotent order intake, signed webhooks, and a full sandbox lifecycle. The integration surface is small on purpose — narrow is what stays stable — and the machine-readable contract is public.

Contract: /api/v1/openapi.json · OpenAPI 3.2 · the document is the authority; this page is the tour.

§D1

Choose the contract that matches the operator

VialAPI exposes two versioned contracts. They share validation, idempotency, sandbox behavior, signed events, and fulfillment state. The credential decides which commercial boundary the request operates inside.

sales-channel API

One site or ordering channel

Use /api/v1 when one website or channel owns its catalog aliases, customers, orders, inventory, and webhook endpoint. Credentials begin with vial_test_ or vial_live_.

Open sales-channel contract →

business-account API

One business across many channels

Use /api/account/v1 when one operator needs a unified catalog, customers, and orders across websites, manual sales, or downstream channels. Credentials begin with vial_test_acct_ or vial_live_acct_.

Open business-account contract →
§D2

Sales-channel quickstart

The exact script below runs in this repository’s CI on every release — it is read from that file, not copied onto this page. A sandbox key (vial_test_) is issued with your workspace when access is approved; order creation returns 202 Accepted and the order then progresses through the lifecycle via webhooks and polling.

docs/examples/vialapi-curl-quickstart.sh — as certified in CI
#!/usr/bin/env bash
set -euo pipefail

: "${VIALAPI_TEST_KEY:?Set VIALAPI_TEST_KEY to an expiring vial_test_ credential}"
VIALAPI_BASE_URL="${VIALAPI_BASE_URL:-https://vialapi.com}"

curl --fail-with-body --silent --show-error \
  --request GET "${VIALAPI_BASE_URL}/api/v1/catalog?limit=20" \
  --header "Authorization: Bearer ${VIALAPI_TEST_KEY}" \
  --header "Accept: application/json"

# Replace VIAL-EXAMPLE-SKU with vialApiSku from the authenticated catalog.
curl --fail-with-body --silent --show-error \
  --request POST "${VIALAPI_BASE_URL}/api/v1/orders/validate" \
  --header "Authorization: Bearer ${VIALAPI_TEST_KEY}" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{"lineItems":[{"sku":"VIAL-EXAMPLE-SKU","quantity":1}]}'

# Use the exact same Idempotency-Key and exact JSON bytes after an uncertain result.
curl --fail-with-body --silent --show-error \
  --request POST "${VIALAPI_BASE_URL}/api/v1/orders" \
  --header "Authorization: Bearer ${VIALAPI_TEST_KEY}" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: quickstart-order-001" \
  --data '{"externalOrderId":"QUICKSTART-001","customer":{"firstName":"API","lastName":"Certification","email":"api-certification@example.test"},"shipTo":{"recipientName":"API Certification","address1":"100 Example Avenue","city":"Atlanta","state":"GA","postalCode":"30309","country":"US","residential":true},"lineItems":[{"sku":"VIAL-EXAMPLE-SKU","quantity":1}],"metadata":{"source":"published-quickstart","vialapiSandboxScenario":"standard"}}'

Retry rule: reuse the exact same Idempotency-Key and the exact same JSON bytes after any uncertain result. A retried request can never create a second order.

§D3

Signed webhooks

Every delivery uses Standard Webhooks headers — webhook-id, webhook-timestamp, webhook-signature — with an HMAC-SHA256 over the exact raw body. Delivery is at least once and ordering is not guaranteed: verify the bytes, then deduplicate by webhook-id.

  • order.created
  • order.status_changed
  • order.approved
  • order.awaiting_payment
  • order.label_created
  • order.shipped
  • order.delivered
  • order.cancelled
  • order.needs_review
  • inventory.low_stock
  • ping
docs/examples/vialapi-webhook-verification.ts — as certified in CI
import { createHmac, timingSafeEqual } from "node:crypto";

type WebhookHeaders = {
  "webhook-id": string;
  "webhook-timestamp": string;
  "webhook-signature": string;
};

/** Verify the exact raw bytes before parsing JSON, then deduplicate webhook-id. */
export function verifyVialApiWebhook({
  headers,
  rawBody,
  secret,
  nowSeconds = Math.floor(Date.now() / 1_000),
  toleranceSeconds = 5 * 60
}: {
  headers: WebhookHeaders;
  rawBody: string;
  secret: string;
  nowSeconds?: number;
  toleranceSeconds?: number;
}) {
  if (!/^\d+$/u.test(headers["webhook-timestamp"])) return false;
  const sentAt = Number(headers["webhook-timestamp"]);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > toleranceSeconds) return false;
  if (!secret.startsWith("whsec_")) return false;

  const encodedSecret = secret.slice("whsec_".length);
  const key = Buffer.from(encodedSecret, "base64");
  if (key.length < 24 || key.length > 64 || key.toString("base64") !== encodedSecret) return false;
  const expected = createHmac("sha256", key)
    .update(`${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${rawBody}`, "utf8")
    .digest();

  return headers["webhook-signature"].split(/\s+/u).some((candidate) => {
    const match = /^v1,([A-Za-z0-9+/]+={0,2})$/u.exec(candidate);
    if (!match?.[1]) return false;
    const received = Buffer.from(match[1], "base64");
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}
§D4

The sandbox is the product

Keys prefixed vial_test_ run the full order lifecycle against a simulated supplier — shipped after about two minutes, delivered after about four — excluded from billing and reports, and purged after thirty days. Webhook payloads carry "livemode": false. There are no magic sandbox SKUs; you exercise the same catalog contract your production key will see.

scopes

  • orders:read
  • orders:validate
  • orders:write
  • catalog:read
  • customers:read
  • webhooks:manage

An empty scope list grants nothing. Every credential carries explicit least-privilege scopes and may be bound to a single connection.

§D5

Connect ChatGPT, Claude, or Codex to VialAPI

Add a custom connector or remote MCP server in your AI app, then paste this one URL. VialAPI opens its own secure sign-in, selects the owner’s most recently used business, and asks them to review the exact access. No Anthropic or OpenAI API key is needed.

remote MCP URL

https://vialapi.com/api/mcp
  1. 01Add the connectionChoose custom connector or remote MCP in the AI app and paste the VialAPI URL.
  2. 02Approve it as the ownerSign in to VialAPI. We select your most recently used business automatically; review the access and connect.
  3. 03Approve each exact orderAfter the AI prepares a quote, open its private VialAPI review link. Only the connected owner can accept the current order terms and unlock that exact order.

Reading documentation can teach an AI how the ordering flow works, but it cannot silently authorize tools. OAuth owner consent is what securely connects the AI to one business. A future connector-directory listing can make the VialAPI connection easier to discover without changing that security boundary.

Versioning: /api/v1 is the compatibility boundary. Breaking changes require a new path version; deprecated operations carry Deprecation and Sunset headers for at least 180 days.

§D6

Access

Access is reviewed. Tell us what you run and we will provision the right workspace, catalog, and credentials — sandbox keys arrive with the workspace, and production keys follow certification against the sandbox lifecycle above.