Subscriptions
List subscriptions
GET /v0/subscriptions/:merchant
Returns all subscription allocations across all modules owned by the merchant.
Auth: API key or SIWE session
Path parameters:
| Parameter | Type | Description |
|---|---|---|
merchant | string | Merchant wallet address (0x…) |
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
chain_id | number | No | Chain ID to filter. Defaults to the indexer's configured chain. |
Response fields:
| Field | Type | Description |
|---|---|---|
allocation_id | string | Internal allocation identifier |
on_chain_id | string | On-chain subscription ID |
module_address | string | SubscriptionModule contract address |
user_address | string | Subscriber wallet address |
plan_id | string | Internal plan identifier |
status | string | Computed status (see Status values) |
total_spent | string | Cumulative amount charged, in token base units |
times_executed | number | Number of successful payment executions |
next_charge_date | string | null | ISO 8601 timestamp of the next scheduled charge, or null if no charge is queued |
expires_at | string | null | Permit2 allowance expiry timestamp |
remaining_executions | number | null | Remaining execution budget, or null for unlimited |
created_at | string | ISO 8601 creation timestamp |
is_blocked | boolean | Whether the subscriber is blocked by the merchant |
Example request:
const res = await fetch(
'https://api.amser.io/v0/subscriptions/0xMerchantAddress?chain_id=84532',
{
headers: {
'Authorization': `Bearer ${process.env.AMSER_API_KEY}`,
},
}
);
const subscriptions = await res.json();
Example response:
[
{
"allocation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"on_chain_id": "1",
"module_address": "0xabcdef1234567890abcdef1234567890abcdef12",
"user_address": "0x1111111111111111111111111111111111111111",
"plan_id": "plan-uuid-here",
"status": "ACTIVE",
"total_spent": "10000000",
"times_executed": 3,
"next_charge_date": "2026-04-01T00:00:00.000Z",
"expires_at": "1743465600",
"remaining_executions": null,
"created_at": "2026-01-01T00:00:00.000Z",
"is_blocked": false
}
]
Status values
The status field is computed at query time by the indexer, not stored directly. It mirrors the on-chain _isActive() logic.
| Status | Meaning |
|---|---|
ACTIVE | Subscription is active and at least one payment has been executed |
PENDING | Subscription exists on-chain but the first payment has not yet executed (times_executed === 0). The keeper will execute the first charge within one poll interval. |
PAUSED | Subscriber or merchant paused the subscription. No charges will execute until resumed. |
CANCELLED | Subscriber cancelled. The allocation remains but is inactive. |
EXPIRED | The access window has lapsed — now > lastPaidAt + interval + gracePeriod. The user still has an allocation, but it is not considered active. |
BLOCKED | The merchant has blocked this subscriber. No charges will execute. |
Get subscription by ID
GET /v0/subscriptions/:merchant/:id
Returns a single subscription by its on-chain ID.
Auth: API key or SIWE session
Path parameters:
| Parameter | Type | Description |
|---|---|---|
merchant | string | Merchant wallet address (0x…) |
id | string | On-chain subscription ID |
The response shape is identical to a single element from the list endpoint.
Check authorization
GET /v0/auth/check
The primary endpoint for checking whether a wallet is currently authorized for one or more plans on a given module. This is the recommended way to gate access in your application without requiring a wallet library or RPC infrastructure on your server.
Auth: None required (public endpoint)
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
module_address | string | Yes | SubscriptionModule contract address (0x…) |
wallet | string | Yes | Wallet address to check (0x…) |
plan_ids | string | Yes | Comma-separated plan IDs (uint32 values) |
mode | string | No | indexed, onchain, or both. Default: indexed |
chain_id | number | No | Chain ID. Defaults to the indexer's configured chain. |
Modes
indexed — Queries the indexer database. Fast (~5ms), free, and sufficient for most access control decisions. The result reflects the most recent events the indexer has processed, which may lag a few seconds behind the chain.
onchain — Calls isActive() (single plan) or isActiveAny() (multiple plans) directly on the contract. Authoritative and trustless, but costs an RPC call and is slightly slower. Use this for high-stakes checks or when you need certainty immediately after a state change.
both — Runs indexed and on-chain checks in parallel and returns both results. The top-level authorized field uses the on-chain result. Useful during development to compare the two approaches or to get fast indexed data with authoritative confirmation.
authorized: true via indexed mode means the indexer believes the subscription is active based on the most recent events it has processed. In the seconds immediately after a new subscription or payment, the indexed result may briefly lag behind the on-chain state.
Response shape
| Field | Type | Description |
|---|---|---|
authorized | boolean | Whether the wallet is authorized for any of the requested plans |
mode | string | The mode used for this check |
wallet | string | The wallet address checked |
module_address | string | The module address checked |
chain_id | number | The chain ID |
plan_ids | string[] | The plan IDs checked |
indexed | object | undefined | Present when mode is indexed or both |
indexed.authorized | boolean | Indexed authorization result |
indexed.matching_plan_ids | string[] | Plan IDs with active paid subscriptions |
indexed.details | array | Per-allocation details (see below) |
onchain | object | undefined | Present when mode is onchain or both |
onchain.authorized | boolean | On-chain authorization result |
onchain.method | string | "isActive" (single plan) or "isActiveAny" (multiple plans) |
Each entry in indexed.details:
| Field | Type | Description |
|---|---|---|
allocation_id | string | Allocation identifier |
plan_id_on_chain | string | On-chain plan ID |
status | string | Current status |
next_charge_date | string | null | Next scheduled charge |
times_executed | number | Number of successful payments |
Example: indexed mode
const res = await fetch(
'https://api.amser.io/v0/auth/check?' +
'module_address=0xModuleAddress&' +
'wallet=0xSubscriberWallet&' +
'plan_ids=1&' +
'mode=indexed'
);
const data = await res.json();
{
"authorized": true,
"mode": "indexed",
"wallet": "0xsubscriberaddress",
"module_address": "0xmoduleaddress",
"chain_id": 84532,
"plan_ids": ["1"],
"indexed": {
"authorized": true,
"matching_plan_ids": ["1"],
"details": [
{
"allocation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"plan_id_on_chain": "1",
"status": "ACTIVE",
"next_charge_date": "2026-04-01T00:00:00.000Z",
"times_executed": 3
}
]
}
}
Example: onchain mode
const res = await fetch(
'https://api.amser.io/v0/auth/check?' +
'module_address=0xModuleAddress&' +
'wallet=0xSubscriberWallet&' +
'plan_ids=1&' +
'mode=onchain'
);
const data = await res.json();
{
"authorized": true,
"mode": "onchain",
"wallet": "0xsubscriberaddress",
"module_address": "0xmoduleaddress",
"chain_id": 84532,
"plan_ids": ["1"],
"onchain": {
"authorized": true,
"method": "isActive"
}
}
Example: both mode
const res = await fetch(
'https://api.amser.io/v0/auth/check?' +
'module_address=0xModuleAddress&' +
'wallet=0xSubscriberWallet&' +
'plan_ids=1,2&' +
'mode=both'
);
const data = await res.json();
{
"authorized": true,
"mode": "both",
"wallet": "0xsubscriberaddress",
"module_address": "0xmoduleaddress",
"chain_id": 84532,
"plan_ids": ["1", "2"],
"indexed": {
"authorized": true,
"matching_plan_ids": ["1"],
"details": [
{
"allocation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"plan_id_on_chain": "1",
"status": "ACTIVE",
"next_charge_date": "2026-04-01T00:00:00.000Z",
"times_executed": 3
}
]
},
"onchain": {
"authorized": true,
"method": "isActiveAny"
}
}
When multiple plan IDs are provided, the onchain mode uses isActiveAny() instead of isActive(), returning true if the wallet is active on any of the specified plans.
Server-side auth check without a wallet
The most common integration pattern is a server-side subscription check using only an API key — no wallet library, no private key, no RPC endpoint:
async function checkSubscription(
walletAddress: string,
moduleAddress: string,
planId: number
): Promise<boolean> {
const res = await fetch(
`https://api.amser.io/v0/auth/check?` +
`module_address=${moduleAddress}&` +
`wallet=${walletAddress}&` +
`plan_ids=${planId}&` +
`mode=indexed`,
{
headers: {
'Authorization': `Bearer ${process.env.AMSER_API_KEY}`,
}
}
);
const { authorized } = await res.json();
return authorized;
}
Related
- Authentication — SIWE, API keys, and on-chain gating patterns
- Merchant Integration — Full end-to-end setup walkthrough
- Webhooks — Receive real-time event notifications for subscription state changes