API Reference
v1

ecom9 API Documentation

Access transactions, product catalogs, subscription state, and tier data for your connected accounts. Every request is authenticated with your Payment Gateway keys. See Subscription Event Handling: Design Patterns and Best Practices for lifecycle event guidance.

Getting Your API Key

  1. Sign in to the ecom9 dashboard.
  2. Open Payment Gateways.
  3. Select your active sandbox or live gateway.
  4. Copy the API Key and signing key ID from the gateway details and store them securely.

Integration Overview

account_ref_id is the primary identity key between ecom9 and your software. Use it to map customers, orgs, or workspaces and keep the same value across all API calls.

  • Required on most endpoints: pass account_ref_id on requests that read or write billing state.
  • Stable and internal: choose an immutable ID from your system (user/org/workspace) to avoid remapping later.
  • Consistent across systems: we echo account_ref_id back in responses and webhook payloads.

Sandbox and production API keys are available in the dashboard under Payment Gateways, letting you pick the right environment per deployment.

Prefer using an SDK? Jump to SDK Examples for language-specific snippets that call each endpoint.

Authentication

All endpoints require your Payment Gateway API key. Include it with each request using one of the supported headers.

Option 1: Authorization header (recommended)

Authorization: Bearer YOUR_API_KEY

Option 2: X-API-Key header

X-API-Key: YOUR_API_KEY

Base URL: https://app.ecom9.com/api/

Test with curl

curl -s https://app.ecom9.com/api/tiers/ \
  -H "Authorization: Bearer YOUR_API_KEY"

Endpoints

Base URL: https://app.ecom9.com/api/

Get Transactions by Account Reference ID

Retrieve the full transaction history for a specific account reference.

Request URL
GET https://app.ecom9.com/api/transactions/ACC123456789/?page=1&page_size=20

Parameters

  • account_ref_id — path parameter (required)
  • page — query parameter (optional, default 1)
  • page_size — query parameter (optional, default 20, max 100)
  • status — query parameter (optional: pending, completed, failed, cancelled)

Example response

{
  "count": 5,
  "next": null,
  "previous": null,
  "results": [
    {
      "transaction_uuid": "123e4567-e89b-12d3-a456-426614174000",
      "account_ref_id": "ACC123456789",
      "status": "completed",
      "amount": "29.99",
      "currency": "usd",
      "customer_email": "customer@example.com",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:35:00Z"
    }
  ]
}

Check Account Status

Return the live subscription state for an account reference, optionally filtered to a specific subscription + tier combination.

Request URL
GET https://app.ecom9.com/api/account/F8xQ2PnL7vKd93sH/status/?subscription=studio-suite&tier=pro

Parameters

  • account_ref_id — path parameter (required)
  • subscription — query parameter (optional subscription label, required whenever tier is provided)
  • tier — query parameter (optional tier label, only evaluated when paired with subscription)

Example response

{
  "account_ref_id": "TWY4k2ZQPp19s8Hd",
  "is_active": true,
  "customer_email": "orchid.ops@examplemail.com",
  "created_at": "2024-03-02T14:11:27Z",
  "updated_at": "2024-03-02T14:11:27Z",
  "subscription_tier": {
    "label_name": "pro",
    "name": "Aurora Growth",
    "description": "45k send limit and co-marketing boosts",
    "price": "32.00",
    "billing_cycle": "monthly",
    "features": "Deliverability audits, Campaign playbooks, Concierge migrations",
    "is_popular": true,
    "product_manage_id": "P6ZmR29aTyQb"
  }
}

Example response (multiple subscriptions)

