ReferenceReference by module

Subscriptions API

Every Subscriptions action (9) and data model (2): endpoints, scopes, input, preconditions, events and examples.

Recurring plans at a fixed price, renewed and billed every period.

Module code subscriptions · 9 actions · 2 data models · 9 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: subscriptions (everything), subscriptions:read (read only) or subscriptions:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
subscription_periodssubscription_period/api/v1/subscription_periods
subscriptionssubscription/api/v1/subscriptions

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
subscriptions.buy_package_onlinesubscriptions:writeBuy a package online
subscriptions.cancelsubscriptions:writeCancel
subscriptions.pausesubscriptions:writePause
subscriptions.raise_ending_noticessubscriptions:writeRaise “ending soon” notices
subscriptions.renewsubscriptions:writeRenew now
subscriptions.renew_duesubscriptions:manageRenew everything due
subscriptions.resumesubscriptions:writeResume
subscriptions.set_auto_renewsubscriptions:writeRenewal
subscriptions.startsubscriptions:writeStart a subscription

subscriptions.buy_package_online

Buy a package online — A signed-in customer starts a package from the site: a subscription at the package’s price, its first period open from today.

Needs subscriptions:write · changes data · Also callable from a published site (customer)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/subscriptions/actions/buy_package_online
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.buy_package_online

Input

FieldTypeRequiredNotes
packageIduuidyesPackage · id of package
autoRenewbooleanRenews automatically

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
package_not_foundThe package must be on sale.
subscription_already_activeOne live subscription per person per package.

Events it publishes: subscriptions.started, subscriptions.period_started

What it writes: subscription (insert) — Starts the subscription; subscription_period (insert) — Opens the first period

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 package_not_found, subscription_already_active · 429 slow down

subscriptions.cancel

Cancel — Ends it now, or at the end of the period already paid for.

Needs subscriptions:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/subscriptions/{id}/actions/cancel
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.cancel

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Subscription · id of subscription
atPeriodEndbooleanAt the end of this period
reasonstringReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_already_endedAn ended subscription is not cancelled again.

Events it publishes: subscriptions.cancelled

What it writes: subscription (update) — Cancels it; subscription_period (update) — Cancels a period that has not started

curl -X POST https://app.getera.io/api/v1/subscriptions/SUBSCRIPTION_ID/actions/cancel \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"atPeriodEnd":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.cancel("SUBSCRIPTION_ID", {
  atPeriodEnd: true
})
era subscriptions cancel SUBSCRIPTION_ID \
  --at-period-end true
