ReferenceReference by module

Packages API

Every Packages action (7) and data model (3): endpoints, scopes, input, preconditions, events and examples.

Monthly packages that bundle services and classes, with sessions counted.

Module code packages · 7 actions · 3 data models · 6 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: packages (everything), packages:read (read only) or packages:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
package_itemspackage_item/api/v1/package_items
package_usagespackage_usage/api/v1/package_usages
packagespackage/api/v1/packagespackages.create_packagepackages.update_package

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
packages.create_packagepackages:manageCreate a package
packages.redeempackages:writeUse a package session
packages.releasepackages:writeGive a session back
packages.retire_packagepackages:manageTake off sale
packages.set_itemspackages:manageChange what it covers
packages.update_packagepackages:manageEdit a package
packages.use_on_my_bookingpackages:writeUse my package

packages.create_package

Create a package — A period, a price and what it covers.

Needs packages:manage · changes data

POSThttps://app.getera.io/api/v1/packages/actions/create_package
API key or OAuth token · packages:manage
  • REST alias: POST /api/v1/packages
  • Canonical address: POST /api/v1/actions/packages.create_package

Input

FieldTypeRequiredNotes
namestringyesName · max 255
periodMonthsnumberyesMonths · min 1 · max 120
priceAmountnumber (money)yesPrice (minor units) · min 1
priceCurrencystringCurrency · max 3
autoRenewbooleanRenews by default
itemsobject / arrayyesWhat it covers · [{ itemVariantId? | groupClassId?, sessionsPerPeriod? (empty = unlimited) }]

Preconditions

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

CodeRefused when
invalid_itemsAt least one item, each either a service or a class.
service_not_foundEvery service must exist.
class_not_foundEvery class must exist and still run.

Events it publishes: packages.package_created

What it writes: package (insert) — Creates the package; package_item (insert) — Writes what it covers

curl -X POST https://app.getera.io/api/v1/packages/actions/create_package \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name","periodMonths":1,"priceAmount":1,"items":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "packages.package_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packages.create({
  name: "Name",
  periodMonths: 1,
  priceAmount: 1,
  items: {}
})
era packages create --data '{"name":"Name","periodMonths":1,"priceAmount":1,"items":{}}'
tools/call
{
  "name": "era__packages__create_package",
  "arguments": {
    "name": "Name",
    "periodMonths": 1,
    "priceAmount": 1,
    "items": {}
  }
}

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

packages.redeem

Use a package session — Covers a booking from a subscription, while it has sessions left.

Needs packages:write · changes data

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

Input

FieldTypeRequiredNotes
subscriptionIduuidyesSubscription · id of subscription
recordTypestring (enum)yesBooking kind · Allowed values: appointment_line, class_joining
recordIduuidyesBooking

Preconditions

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

CodeRefused when
subscription_not_foundThe subscription must exist.
subscription_not_activeThe subscription must be active.
record_not_foundThe booking must exist and not be cancelled.
customer_mismatchThe booking must be for the subscriber.
subscription_expiredThe booking must fall inside the subscription’s window.
not_coveredThe package must include what was booked.
already_redeemedA booking uses at most one session.
no_sessions_leftThe period’s sessions must not all be used.

Events it publishes: packages.session_used, packages.almost_used_up

What it writes: package_usage (insert) — Records the session drawn

curl -X POST https://app.getera.io/api/v1/package_usages/actions/redeem \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"subscriptionId":"SUBSCRIPTION_ID","recordType":"appointment_line","recordId":"RECORD_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "packages.session_used",
    "packages.almost_used_up"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packageUsages.redeem({
  subscriptionId: "SUBSCRIPTION_ID",
  recordType: "appointment_line",
  recordId: "RECORD_ID"
})
era package_usages redeem \
  --subscription-id "SUBSCRIPTION_ID" \
  --record-type "appointment_line" \
  --record-id "RECORD_ID"
tools/call
{
  "name": "era__packages__redeem",
  "arguments": {
    "subscriptionId": "SUBSCRIPTION_ID",
    "recordType": "appointment_line",
    "recordId": "RECORD_ID"
  }
}

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

packages.release

Give a session back — Undoes a session drawn by mistake. The booking itself is not touched.

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

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

Input

FieldTypeRequiredNotes
usageIduuidyesFilled from the path {id} · Session · id of package_usage

Preconditions

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

CodeRefused when
usage_not_foundThe session must have been drawn from a package.

Events it publishes: packages.session_released

What it writes: package_usage (delete) — Removes the usage

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packageUsages.release("PACKAGE_USAGE_ID")
era package_usages release PACKAGE_USAGE_ID
tools/call
{
  "name": "era__packages__release",
  "arguments": {
    "usageId": "PACKAGE_USAGE_ID"
  }
}

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

packages.retire_package

Take off sale — Stops selling it. Running subscriptions carry on to their end dates.

Needs packages:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/packages/{id}/actions/retire_package
API key or OAuth token · packages:manage
  • Canonical address: POST /api/v1/actions/packages.retire_package

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Package · id of package

Preconditions

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

CodeRefused when
not_foundThe package must exist.
package_retiredIt must still be on sale.

Events it publishes: packages.package_retired