{
  "account_ref_id": "TWY4k2ZQPp19s8Hd",
  "customer_email": "orchid.ops@examplemail.com",
  "subscriptions": [
    {
      "is_active": false,
      "created_at": "2023-12-18T09:54:12Z",
      "updated_at": "2024-01-01T00:00:00Z",
      "subscription_tier": {
        "label_name": "starter",
        "name": "Pulse Lite",
        "description": "5k send trial bundle",
        "price": "0.00",
        "billing_cycle": "monthly",
        "features": "API sandbox, Limited automation",
        "is_popular": false,
        "product_manage_id": "V8bLw4cNq0Xs"
      }
    },
    {
      "is_active": true,
      "created_at": "2024-03-02T14:11:27Z",
      "updated_at": "2024-03-02T14:11:27Z",
      "subscription_tier": {
        "label_name": "pro",
        "name": "Aurora Growth",
        "description": "45k send limit and co-marketing boosts",
        "price": "32.00",
        "billing_cycle": "monthly",
        "features": "Deliverability audits, Campaign playbooks, Concierge migrations",
        "is_popular": true,
        "product_manage_id": "P6ZmR29aTyQb"
      }
    }
  ],
  "total_subscriptions": 2
}

List Products

Return the products for the authenticated user’s profile and payment gateway, ordered by title.

Request URL
GET https://app.ecom9.com/api/products/

Response fields

  • manage_id — product’s unique manager ID
  • label_name — label name (for SaaS/subscription use)
  • type — product type (single or subscription)

Example response

{
  "results": [
    { "manage_id": "abc12xyz", "label_name": "my-product", "type": "single" },
    { "manage_id": "def34uvw", "label_name": "pro-plan", "type": "subscription" }
  ],
  "count": 2
}

Create Invoice

Create a Stripe-hosted invoice for a product and return the hosted payment URL. Optionally email the invoice to the customer.

Request URL
POST https://app.ecom9.com/api/invoices/create/

Sample payload (return invoice URL)

{
  "product_manage_id": "abc12xyz",
  "amount": 49.99,
  "customer_email": "customer@example.com"
}

Sample payload (email the customer)

{
  "product_manage_id": "abc12xyz",
  "amount": 49.99,
  "customer_email": "customer@example.com",
  "currency": "usd",
  "description": "One-time setup fee",
  "send_to_customer": true,
  "return_url": "https://app.example.com/thank-you"
}

Example response

{
  "hosted_invoice_url": "https://invoice.stripe.com/i/acct_xxx/...",
  "stripe_invoice_id": "in_1ABCdef...",
  "email_sent": true,
  "return_url": "https://app.example.com/thank-you"
}

Get Available Subscription Tiers

List every tier configured for your account so you can display pricing or validate selection on the client side.

Request URL
GET https://app.ecom9.com/api/tiers/?page=1&page_size=20

Example response

{
  "count": 3,
  "results": [
    {
      "label_name": "basic",
      "name": "Basic Plan",
      "description": "Essential features for getting started",
      "price": "9.99",
      "billing_cycle": "monthly",
      "features": "Basic features, Email support",
      "is_popular": false,
      "product_manage_id": "ABC123DEF456"
    }
  ]
}

Resolve Public URL

Fetch the customer-facing link for a SaaS product or tier before appending your signed ?u= token. The resolver only returns URLs for catalog entries tagged saas_only.

Mandatory requirements
  • account_ref_id is required on every request and must match the account that owns the subscription.
  • When resolving by subscription_name + tier_name, include email so we can match the subscriber.
  • Non-SaaS catalog items respond with 404 Not Found. Confirm the access mode before resolving.
Request URL
GET https://app.ecom9.com/api/resolve/public-url/?product_name=onboarding&account_ref_id=demo-account-123

Parameters

  • account_ref_id — query parameter (required)
  • product_name — query parameter (optional, mutually exclusive with subscription_name + tier_name)
  • subscription_name — query parameter (optional, requires tier_name)
  • tier_name — query parameter (optional, requires subscription_name)
  • email — query parameter (required when using subscription_name + tier_name)

Tip: Use product_name for one-time product sales. Use subscription_name + tier_name for subscriptions with saas_mode enabled.

Subscription + tier parameters

