Resources API
Every Resources action (7) and data model (3): endpoints, scopes, input, preconditions, events and examples.
Rooms, pitches, cars, chairs and equipment people book — by the hour or by the day.
Module code resources · 7 actions · 3 data models · 7 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: resources (everything), resources:read (read only) or resources:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
resource_bookings | resource_booking | /api/v1/resource_bookings | resources.book | ||
resource_services | resource_service | /api/v1/resource_services | |||
resources | resource | /api/v1/resources | resources.create | resources.update | resources.archive |
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
| Action | Needs | What it does |
|---|---|---|
resources.archive | resources:manage | Archive |
resources.book | resources:write | Book a resource |
resources.book_weekly | resources:write | Hold every week |
resources.cancel_booking | resources:write | Cancel booking |
resources.create | resources:manage | Add a resource |
resources.set_services | resources:manage | Set the services that need it |
resources.update | resources:manage | Edit a resource |
resources.archive
Archive — Stops new bookings. Existing ones stay as they are.
Needs resources:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/resources/{id}/actions/archive- REST alias:
DELETE /api/v1/resources/{id} - Canonical address:
POST /api/v1/actions/resources.archive
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Resource · id of resource |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_not_found | The resource must exist. |
resource_archived | It must not be archived already. |
Events it publishes: resources.archived
What it writes: resource (update) — Stamps archived_at
curl -X POST https://app.getera.io/api/v1/resources/RESOURCE_ID/actions/archive \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"resources.archived"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resources.archive("RESOURCE_ID")era resources archive RESOURCE_ID{
"name": "era__resources__archive",
"arguments": {
"id": "RESOURCE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_not_found, resource_archived · 429 slow down
resources.book
Book a resource — An hour on a pitch or nine nights in a room — refused when it is already full then.
Needs resources:write · changes data
https://app.getera.io/api/v1/resource_bookings/actions/book- REST alias:
POST /api/v1/resource_bookings - Canonical address:
POST /api/v1/actions/resources.book
Input
| Field | Type | Required | Notes |
|---|---|---|---|
resourceId | uuid | yes | Resource · id of resource |
startsAt | string | yes | From (local, YYYY-MM-DDTHH:mm) · max 16 |
endsAt | string | yes | Until (local, YYYY-MM-DDTHH:mm) · max 16 |
customerId | uuid | Customer · id of customer | |
appointmentId | uuid | Appointment (the staff half) | |
note | string | Note · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_not_found | The resource must exist. |
resource_archived | An archived resource takes no bookings. |
customer_not_found | The customer must exist. |
appointment_not_found | A linked appointment must exist. |
resource_booking_range_invalid | A booking ends after it starts. |
resource_full | Fewer than capacity bookings may overlap it. |
Events it publishes: resources.booked
What it writes: resource_booking (insert) — Books it
curl -X POST https://app.getera.io/api/v1/resource_bookings/actions/book \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"resourceId":"RESOURCE_ID","startsAt":"From (local, YYYY-MM-DDTHH:mm)","endsAt":"Until (local, YYYY-MM-DDTHH:mm)"}'{
"ok": true,
"result": "…",
"events": [
"resources.booked"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resourceBookings.create({
resourceId: "RESOURCE_ID",
startsAt: "From (local, YYYY-MM-DDTHH:mm)",
endsAt: "Until (local, YYYY-MM-DDTHH:mm)"
})era resource_bookings create \
--resource-id "RESOURCE_ID" \
--starts-at "From (local, YYYY-MM-DDTHH:mm)" \
--ends-at "Until (local, YYYY-MM-DDTHH:mm)"{
"name": "era__resources__book",
"arguments": {
"resourceId": "RESOURCE_ID",
"startsAt": "From (local, YYYY-MM-DDTHH:mm)",
"endsAt": "Until (local, YYYY-MM-DDTHH:mm)"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_not_found, resource_archived, customer_not_found, appointment_not_found, resource_booking_range_invalid, resource_full · 429 slow down
resources.book_weekly
Hold every week — The same weekday and time each week until the subscription’s period ends — all weeks or none.
Needs resources:write · changes data
https://app.getera.io/api/v1/resource_bookings/actions/book_weekly- Canonical address:
POST /api/v1/actions/resources.book_weekly
Input
| Field | Type | Required | Notes |
|---|---|---|---|
resourceId | uuid | yes | Resource · id of resource |
subscriptionId | uuid | yes | Subscription · id of subscription |
startsAt | string | yes | First week from (local) · max 16 |
endsAt | string | yes | First week until (local) · max 16 |
note | string | Note · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_not_found | The resource must exist. |
resource_archived | An archived resource takes no bookings. |
subscription_not_live | The subscription must be running. |
resource_booking_range_invalid | A booking ends after it starts, inside the period. |
resource_full | Every week must have room. |
Events it publishes: resources.series_booked
What it writes: resource_booking (insert) — Books every week of the period
curl -X POST https://app.getera.io/api/v1/resource_bookings/actions/book_weekly \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"resourceId":"RESOURCE_ID","subscriptionId":"SUBSCRIPTION_ID","startsAt":"First week from (local)","endsAt":"First week until (local)"}'{
"ok": true,
"result": "…",
"events": [
"resources.series_booked"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resourceBookings.bookWeekly({
resourceId: "RESOURCE_ID",
subscriptionId: "SUBSCRIPTION_ID",
startsAt: "First week from (local)",
endsAt: "First week until (local)"
})era resource_bookings book_weekly \
--resource-id "RESOURCE_ID" \
--subscription-id "SUBSCRIPTION_ID" \
--starts-at "First week from (local)" \
--ends-at "First week until (local)"{
"name": "era__resources__book_weekly",
"arguments": {
"resourceId": "RESOURCE_ID",
"subscriptionId": "SUBSCRIPTION_ID",
"startsAt": "First week from (local)",
"endsAt": "First week until (local)"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_not_found, resource_archived, subscription_not_live, resource_booking_range_invalid, resource_full · 429 slow down
resources.cancel_booking
Cancel booking — Frees the time. For a weekly series, optionally every week still to come.
Needs resources:write · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/resource_bookings/{id}/actions/cancel_booking- Canonical address:
POST /api/v1/actions/resources.cancel_booking
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Booking · id of resource_booking |
reason | string | Reason · max 500 | |
wholeSeries | boolean | Every week still to come |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_booking_not_found | The booking must exist. |
resource_booking_cancelled | It must not be cancelled already. |
Events it publishes: resources.booking_cancelled
What it writes: resource_booking (update) — Marks it cancelled
curl -X POST https://app.getera.io/api/v1/resource_bookings/RESOURCE_BOOKING_ID/actions/cancel_booking \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"reason":"Reason"}'{
"ok": true,
"result": "…",
"events": [
"resources.booking_cancelled"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resourceBookings.cancelBooking("RESOURCE_BOOKING_ID", {
reason: "Reason"
})era resource_bookings cancel_booking RESOURCE_BOOKING_ID \
--reason "Reason"{
"name": "era__resources__cancel_booking",
"arguments": {
"id": "RESOURCE_BOOKING_ID",
"reason": "Reason"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_booking_not_found, resource_booking_cancelled · 429 slow down
resources.create
Add a resource — A room, pitch, car, chair or piece of equipment people book.
Needs resources:manage · changes data
https://app.getera.io/api/v1/resources/actions/create- REST alias:
POST /api/v1/resources - Canonical address:
POST /api/v1/actions/resources.create
Input
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Name · max 120 |
kind | string (enum) | Kind · Allowed values: room, pitch, vehicle, chair, equipment, other | |
capacity | number | Bookings at once · min 1 · max 1000 | |
opensAt | string | Opens · max 5 | |
closesAt | string | Closes · max 5 | |
slotMinutes | number | Booked per (minutes; 1440 = by the day) · min 5 · max 1440 | |
color | string | Colour · max 20 | |
description | string | Description · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_name_taken | Two resources in use do not share a name. |
resource_hours_invalid | Opening hours are HH:mm and close after they open. |
Events it publishes: resources.created
What it writes: resource (insert) — Adds the resource
curl -X POST https://app.getera.io/api/v1/resources/actions/create \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"resources.created"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resources.create({
name: "Name"
})era resources create \
--name "Name"{
"name": "era__resources__create",
"arguments": {
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_name_taken, resource_hours_invalid · 429 slow down
resources.set_services
Set the services that need it — The services booked with this resource. The booking wizard asks for a free one when such a service is booked.
Needs resources:manage · changes data
https://app.getera.io/api/v1/resource_services/actions/set_services- Canonical address:
POST /api/v1/actions/resources.set_services
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Resource · id of resource |
itemIds | string | Services · Comma-separated service ids. Empty unties it from every service. · max 4000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_not_found | The resource must exist. |
resource_archived | An archived resource is not tied to services. |
item_not_found | Every service must exist. |
Events it publishes: resources.services_changed
What it writes: resource_service (upsert) — Replaces the list of services that need it
curl -X POST https://app.getera.io/api/v1/resource_services/actions/set_services \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"RESOURCE_ID"}'{
"ok": true,
"result": "…",
"events": [
"resources.services_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resourceServices.setServices({
id: "RESOURCE_ID"
})era resource_services set_services \
--id "RESOURCE_ID"{
"name": "era__resources__set_services",
"arguments": {
"id": "RESOURCE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_not_found, resource_archived, item_not_found · 429 slow down
resources.update
Edit a resource — Name, kind, capacity, hours and slot length. Existing bookings stand.
Needs resources:manage · changes data
https://app.getera.io/api/v1/resources/{id}/actions/update- REST alias:
PATCH /api/v1/resources/{id} - Canonical address:
POST /api/v1/actions/resources.update
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Resource · id of resource |
name | string | Name · max 120 | |
kind | string (enum) | Kind · Allowed values: room, pitch, vehicle, chair, equipment, other | |
capacity | number | Bookings at once · min 1 · max 1000 | |
opensAt | string | Opens · max 5 | |
closesAt | string | Closes · max 5 | |
slotMinutes | number | Booked per (minutes; 1440 = by the day) · min 5 · max 1440 | |
color | string | Colour · max 20 | |
description | string | Description · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
resource_not_found | The resource must exist. |
resource_archived | An archived resource is not edited. |
resource_name_taken | Two resources in use do not share a name. |
resource_hours_invalid | Opening hours are HH:mm and close after they open. |
Events it publishes: resources.updated
What it writes: resource (update) — Updates the resource
curl -X POST https://app.getera.io/api/v1/resources/RESOURCE_ID/actions/update \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"resources.updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.resources.update("RESOURCE_ID", {
name: "Name"
})era resources update RESOURCE_ID \
--name "Name"{
"name": "era__resources__update",
"arguments": {
"id": "RESOURCE_ID",
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold resources:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 resource_not_found, resource_archived, resource_name_taken, resource_hours_invalid · 429 slow down
Data models
ResourceBooking
Table resource_booking · Resource resource_bookings
GET /api/v1/resource_bookingsGET /api/v1/resource_bookings/{id}POST /api/v1/resource_bookings→resources.book
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
resource_id | string (uuid) · uuid | resource | ||
customer_id | string (uuid) · uuid | yes | customer | |
appointment_id | string (uuid) · uuid | yes | ||
subscription_id | string (uuid) · uuid | yes | subscription | |
series_id | string (uuid) · uuid | yes | ||
starts_at | string (date-time) · timestamptz | |||
ends_at | string (date-time) · timestamptz | |||
status | string · text | booked, cancelled | ||
note | string · text | yes | ||
cancelled_at | string (date-time) · timestamptz | yes | ||
cancel_reason | string · text | yes | ||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((ends_at > starts_at))
ResourceService
Table resource_service · Resource resource_services
GET /api/v1/resource_servicesGET /api/v1/resource_services/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
resource_id | string (uuid) · uuid | resource | ||
item_id | string (uuid) · uuid | item | ||
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: resource_id, item_id
Resource
Table resource · Resource resources
GET /api/v1/resourcesGET /api/v1/resources/{id}POST /api/v1/resources→resources.createPATCH /api/v1/resources/{id}→resources.updateDELETE /api/v1/resources/{id}→resources.archive
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
name | string · text | |||
kind | string · text | room, pitch, vehicle, chair, equipment, other | ||
capacity | integer · int4 | |||
opens_at | string (time) · time | |||
closes_at | string (time) · time | |||
slot_minutes | integer · int4 | |||
color | string · text | yes | ||
description | string · text | yes | ||
archived_at | string (date-time) · timestamptz | yes | ||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
is_public | boolean · bool | yes |
Primary key: id
Checks:
CHECK (((capacity >= 1) AND (capacity <= 1000)))CHECK (((slot_minutes = 1440) OR (closes_at > opens_at)))CHECK ((length(TRIM(BOTH FROM name)) > 0))CHECK (((slot_minutes >= 5) AND (slot_minutes <= 1440)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
resources.archived | A resource was archived. | id |
resources.booked | A resource was booked. | id, resourceId, customerId, appointmentId, startsAt, endsAt |
resources.booking_cancelled | A resource booking was cancelled. | id, resourceId, count |
resources.created | A resource was added. | id, name, kind, capacity |
resources.series_booked | A resource was held every week for a subscription. | id, resourceId, subscriptionId, customerId, weeks, until |
resources.services_changed | The services that need a resource changed. | id, itemIds |
resources.updated | A resource changed. | id |