tools/call
{
  "name": "era__subscriptions__cancel",
  "arguments": {
    "id": "SUBSCRIPTION_ID",
    "atPeriodEnd": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subscription_not_found, subscription_already_ended · 429 slow down

subscriptions.pause

Pause — Stops renewals without ending the subscription.

Needs subscriptions:write · changes data

POSThttps://app.getera.io/api/v1/subscriptions/{id}/actions/pause
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.pause

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Subscription · id of subscription

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_not_activeOnly an active subscription pauses.

Events it publishes: subscriptions.paused

What it writes: subscription (update) — Pauses it

curl -X POST https://app.getera.io/api/v1/subscriptions/SUBSCRIPTION_ID/actions/pause \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.paused"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.pause("SUBSCRIPTION_ID")
era subscriptions pause SUBSCRIPTION_ID
tools/call
{
  "name": "era__subscriptions__pause",
  "arguments": {
    "id": "SUBSCRIPTION_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subscription_not_found, subscription_not_active · 429 slow down

subscriptions.raise_ending_notices

Raise “ending soon” notices — Machine-only (the daily routine): every subscription that will not renew and ends within the window is told about once per period.

Needs subscriptions:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/subscriptions/actions/raise_ending_notices
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.raise_ending_notices

Input

FieldTypeRequiredNotes
asOfdateAs of

Events it publishes: subscriptions.ending_soon

What it writes: subscription (update) — Records that the ending notice was raised

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

subscriptions.renew

Renew now — Opens the next period — or ends the subscription when its term or its cancellation says so.

Needs subscriptions:write · changes data

POSThttps://app.getera.io/api/v1/subscriptions/{id}/actions/renew
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.renew

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Subscription · id of subscription

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_not_renewableOnly an active or trialing subscription renews.

Events it publishes: subscriptions.renewed, subscriptions.period_started, subscriptions.cancelled, subscriptions.expired

What it writes: subscription (update) — Moves it onto the next period; subscription_period (insert) — Opens the period

curl -X POST https://app.getera.io/api/v1/subscriptions/SUBSCRIPTION_ID/actions/renew \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.renewed",
    "subscriptions.period_started",
    "subscriptions.cancelled",
    "subscriptions.expired"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.renew("SUBSCRIPTION_ID")
era subscriptions renew SUBSCRIPTION_ID
tools/call
{
  "name": "era__subscriptions__renew",
  "arguments": {
    "id": "SUBSCRIPTION_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subscription_not_found, subscription_not_renewable · 429 slow down

subscriptions.renew_due

Renew everything due — Renews every automatic subscription whose renewal date has come.

Needs subscriptions:manage · changes data

POSThttps://app.getera.io/api/v1/subscription_periods/actions/renew_due
API key or OAuth token · subscriptions:manage
  • Canonical address: POST /api/v1/actions/subscriptions.renew_due

Input

FieldTypeRequiredNotes
asOfdateAs of

Events it publishes: subscriptions.renewed, subscriptions.period_started, subscriptions.cancelled, subscriptions.expired

What it writes: subscription (update) — Moves each onto its next period; subscription_period (insert) — Opens the periods

curl -X POST https://app.getera.io/api/v1/subscription_periods/actions/renew_due \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"asOf":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.renewed",
    "subscriptions.period_started",
    "subscriptions.cancelled",
    "subscriptions.expired"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptionPeriods.renewDue({
  asOf: "2026-11-02"
})
era subscription_periods renew_due \
  --as-of "2026-11-02"
tools/call
{
  "name": "era__subscriptions__renew_due",
  "arguments": {
    "asOf": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

subscriptions.resume

Resume — Picks a paused subscription back up; the next renewal is today at the earliest.

Needs subscriptions:write · changes data

POSThttps://app.getera.io/api/v1/subscriptions/{id}/actions/resume
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.resume

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Subscription · id of subscription

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_not_pausedOnly a paused subscription resumes.

Events it publishes: subscriptions.resumed

What it writes: subscription (update) — Resumes it

curl -X POST https://app.getera.io/api/v1/subscriptions/SUBSCRIPTION_ID/actions/resume \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.resumed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.resume("SUBSCRIPTION_ID")
era subscriptions resume SUBSCRIPTION_ID
tools/call
{
  "name": "era__subscriptions__resume",
  "arguments": {
    "id": "SUBSCRIPTION_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subscription_not_found, subscription_not_paused · 429 slow down

subscriptions.set_auto_renew

Renewal — Whether it renews by itself when the period ends; the daily sweep ends one that does not.

Needs subscriptions:write · changes data

POSThttps://app.getera.io/api/v1/subscriptions/{id}/actions/set_auto_renew
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.set_auto_renew

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Subscription · id of subscription
autoRenewbooleanyesRenew automatically

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_already_endedAn ended subscription does not renew.

Events it publishes: subscriptions.auto_renew_changed

What it writes: subscription (update) — Sets auto_renew

curl -X POST https://app.getera.io/api/v1/subscriptions/SUBSCRIPTION_ID/actions/set_auto_renew \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"autoRenew":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.auto_renew_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.setAutoRenew("SUBSCRIPTION_ID", {
  autoRenew: true
})
era subscriptions set_auto_renew SUBSCRIPTION_ID \
  --auto-renew true
tools/call
{
  "name": "era__subscriptions__set_auto_renew",
  "arguments": {
    "id": "SUBSCRIPTION_ID",
    "autoRenew": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 subscription_not_found, subscription_already_ended · 429 slow down

subscriptions.start

Start a subscription — A recurring term at a locked price. The first period opens now, or when the trial ends.

Needs subscriptions:write · changes data

POSThttps://app.getera.io/api/v1/subscriptions/actions/start
API key or OAuth token · subscriptions:write
  • Canonical address: POST /api/v1/actions/subscriptions.start

Input

FieldTypeRequiredNotes
customerIduuidyesSubscriber · id of customer
namestringyesPlan · max 255
amountnumber (money)yesPrice per period
currencystringCurrency · max 3
billingCyclestring (enum)Every · Allowed values: weekly, monthly, quarterly, yearly
startDatedateStarts
trialDaysnumberTrial days · min 0 · max 365
endDatedateEnds
autoRenewbooleanRenews automatically
itemModulestringItem module · max 40
itemRecordIduuidItem
periodMonthsnumberMonths per period · A plan sold in whole months (a package); overrides the cycle. · min 1 · max 120
notesstringNotes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
customer_not_foundThe person must exist.
customer_archivedAn archived person does not subscribe.
invalid_currencyThe currency must be an ISO-4217 code.
subscription_amount_not_positiveA subscription costs more than zero.
subscription_already_activeOne live subscription per person per plan.
subscription_end_before_startA term cannot end before it starts.

Events it publishes: subscriptions.started, subscriptions.period_started

What it writes: subscription (insert) — Starts the subscription; subscription_period (insert) — Opens the first period

curl -X POST https://app.getera.io/api/v1/subscriptions/actions/start \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID","name":"Plan","amount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "subscriptions.started",
    "subscriptions.period_started"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.subscriptions.start({
  customerId: "CUSTOMER_ID",
  name: "Plan",
  amount: 1
})
era subscriptions start \
  --customer-id "CUSTOMER_ID" \
  --name "Plan" \
  --amount 1
tools/call
{
  "name": "era__subscriptions__start",
  "arguments": {
    "customerId": "CUSTOMER_ID",
    "name": "Plan",
    "amount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold subscriptions:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found, customer_archived, invalid_currency, subscription_amount_not_positive, subscription_already_active, subscription_end_before_start · 429 slow down

Data models

SubscriptionPeriod

Table subscription_period · Resource subscription_periods

  • GET /api/v1/subscription_periods
  • GET /api/v1/subscription_periods/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
subscription_idstring (uuid) · uuidsubscription
sequenceinteger · int4
period_startstring (date) · date
period_endstring (date) · date
currencystring · bpcharmax 3
amountstring (int64) · int8
statusstring · textcurrent, completed, cancelled
created_atstring (date-time) · timestamptz

Primary key: id

Unique: subscription_id, sequence

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((period_end >= period_start))
  • CHECK ((sequence > 0))

Subscription

Table subscription · Resource subscriptions

  • GET /api/v1/subscriptions
  • GET /api/v1/subscriptions/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
customer_idstring (uuid) · uuidcustomer
namestring · text
item_modulestring · textyes
item_record_idstring (uuid) · uuidyes
statusstring · texttrialing, active, paused, cancelled, expired
billing_cyclestring · textweekly, monthly, quarterly, yearly
currencystring · bpcharmax 3
amountstring (int64) · int8
start_datestring (date) · date
trial_ends_atstring (date) · dateyes
end_datestring (date) · dateyes
current_period_startstring (date) · dateyes
current_period_endstring (date) · dateyes
next_renewal_datestring (date) · dateyes
auto_renewboolean · bool
cancel_at_period_endboolean · bool
cancelled_atstring (date-time) · timestamptzyes
cancel_reasonstring · textyes
paused_atstring (date-time) · timestamptzyes
notesstring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
period_monthsinteger · int4yes
renewalsinteger · int4
ending_notice_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK (((end_date IS NULL) OR (end_date >= start_date)))
  • CHECK ((length(TRIM(BOTH FROM name)) > 0))
  • CHECK (((period_months IS NULL) OR (period_months > 0)))
  • CHECK ((renewals >= 0))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
subscriptions.auto_renew_changedA subscription’s renewal setting changed.id, autoRenew
subscriptions.cancelledA subscription was cancelled.id, atPeriodEnd, customerId, itemModule
subscriptions.ending_soonA membership that will not renew ends soon.id, customerId, customerName, name, endsOn, daysLeft, itemModule, itemRecordId
subscriptions.expiredA subscription ran out — its term ended, or its period ended without renewing.id, customerId, autoRenew, itemModule, itemRecordId
subscriptions.pausedA subscription was paused.id
subscriptions.period_startedA paid period began — finance opens its agreement.id, subscriptionId, customerId, name, sequence, periodStart, periodEnd, amount, currency, itemModule, itemRecordId
subscriptions.renewedA subscription renewed.id, count
subscriptions.resumedA subscription was resumed.id
subscriptions.startedA subscription started — a plan, or a package sold as a membership.id, customerId, trialEndsAt, name, startsOn, endsOn, itemModule, itemRecordId
Was this page helpful?