Use when you only know the catalog names. Required query params: subscription_name, tier_name, account_ref_id, email.

curl -X GET "https://app.ecom9.com/api/resolve/public-url/?subscription_name=pro-plan&tier_name=pro&account_ref_id=demo-account-123&email=buyer%40example.com" \
  -H "Authorization: Bearer YOUR_API_KEY_HERE"
Single product parameters

Required query params: product_name, account_ref_id.

curl -X GET "https://app.ecom9.com/api/resolve/public-url/?product_name=onboarding&account_ref_id=demo-account-123" \
  -H "Authorization: Bearer YOUR_API_KEY_HERE"

Example response

{
  "public_url": "https://app.ecom9.com/p/offer123?u=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImlzcyI6ImRlbW8tYWNjb3VudC0xMjMifQ.eyJhY2NvdW50X3JlZl9pZCI6ImRlbW8tYWNjb3VudC0xMjMiLCJ0aWVyIjoicHJvIiwiaWF0IjoxNzAwMDAwMDB9._fake-signature-abc123"
}

Fix Subscription

Return a hosted payment URL for the invoice that failed and put a subscription into a past-due or incomplete state.

Request URL
GET https://app.ecom9.com/api/fix-subscription/?account_ref_id=IAS0I48ZMYEF&subscription_name=pro-plan&tier_name=pro

Parameters

  • account_ref_id — query parameter (required)
  • subscription_name — query parameter (required, requires tier_name)
  • tier_name — query parameter (required, requires subscription_name)

Example response

{
  "url": "https://app.ecom9.com/pay/in_1PLsXjKz8C7Demo"
}

If there is no past-due invoice or the subscription is already active, the response body is empty.

Check Entitlements

Confirm whether an account has access to a product entitlement. Auth uses the Payment Gateway API key and scopes to the gateway owner.

Request URL
GET https://app.ecom9.com/api/entitlements/check/?account_ref_id=org_123&product_code=pro-api&entitlement_code=api_access

Query parameters

  • account_ref_id — downstream identity (required)
  • product_code — stable product code (required)
  • entitlement_code — entitlement code (required)

Example response

{
  "account_ref_id": "org_123",
  "product_code": "pro-api",
  "entitlement_code": "api_access",
  "has_access": true
}

Get Prepaid Credit Balance

Return remaining prepaid credits for an account, product, and meter. If no balance row exists, the balance is 0.

Request URL
GET https://app.ecom9.com/api/usage/credits/balance/?account_ref_id=org_123&product_code=ai-images&meter_code=credits

Query parameters

  • account_ref_id — downstream identity (required)
  • product_code — product code (required)
  • meter_code — meter code (required, e.g. credits, api_calls)

Example response

{
  "account_ref_id": "org_123",
  "product_code": "ai-images",
  "meter_code": "credits",
  "balance": 42
}

Debit Prepaid Credits (Authorization)

Use for products with usage_model="prepaid_credits". Call this before performing an action to authorize and debit credits.

Request URL
POST https://app.ecom9.com/api/usage/credits/debit/

Request body

{
  "account_ref_id": "org_123",
  "product_code": "ai-images",
  "meter_code": "credits",
  "quantity": 5,
  "event_key": "imggen_987",
  "metadata": {
    "feature": "image_generation",
    "request_id": "req_123"
  }
}

Fields

  • account_ref_id — required
  • product_code — required; must use prepaid_credits
  • meter_code — required; meter must belong to the product
  • quantity — required; positive integer to debit
  • event_key — required; unique idempotency key per debit attempt
  • metadata — optional JSON blob for your own tracking

Success response

{
  "approved": true,
  "debited": 5,
  "remaining_balance": 42
}

Insufficient credits

{
  "approved": false,
  "error_code": "insufficient_credits",
  "remaining_balance": 2
}

Configuration errors

{
  "approved": false,
  "error_code": "invalid_usage_model",
  "remaining_balance": 0
}