What it writes: package (update) — Stamps archived_at

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packages.retirePackage("PACKAGE_ID")
era packages retire_package PACKAGE_ID
tools/call
{
  "name": "era__packages__retire_package",
  "arguments": {
    "id": "PACKAGE_ID"
  }
}

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

packages.set_items

Change what it covers — The services and classes in the package, with how many sessions each.

Needs packages:manage · removes or cannot be undone

POSThttps://app.getera.io/api/v1/package_items/actions/set_items
API key or OAuth token · packages:manage
  • Canonical address: POST /api/v1/actions/packages.set_items

Input

FieldTypeRequiredNotes
iduuidyesPackage · id of package
itemsobject / arrayyesWhat it covers · [{ itemVariantId? | groupClassId?, sessionsPerPeriod? (empty = unlimited) }]

Preconditions

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

CodeRefused when
not_foundThe package must exist.
package_retiredA package off sale cannot be edited.
invalid_itemsAt least one item, each either a service or a class.
service_not_foundEvery service must exist.
class_not_foundEvery class must exist and still run.
items_in_useContents cannot change once sessions have been used from them.

Events it publishes: packages.package_updated

What it writes: package_item (delete) — Clears the old contents; package_item (insert) — Writes the new contents

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packageItems.setItems({
  id: "PACKAGE_ID",
  items: {}
})
era package_items set_items --data '{"id":"PACKAGE_ID","items":{}}'
tools/call
{
  "name": "era__packages__set_items",
  "arguments": {
    "id": "PACKAGE_ID",
    "items": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold packages:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, package_retired, invalid_items, service_not_found, class_not_found, items_in_use · 429 slow down

packages.update_package

Edit a package — Name, period, price and renewal default — for new sales only.

Needs packages:manage · changes data

POSThttps://app.getera.io/api/v1/packages/{id}/actions/update_package
API key or OAuth token · packages:manage
  • REST alias: PATCH /api/v1/packages/{id}
  • Canonical address: POST /api/v1/actions/packages.update_package

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Package · id of package
namestringName · max 255
periodMonthsnumberMonths · min 1 · max 120
priceAmountnumber (money)Price (minor units) · min 1
priceCurrencystringCurrency · max 3
autoRenewbooleanRenews by default

Preconditions

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

CodeRefused when
not_foundThe package must exist.
package_retiredA package off sale cannot be edited.

Events it publishes: packages.package_updated

What it writes: package (update) — Updates the package

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

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.packages.updatePackage("PACKAGE_ID", {
  name: "Name"
})
era packages update_package PACKAGE_ID \
  --name "Name"
tools/call
{
  "name": "era__packages__update_package",
  "arguments": {
    "id": "PACKAGE_ID",
    "name": "Name"
  }
}

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

packages.use_on_my_booking

Use my package — A signed-in customer covers the booking they just made from their package, while it has sessions left.

Needs packages: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/package_usages/actions/use_on_my_booking
API key or OAuth token · packages:write
  • Canonical address: POST /api/v1/actions/packages.use_on_my_booking

Input

FieldTypeRequiredNotes
subscriptionIduuidyesPackage · id of subscription
appointmentIduuidyesBooking · id of appointment

Preconditions

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

CodeRefused when
not_your_bookingOnly a booking the customer is a guest on.
no_sessions_leftThe period’s sessions must not all be used.

Events it publishes: packages.session_used, packages.almost_used_up

What it writes: package_usage (insert) — Records the session drawn

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

Data models

PackageItem

Table package_item · Resource package_items

  • GET /api/v1/package_items
  • GET /api/v1/package_items/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
package_idstring (uuid) · uuidpackage
item_variant_idstring (uuid) · uuidyesitem_variant
group_class_idstring (uuid) · uuidyesgroup_class
sessions_per_periodinteger · int4yes
sort_orderinteger · int4
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((num_nonnulls(item_variant_id, group_class_id) = 1))
  • CHECK (((sessions_per_period IS NULL) OR (sessions_per_period > 0)))

PackageUsage

Table package_usage · Resource package_usages

  • GET /api/v1/package_usages
  • GET /api/v1/package_usages/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
subscription_idstring (uuid) · uuidsubscription
package_item_idstring (uuid) · uuidpackage_item
record_typestring · textappointment_line, class_joining
record_idstring (uuid) · uuid
used_onstring (date) · date
created_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Unique: record_type, record_id

Package

Table package · Resource packages

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
period_monthsinteger · int4
price_amountstring (int64) · int8
price_currencystring · bpcharyesmax 3
auto_renewboolean · bool
archived_atstring (date-time) · timestamptzyes
created_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
is_publicboolean · boolyes

Primary key: id

Checks:

  • CHECK (((length(btrim(name)) >= 1) AND (length(btrim(name)) <= 255)))
  • CHECK ((period_months > 0))
  • CHECK ((price_amount >= 0))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
packages.almost_used_upA subscription has two or fewer sessions left this period — time to offer the renewal.id, subscriptionId, packageId, packageName, packageItemId, customerId, remaining, included, endsOn, autoRenew
packages.package_createdA package was put on sale.id, name, periodMonths, priceAmount
packages.package_retiredA package was taken off sale.id
packages.package_updatedA package changed.id
packages.session_releasedA package session was given back.id, subscriptionId
packages.session_usedA booking was covered by a package.id, subscriptionId, recordType, recordId, customerId, remaining
Was this page helpful?