ecom9 API Documentation
Access transactions, subscription state, and tier data for your connected accounts. Every request is authenticated with your Payment Gateway keys.
Getting Your API Key
- Sign in to the ecom9 dashboard.
- Open Payment Gateways.
- Select your active sandbox or live gateway.
- Copy the API Key and signing key ID from the gateway details and store them securely.
Integration Overview
Every handoff must include a signed token. The SDK keeps your account_ref_id intact so you can map records without secondary lookups.
- Signed handoff: Generate a signed token that includes your
account_ref_id and timestamp, then append it as the ?u= parameter on any hosted paywall link.
Sandbox and production API keys plus their signing key IDs are available in the dashboard under Payment Gateways, letting you pick the right environment per deployment.
The SDK can generate a complete redirect URL for you. If you need the canonical public URL for a product or tier, call the Products API to fetch the public_url property and append your signed token.
Signed URL handoff
https://paywall.ecom9.com/onboarding?u=eyJhbGciOiJIUzI1NiIsIn...
Prefer using an SDK? Jump to SDK Examples for language-specific snippets that generate signed URLs and call each endpoint.
Token Generation (No SDK)
Generate the ?u= parameter yourself when you cannot use the SDK. Create a signed JWT that carries the account_ref_id, encode it, and append it to the product or tier URL.
Note: The generated JWT string must be signed with your Payment Gateway signing key; unsigned tokens are rejected.
- Required claim:
account_ref_id (string)
- Algorithm: HS256
- Secret: The product or tier’s payment gateway signing key
- Usage:
https://app.ecom9.com/p/<product_url_id>?u=<urlencoded_jwt> or /t/<tier_url_id>?u=<urlencoded_jwt>
- Need the public URL? Call
/api/resolve/public-url/ with product_manage_id or tier_manage_id to grab the canonical link first.
On our side we URL-decode the token, run jwt.decode(token, sign_key, algorithms=['HS256']), and read the account_ref_id. No expiration claim is required today.
Python example
import jwt
import urllib.parse
import requests
API_BASE = "https://app.ecom9.com/api"
API_KEY = "YOUR_API_KEY"
product_manage_id = "RYVPMlrLQOBr"
account_ref_id = "ACC123456789"
sign_key = "pk_live_xxx"
response = requests.get(
f"{API_BASE}/resolve/public-url/",
params={"product_manage_id": product_manage_id},
headers={"Authorization": f"Bearer {API_KEY}"}
)
response.raise_for_status()
public_url = response.json()["public_url"]
token = jwt.encode(
{"account_ref_id": account_ref_id},
sign_key,
algorithm="HS256"
)
encoded = urllib.parse.quote(token)
redirect_url = f"{public_url}?u={encoded}"
print(redirect_url)
curl with existing token
curl -I "https://app.ecom9.com/p/offer123?u=eyJhbGciOiJIUzI1NiIsIn..."
If you already have the JWT, just URL-encode it and append to your product or tier URL. The SDK’s response body also includes redirect_url if you prefer to have us format it.
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
}
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.
- 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_manage_id=RYVPMlrLQOBr&account_ref_id=demo-account-123
Parameters
account_ref_id — query parameter (required)
product_manage_id — query parameter (optional, mutually exclusive with tier_manage_id)
tier_manage_id — query parameter (optional, mutually exclusive with product_manage_id)
subscription_name — query parameter (optional, requires tier_name)
tier_name — query parameter (optional, requires subscription_name)
Tip: Provide either a manage ID or a subscription/tier name pair. When you only know the subscription alias, pass both subscription_name and tier_name with the same account_ref_id.
Subscription + tier parameters
Use when you only know the catalog names. Required query params: subscription_name, tier_name, account_ref_id.
curl -X GET "https://app.ecom9.com/api/resolve/public-url/?subscription_name=pro-plan&tier_name=pro&account_ref_id=demo-account-123" \
-H "Authorization: Bearer YOUR_API_KEY_HERE"
Tier manage ID parameters
Required query params: tier_manage_id, account_ref_id.
curl -X GET "https://app.ecom9.com/api/resolve/public-url/?tier_manage_id=AbC123xYz789&account_ref_id=demo-account-123" \
-H "Authorization: Bearer YOUR_API_KEY_HERE"
Product manage ID parameters
Required query params: product_manage_id, account_ref_id.
curl -X GET "https://app.ecom9.com/api/resolve/public-url/?product_manage_id=AbC123xYz789&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"
}
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_uuid 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
{
"type": "subscription.updated",
"transaction_uuid": "123e4567-e89b-12d3-a456-426614174000",
"account_ref_id": "ACC123456789",
"is_active": true,
"tier": "pro",
"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_manage_id="RYVPMlrLQOBr",
account_ref_id="demo-account-123",
)
print(public_url["public_url"])
$url = $client->links()->resolvePublicUrl([
'tier_manage_id' => 'AbC123xYz789',
'account_ref_id' => 'demo-account-123',
]);
echo $url['public_url'];
const url = await client.links.resolvePublicUrl({
productManageId: "RYVPMlrLQOBr",
accountRefId: "demo-account-123",
});
console.log(url.public_url);
import { useResolvePublicUrl } from "@ecom9/react";
export function CheckoutLink({ productManageId, accountRefId }) {
const { data } = useResolvePublicUrl({ productManageId, 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.