Idempotency: event_key maps to an internal ledger ref_key. Repeating the call with the same key returns the same effective result and does not double-debit.

Record Usage Events (Postpaid / Metered)

Use for postpaid or metered usage models: metered_postpaid, included_overage, tiered_usage, volume_usage. Call after an action to record immutable usage.

Request URL
POST https://app.ecom9.com/api/usage/events/

Request body

{
  "account_ref_id": "org_123",
  "product_code": "api-access",
  "meter_code": "api_calls",
  "quantity": 1,
  "event_key": "req_abc_123",
  "metadata": {
    "path": "/v1/generate",
    "method": "POST",
    "request_id": "req_abc_123"
  }
}

Fields

  • account_ref_id — required
  • product_code — required; must use a postpaid usage model
  • meter_code — required; meter must belong to the product
  • quantity — required; positive integer units to record
  • event_key — required; unique idempotency key per usage event
  • metadata — optional JSON blob

Success response

{
  "accepted": true,
  "recorded_quantity": 1,
  "idempotent": false
}

Idempotent retry

{
  "accepted": true,
  "recorded_quantity": 1,
  "idempotent": true
}

Configuration errors

{
  "accepted": false,
  "error_code": "invalid_usage_model"
}

Get Usage Summary

Summarize usage for an account/product/meter over an optional time window. Use for reporting or billing runs.

Request URL
GET https://app.ecom9.com/api/usage/summary/?account_ref_id=org_123&product_code=api-access&meter_code=api_calls&start=2026-03-01T00:00:00Z&end=2026-03-31T23:59:59Z

Query parameters

  • account_ref_id — required
  • product_code — required
  • meter_code — required
  • start — optional ISO-8601 timestamp (inclusive)
  • end — optional ISO-8601 timestamp (inclusive)

Example response

{
  "account_ref_id": "org_123",
  "product_code": "api-access",
  "meter_code": "api_calls",
  "total_usage": 1250
}

If no events exist, total_usage is 0.

Webhooks

We deliver webhook events so your product can react instantly without duplicating Stripe plumbing.

  • Payload consistency: Every webhook includes the original account_ref_id and a unique transaction_id so you can correlate events or detect retries.
  • Subscription coverage: We process Stripe subscription lifecycle events (payment succeeded, failed, cancellation, cancel at period end, incomplete) for you.
  • Simple consumption: Call the status endpoint after receiving a webhook if you need confirmation. The response always resolves to a yes/no signal for access control.

Sample webhook payload

{
  "event": "subscription-created",
  "transaction_id": "123e4567-e89b-12d3-a456-426614174000",
  "account_ref_id": "ACC123456789",
  "subscription_id": "sub_xxxx",
  "is_active": true,
  "label_name": "pro", // tier's label name
  "timestamp": "2024-01-15T10:35:00Z"
}
# Your webhook endpoint should respond with 200 OK after processing
HTTP/1.1 200 OK
Content-Type: application/json

{"received": true}

Error Responses

401 Unauthorized
{
  "detail": "Invalid API key"
}
404 Not Found
{
  "error": "Account not found or invalid subscription tier"
}
400 Bad Request
{
  "field_name": ["Error message describing the issue"]
}

Rate Limiting

If you exceed your plan’s rate limit, responses include 429 Too Many Requests. Inspect the Retry-After header before retrying.

Data Privacy

  • Only data tied to your Payment Gateway account is returned.
  • Customer email addresses appear for identification only.
  • Sensitive payment identifiers (Stripe IDs) are never exposed.
  • All timestamps use UTC (ISO 8601) format.

SDK Examples

The official SDKs wrap the REST endpoints so you can keep signing, pagination, and retries consistent across services. Use the tabs to switch languages.

Get Transactions

from ecom9_sdk import Ecom9Client

client = Ecom9Client(api_key="YOUR_API_KEY")

