HyphaBase

Services

What you offer and what it costs. Defined once, optionally shared across every site.

Brightside offers a standard clean, a deep clean, and an end-of-tenancy clean. The deep clean is £120 for a three-bed and takes three hours. Customers can add oven cleaning for £25.

That's this module. Services are what you sell, with a price and optional tax treatment. Service groups organise them for display. Add-ons are the extras someone ticks on top.

What you can build with Services · 1:16
Read it instead

Services is what you sell and what it costs, defined once and offered at whichever sites you choose. You reach it over REST: a secret key and a location header.

A service carries a name, a price, whether it is taxable, and a tax category. Groups set display order on a booking page, add-ons hang off it like oven cleaning at twenty-five pounds, and location_ids offer the same clean at Portland and Salem.

Grant services.view on a read-only key and your public price list comes straight from the API. Subscribe to services.service.price_changed and a rate rise reaches the booking page the moment it is saved.

Four scopes decide what a key can do. One that only reads can never move a number, which is what makes a public price list safe to ship.

Services: name, price, groups and add-ons on one record, shared across sites and priced in one place. The guide is below.

Before you start

  • The module switched on for the site. If it isn't, everything below returns 403 regardless of your key — check the site's modules on the Locations page first.
  • A credential with the right scopes — services.view to read, services.create to add. See what to grant.
  • The site's id, for X-Location-Id on every request.

What a service looks like

Prop

Type

The endpoints

The six service endpoints below are stable. Groups and add-ons are in beta — they work, but their shape may still change, so pin to a dated API version before building on them.

Adding a service

await fetch('https://api.hyphabase.com/api/modules/services', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'X-XSRF-TOKEN': readCookie('XSRF-TOKEN'),
    'X-Location-Id': locationId,
  },
  body: JSON.stringify({
    name: 'Deep clean — 3 bed',
    description: 'Three hours, two cleaners, inside cupboards and appliances.',
    price: 120,
    taxable: true,
  }),
});

Only name and price are required. Everything else is optional, which means a service created with two fields is valid and sellable. It takes 60 minutes unless you say otherwise.

A successful create returns 201 with the new service. A negative or non-numeric duration_minutes, buffer_time_minutes or capacity_cost returns 422.

Changing and deleting. PATCH changes only the fields you send; PUT replaces the whole record. If two people edit the same service, the last write wins.

DELETE archives the service: it disappears from the list and from GET, which then returns 404, but invoices and bookings that used it keep their history.

Offering it at more than one site

Pass location_ids with every site that should offer it:

{
  "name": "Deep clean — 3 bed",
  "price": 120,
  "location_ids": ["019aff48-…", "019aff52-…"]
}

Leave it out and the service belongs to the site you created it at. Portland's price list is genuinely separate from Salem's — which is usually what you want, because a Salem three-bed doesn't cost the same as a Portland one.

Same service everywhere? Define it once at the app level.

If your prices really are identical across sites, maintaining them per site is a chore that drifts. Standard services can be defined once for the whole application and pushed down to the sites that opt in, so an edit happens in one place. That's set up in the dashboard rather than through the API — see Model your business.

Groups, for display

A group is a heading on your booking page. "Regular cleans", "One-off deep cleans", "Move-out".

curl -X POST https://api.hyphabase.com/api/modules/services/service-groups \
  -u sk_live_xxx: -H "X-Location-Id: $HYPHABASE_LOCATION_ID" \
  -H "Content-Type: application/json" \
  -d '{"name": "One-off deep cleans", "description": "Booked as needed."}'

Then set service_group_id on the services that belong to it. Grouping is presentational — nothing about pricing or booking depends on it.

Add-ons

The extras a customer ticks alongside the main thing. Oven cleaning, inside windows, fridge.

Prop

Type

curl -X POST https://api.hyphabase.com/api/modules/services/$SERVICE_ID/addons \
  -u sk_live_xxx: -H "X-Location-Id: $HYPHABASE_LOCATION_ID" \
  -H "Content-Type: application/json" \
  -d '{"name": "Oven clean", "price": 25, "display_order": 1}'

Add-ons belong to one service, so "oven clean" on both the standard and deep clean is two records. If you find yourself maintaining the same add-on across many services, that's a sign it should be a service in its own right.

What you get told about

EventWhen
services.service.createdA service is added
services.service.price_changedIts price changes

price_changed is the one worth subscribing to. If you cache prices anywhere — a booking page, a mobile app — this is how you know to refresh rather than quoting last month's figure. See Getting told when things happen.

What to grant

ScopeLets you
services.viewRead services, groups and add-ons
services.createAdd them
services.updateChange them, including prices
services.deleteRemove them

services.view is safe for a publishable key, which is what makes a public price list possible without a server in between.

Be careful with services.update — it's the scope that can change what you charge. A booking page never needs it.

What goes wrong

Recipes

Publish a price list without a server. Grant a publishable key services.view, fetch groups and services from your booking page, and render them. No secret key, no backend.

Raise prices for the new year. List services, PATCH each one's price, and let the services.service.price_changed event invalidate whatever you've cached. Do it per site — Portland and Salem are separate lists unless you're sharing at the app level.

Mirror your catalogue into a booking widget. Subscribe to services.service.created and price_changed, and upsert into your own store. Everything in When deliveries fail applies.

Next

On this page