transactions = client.transactions.list(
  account_ref_id="ACC123456789",
  page=1,
  status="completed"
)

for entry in transactions.results:
  print(entry["transaction_uuid"], entry["status"])
<?php

use Ecom9\Sdk\Client;

$client = new Client([
  'api_key' => getenv('ECOM9_KEY'),
]);

$transactions = $client->transactions()->list([
  'account_ref_id' => 'ACC123456789',
  'page' => 1,
]);

foreach ($transactions['results'] as $tx) {
  echo $tx['transaction_uuid'] . ' ' . $tx['status'] . PHP_EOL;
}
import { Ecom9Client } from "@ecom9/sdk";

const client = new Ecom9Client({ apiKey: process.env.ECOM9_KEY });

const transactions = await client.transactions.list({
  accountRefId: "ACC123456789",
  page: 1,
});

transactions.results.forEach(tx => {
  console.log(`${tx.transaction_uuid} ${tx.status}`);
});
import { useTransactions } from "@ecom9/react";

export function TransactionsList({ accountRefId }) {
  const { data, isLoading, error } = useTransactions({
    accountRefId,
    page: 1,
  });

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.results.map(tx => (
        <li key={tx.transaction_uuid}>
          {tx.status} — {tx.amount}
        </li>
      ))}
    </ul>
  );
}

Check Account Status

status = client.accounts.status(
  account_ref_id="ACC123456789",
  tier="pro"
)

print(status["is_active"], status["subscription_tier"]["label_name"])
$status = $client->accounts()->status([
  'account_ref_id' => 'ACC123456789',
  'tier' => 'pro',
]);

echo $status['is_active'] ? 'active' : 'inactive';
const status = await client.accounts.status({
  accountRefId: "ACC123456789",
  tier: "pro",
});

console.log(status.is_active);
import { useAccountStatus } from "@ecom9/react";

export function AccountStatus({ accountRefId }) {
  const { data } = useAccountStatus({ accountRefId, tier: "pro" });

  if (!data) return null;

  return (
    <span>
      {data.is_active ? "Active" : "Inactive"} — {data.subscription_tier.label_name}
    </span>
  );
}

Get Subscription Tiers

tiers = client.tiers.list()

print([tier["label_name"] for tier in tiers["results"]])
$tiers = $client->tiers()->list();

return array_column($tiers['results'], 'label_name');
const tiers = await client.tiers.list();

console.log(tiers.results.map(tier => tier.label_name));
import { useTiers } from "@ecom9/react";

export function TierSelect() {
  const { data, isLoading } = useTiers();

  if (isLoading) return <option>Loading…</option>;

  return (
    <select>
      {data.results.map(tier => (
        <option key={tier.label_name} value={tier.label_name}>
          {tier.name}
        </option>
      ))}
    </select>
  );
}

Resolve Public URL

Use the SDK helper to wrap /api/resolve/public-url/. Refer to the endpoint documentation above for required parameters and SaaS-only constraints.

public_url = client.links.resolve_public_url(
  product_name="onboarding",
  account_ref_id="demo-account-123",
)

print(public_url["public_url"])
$url = $client->links()->resolvePublicUrl([
  'subscription_name' => 'pro-plan',
  'tier_name' => 'pro',
  'account_ref_id' => 'demo-account-123',
  'email' => 'buyer@example.com',
]);

echo $url['public_url'];
const url = await client.links.resolvePublicUrl({
  productName: "onboarding",
  accountRefId: "demo-account-123",
});

console.log(url.public_url);
import { useResolvePublicUrl } from "@ecom9/react";

export function CheckoutLink({ productName, accountRefId }) {
  const { data } = useResolvePublicUrl({ productName, accountRefId });

  if (!data) return null;

  return (
    <a href={data.public_url} target="_blank" rel="noreferrer">
      View checkout
    </a>
  );
}

Support

Need a new endpoint or payload tweak? Reach the team through your dashboard chat or email support, and we’ll scope it with you.