Skip to content

Beta program: three months of DeMark free, no card. 25 of 25 spots left, sign-ups close November 16. Join the beta

DeMark

Developers

API reference

Use your DeMark back office from your own website: book appointments, receive requests, sell and chat. Everything is JSON over HTTPS.

Base URL
https://demarkstudio.ca/api/v1
OpenAPI
Download openapi.json

Basics

Authentication

Create keys in Settings > Developers and send them in the Authorization header. A revoked key stops working at once.

  • dmk_live_... Secret key: only from your server. It is refused when a browser sends it.
  • dmk_pk_... Public key: safe in your HTML. It reads services, availability, products and events and creates bookings, requests, carts, registrations and chat turns, only from the domains you allowed.
Authorization: Bearer dmk_live_...

Errors

Every error has the same body: a generic code (unauthorized, forbidden, not_found, conflict, validation, rate_limited) and a reason in message. Validation errors list the fields.

{
  "error": {
    "code": "validation",
    "message": "invalid_fields",
    "fields": {
      "email": "invalid_email"
    }
  }
}

Pagination

Lists return data and next_cursor. Send next_cursor back as ?cursor= to get the next page; limit goes from 1 to 100 (25 by default). A null next_cursor means you reached the end.

{
  "data": [
    "..."
  ],
  "next_cursor": "MTAx"
}

Dates and times

Times are ISO 8601 with the offset of the business time zone. You can send any ISO 8601 time with an offset or Z; without one it is read in the business time zone.

Rate limits

600 requests per minute per key. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; past the limit you get 429 with Retry-After.

Account

Which business and which key you are using.

GET /api/v1/me

The business and the key of the request

Works with a public key

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 429 Rate limited

Example response (200)

{
  "business": {
    "id": 12,
    "slug": "salon-ana",
    "name": "Salon Ana",
    "languages": [
      "en",
      "fr"
    ],
    "timezone": "America/Moncton",
    "currency": "CAD",
    "booking_url": "https://demarkstudio.ca/salon-ana/book",
    "site_url": "https://www.salonana.ca"
  },
  "key": {
    "name": "Website",
    "prefix": "dmk_pk_ab12c",
    "type": "public",
    "permissions": [
      "reservas",
      "solicitudes"
    ],
    "scopes": [
      "bookings:create",
      "requests:create",
      "services:read"
    ]
  }
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/me" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Bookings

Services, free times and appointments. A booking made here sends the same emails as the booking page.

GET /api/v1/services

List the services that can be booked

Works with a public key

Parameters

NameInTypeRequiredDescription
lang query string | null No en or fr; without it, Accept-Language.
cursor query string | null No
limit query integer No Defaults to the maximum: most businesses fit in one page.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 3,
      "name": "Coupe",
      "description": "Lavage, coupe et coiffage.",
      "duration_min": 45,
      "price": "45.00",
      "currency": "CAD",
      "staff": [
        {
          "id": 1,
          "name": "Julie"
        }
      ],
      "image_url": null
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/services" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/availability

Free start times for a service on one day

Works with a public key

Parameters

NameInTypeRequiredDescription
service query integer Yes Service id.
date query string Yes Local day, YYYY-MM-DD.
staff query integer | null No Staff id; omit for anyone.
location query string | null No Location id or slug (GET /locations); omit for the primary location.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "start": "2026-09-21T09:00:00-03:00",
      "end": "2026-09-21T09:45:00-03:00",
      "staff_id": null
    }
  ],
  "timezone": "America/Moncton"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/availability?service=7&date=2026-09-21" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/locations

List the locations of the business

Works with a public key

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 2,
      "name": "Downtown",
      "slug": "downtown",
      "address": "12 King St, Moncton NB",
      "phone": "506 555 0100",
      "timezone": "America/Moncton",
      "primary": true
    }
  ]
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/locations" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/bookings

Book an appointment

Works with a public key

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
service integer Yes Service id.
start string Yes A start from /availability (ISO 8601).
name string Yes
email string Yes
phone string | null No
staff integer | null No Staff id; omit for anyone.
location integer | null No Location id (GET /locations); omit for the primary location.
notes string | null No
lang string | null No en or fr; defaults to the business language.
consent boolean | null No Opt-in to news from the business.
policy_accepted boolean | null No Required when the service asks for a deposit or card.

Example body

{
  "service": 7,
  "start": "2026-09-21T09:00:00-03:00",
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "staff": null,
  "location": null,
  "notes": "First visit",
  "lang": "en",
  "consent": false,
  "policy_accepted": true
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 1,
  "status": "confirmed",
  "start": "2026-10-09T09:00:00-03:00",
  "end": "2026-10-09T09:45:00-03:00",
  "service": {
    "id": 3,
    "name": "Coupe"
  },
  "staff": {
    "id": 1,
    "name": "Julie"
  },
  "location": {
    "id": 2,
    "name": "Salon Ana",
    "timezone": "America/Moncton"
  },
  "customer": {
    "name": "Marie Tremblay",
    "email": "marie.tremblay@mail.test",
    "phone": "506 555 0123"
  },
  "notes": "First visit",
  "lang": "fr",
  "source": "api",
  "created_at": "2026-10-02T23:24:52-03:00",
  "token": "IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA",
  "manage_url": "https://demarkstudio.ca/citas/IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA",
  "cancel_url": "https://demarkstudio.ca/citas/IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA/cancel",
  "payment_url": null
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/bookings" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"service": 7, "start": "2026-09-21T09:00:00-03:00", "name": "Lea Roy", "email": "lea@example.com", "phone": "506 555 0100", "staff": null, "location": null, "notes": "First visit", "lang": "en", "consent": false, "policy_accepted": true}'

Back to the index

GET /api/v1/bookings

List bookings

Secret key only

Parameters

NameInTypeRequiredDescription
from query string | null No Start day or datetime (ISO).
to query string | null No End day (exclusive) or datetime (ISO).
status query string | null No pending, confirmed, cancelled, completed or no_show.
location query integer | null No Only this location's bookings (GET /locations).
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 1,
      "status": "confirmed",
      "start": "2026-10-09T09:00:00-03:00",
      "end": "2026-10-09T09:45:00-03:00",
      "service": {
        "id": 3,
        "name": "Coupe"
      },
      "staff": {
        "id": 1,
        "name": "Julie"
      },
      "location": {
        "id": 2,
        "name": "Salon Ana",
        "timezone": "America/Moncton"
      },
      "customer": {
        "name": "Marie Tremblay",
        "email": "marie.tremblay@mail.test",
        "phone": "506 555 0123"
      },
      "notes": "First visit",
      "lang": "fr",
      "source": "api",
      "created_at": "2026-10-02T23:24:52-03:00"
    }
  ],
  "next_cursor": "MTAx"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/bookings" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/bookings/{booking_id}

Read a booking

Works with a public key

Parameters

NameInTypeRequiredDescription
booking_id path integer Yes
token query string | null No Management token. Required with a public key or without a key.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 1,
  "status": "confirmed",
  "start": "2026-10-09T09:00:00-03:00",
  "end": "2026-10-09T09:45:00-03:00",
  "service": {
    "id": 3,
    "name": "Coupe"
  },
  "staff": {
    "id": 1,
    "name": "Julie"
  },
  "location": {
    "id": 2,
    "name": "Salon Ana",
    "timezone": "America/Moncton"
  },
  "customer": {
    "name": "Marie Tremblay",
    "email": "marie.tremblay@mail.test",
    "phone": "506 555 0123"
  },
  "notes": "First visit",
  "lang": "fr",
  "source": "api",
  "created_at": "2026-10-02T23:24:52-03:00"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/bookings/1" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

DELETE /api/v1/bookings/{booking_id}

Cancel a booking

Works with a public key

Parameters

NameInTypeRequiredDescription
booking_id path integer Yes
token query string | null No Management token. Required with a public key or without a key.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 101,
  "status": "cancelled"
}

Try it with curl

curl -X DELETE "https://demarkstudio.ca/api/v1/bookings/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

PATCH /api/v1/bookings/{booking_id}

Reschedule a booking or update its notes

Secret key only

Parameters

NameInTypeRequiredDescription
booking_id path integer Yes

Request body

NameTypeRequiredDescription
start string | null No New start (ISO 8601), from GET /availability/slots. Reschedules the booking: this id is cancelled and a new one is created (see the returned id). Omit to only update notes.
notes string | null No
location integer | null No Move the booking to this location (GET /locations). Works like a reschedule: with start, at that time; without it, at the same time. The staff member must work there, the service's room must be one of that location's, and the time must be free there (a staff member's bookings at every location count).

Example body

{
  "start": "2026-09-21T09:00:00-03:00",
  "notes": "Client asked to move to the morning",
  "location": null
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 101,
  "status": "confirmed",
  "start": "2026-09-21T09:00:00-03:00",
  "end": "2026-09-21T09:45:00-03:00",
  "service": {
    "id": 7,
    "name": "Haircut"
  },
  "staff": {
    "id": 3,
    "name": "Julie"
  },
  "location": {
    "id": 2,
    "name": "Downtown",
    "timezone": "America/Moncton"
  },
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "notes": "First visit",
  "lang": "en",
  "source": "api",
  "created_at": "2026-09-14T10:02:11-03:00",
  "manage_url": "https://demarkstudio.ca/citas/kR3...x9"
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/bookings/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"start": "2026-09-21T09:00:00-03:00", "notes": "Client asked to move to the morning", "location": null}'

Back to the index

GET /api/v1/availability/slots

Free start times over a date range

Secret key only

Parameters

NameInTypeRequiredDescription
service_id query integer Yes
from query string Yes First local day, YYYY-MM-DD.
to query string Yes Last local day, YYYY-MM-DD (inclusive).
staff_id query integer | null No
location query string | null No Location id or slug (GET /locations); omit for the primary location.

Responses

  • 200 Successful Response
  • 400 Bad request
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "start": "2026-09-21T09:00:00-03:00",
      "end": "2026-09-21T09:45:00-03:00",
      "staff_id": null
    }
  ],
  "timezone": "America/Moncton"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/availability/slots?service_id=1&from=2026-09-21&to=2026-09-25" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/booking-series

Create a recurring series of bookings

Secret key only

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
service integer Yes Service id.
start string Yes First booking (ISO 8601). Every date keeps this local time.
name string Yes
email string No
phone string | null No
staff integer | null No Staff id; omit to leave it unassigned.
location integer | null No Location id (GET /locations); omit for the primary location.
notes string | null No
lang string | null No en or fr; defaults to the business language.
notify boolean No Email the customer one message with every date.
recurrence Recurrencia Yes

Example body

{
  "service": 7,
  "start": "2026-10-06T10:00:00-03:00",
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "staff": null,
  "location": null,
  "notes": "Physio, every two weeks",
  "lang": "en",
  "notify": true,
  "recurrence": {
    "frequency": "every_2_weeks",
    "count": 6,
    "until": null,
    "skip_conflicts": true
  }
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 12,
  "frequency": "every_2_weeks",
  "count": 6,
  "until": null,
  "created_at": "2026-10-02T09:12:00-03:00",
  "bookings": [
    {
      "id": 1,
      "status": "confirmed",
      "start": "2026-10-09T09:00:00-03:00",
      "end": "2026-10-09T09:45:00-03:00",
      "service": {
        "id": 3,
        "name": "Coupe"
      },
      "staff": {
        "id": 1,
        "name": "Julie"
      },
      "location": {
        "id": 2,
        "name": "Salon Ana",
        "timezone": "America/Moncton"
      },
      "customer": {
        "name": "Marie Tremblay",
        "email": "marie.tremblay@mail.test",
        "phone": "506 555 0123"
      },
      "notes": "First visit",
      "lang": "fr",
      "source": "api",
      "created_at": "2026-10-02T23:24:52-03:00"
    }
  ],
  "skipped": [
    {
      "start": "2026-10-20T10:00:00-03:00",
      "reasons": [
        "staff_busy"
      ]
    }
  ]
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/booking-series" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"service": 7, "start": "2026-10-06T10:00:00-03:00", "name": "Lea Roy", "email": "lea@example.com", "phone": "506 555 0100", "staff": null, "location": null, "notes": "Physio, every two weeks", "lang": "en", "notify": true, "recurrence": {"frequency": "every_2_weeks", "count": 6, "until": null, "skip_conflicts": true}}'

Back to the index

GET /api/v1/booking-series/{series_id}

Read a recurring series

Secret key only

Parameters

NameInTypeRequiredDescription
series_id path integer Yes

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 12,
  "frequency": "every_2_weeks",
  "count": 6,
  "until": null,
  "created_at": "2026-10-02T09:12:00-03:00",
  "bookings": [
    {
      "id": 1,
      "status": "confirmed",
      "start": "2026-10-09T09:00:00-03:00",
      "end": "2026-10-09T09:45:00-03:00",
      "service": {
        "id": 3,
        "name": "Coupe"
      },
      "staff": {
        "id": 1,
        "name": "Julie"
      },
      "location": {
        "id": 2,
        "name": "Salon Ana",
        "timezone": "America/Moncton"
      },
      "customer": {
        "name": "Marie Tremblay",
        "email": "marie.tremblay@mail.test",
        "phone": "506 555 0123"
      },
      "notes": "First visit",
      "lang": "fr",
      "source": "api",
      "created_at": "2026-10-02T23:24:52-03:00"
    }
  ],
  "skipped": [
    {
      "start": "2026-10-20T10:00:00-03:00",
      "reasons": [
        "staff_busy"
      ]
    }
  ]
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/booking-series/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/booking-series/{series_id}/cancel

Cancel a series, or one booking and the following ones

Secret key only

Parameters

NameInTypeRequiredDescription
series_id path integer Yes

Request body

NameTypeRequiredDescription
from_booking integer | null No Cancel this booking and the following ones. Omit to cancel every booking of the series that has not started yet.
notify boolean No Email the customer one message with the cancelled dates.

Example body

{
  "from_booking": null,
  "notify": true
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 12,
  "cancelled": [
    41,
    42,
    43
  ]
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/booking-series/1/cancel" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"from_booking": null, "notify": true}'

Back to the index

Services

Bookable services and their free times.

POST /api/v1/services

Create a service

Secret key only

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
name_en string Yes
name_fr string No
description_en string No
description_fr string No
duration_min integer No
price number No

Example body

{
  "name_en": "Haircut",
  "name_fr": "Coupe de cheveux",
  "description_en": "Wash, cut and style.",
  "description_fr": "",
  "duration_min": 45,
  "price": 45.0
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 3,
  "name": "Coupe",
  "description": "Lavage, coupe et coiffage.",
  "duration_min": 45,
  "price": "45.00",
  "currency": "CAD",
  "staff": [
    {
      "id": 1,
      "name": "Julie"
    }
  ],
  "image_url": null
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/services" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name_en": "Haircut", "name_fr": "Coupe de cheveux", "description_en": "Wash, cut and style.", "description_fr": "", "duration_min": 45, "price": 45.0}'

Back to the index

GET /api/v1/services/{service_id}

Read a service

Secret key only

Parameters

NameInTypeRequiredDescription
service_id path integer Yes
lang query string | null No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 3,
  "name": "Coupe",
  "description": "Lavage, coupe et coiffage.",
  "duration_min": 45,
  "price": "45.00",
  "currency": "CAD",
  "staff": [
    {
      "id": 1,
      "name": "Julie"
    }
  ],
  "image_url": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/services/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

PATCH /api/v1/services/{service_id}

Update a service

Secret key only

Parameters

NameInTypeRequiredDescription
service_id path integer Yes

Request body

NameTypeRequiredDescription
name_en string | null No
name_fr string | null No
duration_min integer | null No
price number | null No
active boolean | null No
staff_ids array[integer] | null No Replaces the whole list of who can do this service.

Example body

{
  "name_en": "string",
  "name_fr": "string",
  "duration_min": 1,
  "price": 1.0,
  "active": true,
  "staff_ids": [
    1
  ]
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 3,
  "name": "Coupe",
  "description": "Lavage, coupe et coiffage.",
  "duration_min": 45,
  "price": "45.00",
  "currency": "CAD",
  "staff": [
    {
      "id": 1,
      "name": "Julie"
    }
  ],
  "image_url": null
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/services/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name_en": "string", "name_fr": "string", "duration_min": 1, "price": 1.0, "active": true, "staff_ids": [1]}'

Back to the index

Requests

Quote, contact and support requests. Each one becomes a request in the back office with its emails.

POST /api/v1/requests

Send a request

Works with a public key

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
type string No quote, contact or support.
name string Yes
email string Yes
phone string | null No
subject string | null No
message string Yes
attachment Adjunto | null No
attachments array[Adjunto] | null No Up to 3 files (together with attachment).
lang string | null No
page_url string | null No

Example body

{
  "type": "quote",
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "subject": "Kitchen renovation",
  "message": "We would like a quote for a 12 m2 kitchen.",
  "attachment": {
    "filename": "plan.pdf",
    "content_type": "application/pdf",
    "content_base64": "JVBERi0xLjQK..."
  },
  "attachments": [
    {
      "filename": "plan.pdf",
      "content_type": "application/pdf",
      "content_base64": "JVBERi0xLjQK..."
    }
  ],
  "lang": "en",
  "page_url": "https://www.example.com/contact"
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 55,
  "status": "new"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/requests" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"type": "quote", "name": "Lea Roy", "email": "lea@example.com", "phone": "506 555 0100", "subject": "Kitchen renovation", "message": "We would like a quote for a 12 m2 kitchen.", "attachment": {"filename": "plan.pdf", "content_type": "application/pdf", "content_base64": "JVBERi0xLjQK..."}, "attachments": [{"filename": "plan.pdf", "content_type": "application/pdf", "content_base64": "JVBERi0xLjQK..."}], "lang": "en", "page_url": "https://www.example.com/contact"}'

Back to the index

GET /api/v1/requests

List requests

Secret key only

Parameters

NameInTypeRequiredDescription
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 1,
      "type": "quote",
      "status": "new",
      "subject": "Wedding party, 6 people",
      "message": "Hair and make-up for six people on a Saturday morning.",
      "customer": {
        "name": "Marc Leblanc",
        "email": "marc.leblanc@mail.test",
        "phone": "506 555 0199"
      },
      "page_url": "https://www.your-salon.ca/weddings",
      "attachments": [
        {
          "id": 1,
          "filename": "inspiration.png",
          "content_type": "image/png",
          "size": 69,
          "url": "https://demarkstudio.ca/adjuntos/2/1?exp=1791253492&sig=c376ff521d8e24efa9fa372d1b46bd445c99acfc839ccd051a54d04c9dff4c3f",
          "expires_at": "2026-10-05T23:24:52-03:00"
        }
      ],
      "lang": "fr",
      "source": "api",
      "created_at": "2026-10-02T23:24:52-03:00"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/requests" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Customers

The customer list of the business. Secret key only.

GET /api/v1/customers

List or search customers

Secret key only

Parameters

NameInTypeRequiredDescription
email query string | null No Exact email.
q query string | null No Search in name, email, company, phone.
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 2,
      "name": "Marie Tremblay",
      "email": "marie.tremblay@mail.test",
      "phone": "506 555 0123",
      "company": "",
      "lang": "fr",
      "notes": "Prefers mornings",
      "marketing_consent": false,
      "created_at": "2026-10-02T23:24:52-03:00"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/customers" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/customers

Add a customer

Secret key only

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
name string Yes
email string Yes
phone string | null No
lang string | null No
consent boolean Yes Must be true: the customer agreed to be stored.
notes string | null No

Example body

{
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "lang": "fr",
  "consent": true,
  "notes": "Met at the fair"
}

Responses

  • 200 Already existed
  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 31,
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "company": "",
  "lang": "fr",
  "notes": "",
  "marketing_consent": false,
  "created_at": "2026-09-14T10:02:11-03:00",
  "created": true
}

Example response (201)

{
  "id": 31,
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": "506 555 0100",
  "company": "",
  "lang": "fr",
  "notes": "",
  "marketing_consent": false,
  "created_at": "2026-09-14T10:02:11-03:00",
  "created": true
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/customers" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Lea Roy", "email": "lea@example.com", "phone": "506 555 0100", "lang": "fr", "consent": true, "notes": "Met at the fair"}'

Back to the index

GET /api/v1/customers/{customer_id}

Read a customer

Secret key only

Parameters

NameInTypeRequiredDescription
customer_id path integer Yes

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 2,
  "name": "Marie Tremblay",
  "email": "marie.tremblay@mail.test",
  "phone": "506 555 0123",
  "company": "",
  "lang": "fr",
  "notes": "Prefers mornings",
  "marketing_consent": false,
  "created_at": "2026-10-02T23:24:52-03:00"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/customers/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

PATCH /api/v1/customers/{customer_id}

Update a customer

Secret key only

Parameters

NameInTypeRequiredDescription
customer_id path integer Yes

Request body

NameTypeRequiredDescription
name string | null No
email string | null No 422 if empty; 409 if another customer of this business already has it.
phone string | null No
lang string | null No en or fr.
notes string | null No

Example body

{
  "name": "string",
  "email": "string",
  "phone": "string",
  "lang": "string",
  "notes": "string"
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 2,
  "name": "Marie Tremblay",
  "email": "marie.tremblay@mail.test",
  "phone": "506 555 0123",
  "company": "",
  "lang": "fr",
  "notes": "Prefers mornings",
  "marketing_consent": false,
  "created_at": "2026-10-02T23:24:52-03:00"
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/customers/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "email": "string", "phone": "string", "lang": "string", "notes": "string"}'

Back to the index

Shop

Products, carts that end in the hosted checkout, and orders.

GET /api/v1/products

List the products for sale

Works with a public key

Parameters

NameInTypeRequiredDescription
lang query string | null No en or fr; without it, Accept-Language.
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 1,
      "name": "Bougie au cedre",
      "description": "",
      "price": "20.00",
      "currency": "CAD",
      "image_url": null,
      "url": "https://demarkstudio.ca/salon-ana/shop/cedar-candle?lang=fr",
      "stock": 10,
      "variants": []
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/products" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/carts

Create a cart and get its checkout link

Works with a public key

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
items array[LineaCompra] Yes
lang string | null No

Example body

{
  "items": [
    {
      "product": 9,
      "qty": 2,
      "variant": null
    }
  ],
  "lang": "en"
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "checkout_url": "https://demarkstudio.ca/salon-ana/cart?adopt=Qm9..."
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/carts" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"product": 9, "qty": 2, "variant": null}], "lang": "en"}'

Back to the index

POST /api/v1/orders

Create an order

Secret key only

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
items array[LineaCompra] Yes
customer ClientePedido Yes
delivery string | null No pickup, shipping or digital.
shipping_address string | null No
notes string | null No
lang string | null No

Example body

{
  "items": [
    {
      "product": 9,
      "qty": 2,
      "variant": null
    }
  ],
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "delivery": "pickup",
  "shipping_address": null,
  "notes": null,
  "lang": "en"
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 18,
  "number": "O-2026-0018",
  "status": "pending_payment",
  "total": "46.00",
  "currency": "CAD",
  "checkout_url": null,
  "order_url": "https://demarkstudio.ca/salon-ana/order/3f1..."
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/orders" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"product": 9, "qty": 2, "variant": null}], "customer": {"name": "Lea Roy", "email": "lea@example.com", "phone": ""}, "delivery": "pickup", "shipping_address": null, "notes": null, "lang": "en"}'

Back to the index

Products

The shop catalogue: products, options, variants and stock.

POST /api/v1/products

Create a product

Secret key only

Parameters

NameInTypeRequiredDescription
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
name_en string Yes
name_fr string | null No
description_en string | null No
description_fr string | null No
price_cents integer Yes Base price in cents, in the business currency.
type string No
stock integer | null No null = unlimited.
public boolean No false keeps it as a draft: POST /products/{id}/publish shows it.
option_groups array[OptionGroupInput] No
variants array[VariantInput] No Settings for some combinations; the others are calculated.

Example body

{
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "price_cents": 2000,
  "type": "physical",
  "stock": 10,
  "public": false,
  "option_groups": [
    {
      "id": "colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "sku": "BE-HON-12",
      "price_cents": null,
      "stock": 4,
      "active": true
    }
  ]
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/products" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name_en": "Bird's eye vinyl", "name_fr": "Vinyle oeil d'oiseau", "description_en": "Wall panel.", "description_fr": "Panneau mural.", "price_cents": 2000, "type": "physical", "stock": 10, "public": false, "option_groups": [{"id": "colour", "name_en": "Colour", "name_fr": "Couleur", "mode": "surcharge", "options": [null]}], "variants": [{"options": {"colour": "honey", "size": "12x12"}, "sku": "BE-HON-12", "price_cents": null, "stock": 4, "active": true}]}'

Back to the index

GET /api/v1/products/{product_id}

Read a product with its options and variants

Works with a public key

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
lang query string | null No en or fr; without it, Accept-Language.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/products/1" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

PATCH /api/v1/products/{product_id}

Update a product

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.

Request body

NameTypeRequiredDescription
name_en string | null No
name_fr string | null No
description_en string | null No
description_fr string | null No
price_cents integer | null No
public boolean | null No
stock integer | null No Absolute product stock; null = unlimited. Not together with stock_delta.
stock_delta integer | null No Refused with 422 insufficient_stock if it would go below 0.

Example body

{
  "name_en": "Walnut vinyl",
  "name_fr": null,
  "description_en": null,
  "description_fr": null,
  "price_cents": 2200,
  "public": true,
  "stock": null,
  "stock_delta": 5
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/products/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name_en": "Walnut vinyl", "name_fr": null, "description_en": null, "description_fr": null, "price_cents": 2200, "public": true, "stock": null, "stock_delta": 5}'

Back to the index

POST /api/v1/products/{product_id}/publish

Publish a product in the shop

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/products/1/publish" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/products/{product_id}/unpublish

Take a product out of the shop

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/products/1/unpublish" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/products/{product_id}/option-groups

Add an option group to a product

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
id string | null No Group id (slug). Omit it to derive it from name_en.
name_en string Yes
name_fr string | null No Defaults to name_en.
mode string No surcharge adds amount_cents; price sets the price. At most one price group per product.
options array[OptionInput] Yes

Example body

{
  "id": "colour",
  "name_en": "Colour",
  "name_fr": "Couleur",
  "mode": "surcharge",
  "options": [
    {
      "id": "honey",
      "name_en": "Honey maple",
      "name_fr": "Erable miel",
      "amount_cents": 300,
      "image_id": null
    }
  ]
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": "colour",
  "name": "Colour",
  "name_en": "Colour",
  "name_fr": "Couleur",
  "mode": "surcharge",
  "options": [
    {
      "id": "honey",
      "name": "Honey maple",
      "name_en": "Honey maple",
      "name_fr": "Erable miel",
      "amount_cents": null,
      "image_id": null,
      "image_url": null
    }
  ]
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/products/1/option-groups" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"id": "colour", "name_en": "Colour", "name_fr": "Couleur", "mode": "surcharge", "options": [{"id": "honey", "name_en": "Honey maple", "name_fr": "Erable miel", "amount_cents": 300, "image_id": null}]}'

Back to the index

PATCH /api/v1/products/{product_id}/option-groups/{group_id}

Update an option group

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
group_id path string Yes Option group id.

Request body

NameTypeRequiredDescription
name_en string | null No
name_fr string | null No
mode string | null No
options array[OptionInput] | null No Replaces the whole list. Variants of option ids that disappear are dropped; the others keep sku, price and stock.

Example body

{
  "name_en": "Finish",
  "name_fr": "Fini",
  "mode": "price",
  "options": [
    {
      "id": "honey",
      "name_en": "Honey maple",
      "name_fr": "Erable miel",
      "amount_cents": 300,
      "image_id": null
    }
  ]
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": "colour",
  "name": "Colour",
  "name_en": "Colour",
  "name_fr": "Couleur",
  "mode": "surcharge",
  "options": [
    {
      "id": "honey",
      "name": "Honey maple",
      "name_en": "Honey maple",
      "name_fr": "Erable miel",
      "amount_cents": null,
      "image_id": null,
      "image_url": null
    }
  ]
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/products/1/option-groups/string" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name_en": "Finish", "name_fr": "Fini", "mode": "price", "options": [{"id": "honey", "name_en": "Honey maple", "name_fr": "Erable miel", "amount_cents": 300, "image_id": null}]}'

Back to the index

DELETE /api/v1/products/{product_id}/option-groups/{group_id}

Remove an option group

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
group_id path string Yes Option group id.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 9,
  "slug": "birds-eye-vinyl",
  "name": "Bird's eye vinyl",
  "name_en": "Bird's eye vinyl",
  "name_fr": "Vinyle oeil d'oiseau",
  "description": "Wall panel.",
  "description_en": "Wall panel.",
  "description_fr": "Panneau mural.",
  "type": "physical",
  "price_cents": 2000,
  "currency": "CAD",
  "stock": 10,
  "public": true,
  "sold_out": false,
  "url": "https://demarkstudio.ca/salon-ana/shop/birds-eye-vinyl",
  "images": [
    {
      "id": 41,
      "url": "https://demarkstudio.ca/m/12/41.webp",
      "thumbnail_url": "https://demarkstudio.ca/m/12/41_t.webp",
      "alt": ""
    }
  ],
  "option_groups": [
    {
      "id": "colour",
      "name": "Colour",
      "name_en": "Colour",
      "name_fr": "Couleur",
      "mode": "surcharge",
      "options": [
        null
      ]
    }
  ],
  "variants": [
    {
      "id": "honey|12x12",
      "sku": "BE-HON-12",
      "price_cents": 1500,
      "stock": 4,
      "shared_stock": false,
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "active": true,
      "available": true
    }
  ],
  "policies": {
    "lang": "en",
    "shipping": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    },
    "returns": {
      "text": "We ship across New Brunswick for $12.00.",
      "paragraphs": [
        "We ship across New Brunswick for $12.00."
      ],
      "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
    }
  },
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X DELETE "https://demarkstudio.ca/api/v1/products/1/option-groups/string" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/products/{product_id}/variants

Set up a variant

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
options object Yes One option per group: {group id: option id}.
sku string | null No
price_cents integer | null No Fixed price; null = calculated from the product price and the option amounts.
stock integer | null No Own stock; null = shares the product stock.
active boolean No An inactive variant is kept but cannot be bought.

Example body

{
  "options": {
    "colour": "honey",
    "size": "12x12"
  },
  "sku": "BE-HON-12",
  "price_cents": null,
  "stock": 4,
  "active": true
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": "honey|12x12",
  "sku": "BE-HON-12",
  "price_cents": 1500,
  "stock": 4,
  "shared_stock": false,
  "options": {
    "colour": "honey",
    "size": "12x12"
  },
  "active": true,
  "available": true
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/products/1/variants" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"options": {"colour": "honey", "size": "12x12"}, "sku": "BE-HON-12", "price_cents": null, "stock": 4, "active": true}'

Back to the index

PATCH /api/v1/products/{product_id}/variants/{variant_id}

Update a variant (price, stock, active)

Secret key only

Parameters

NameInTypeRequiredDescription
product_id path integer Yes Product id.
variant_id path string Yes Variant id, '|' URL-encoded as %7C.

Request body

NameTypeRequiredDescription
sku string | null No
price_cents integer | null No null = calculated.
stock integer | null No Absolute own stock; null = shares the product stock. Not together with stock_delta.
stock_delta integer | null No Adds to the stock the variant uses (its own or the shared one). Refused with 422 insufficient_stock if it would go below 0.
active boolean | null No

Example body

{
  "sku": "BE-HON-12",
  "price_cents": 2500,
  "stock": null,
  "stock_delta": -2,
  "active": false
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": "honey|12x12",
  "sku": "BE-HON-12",
  "price_cents": 1500,
  "stock": 4,
  "shared_stock": false,
  "options": {
    "colour": "honey",
    "size": "12x12"
  },
  "active": true,
  "available": true
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/products/1/variants/string" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"sku": "BE-HON-12", "price_cents": 2500, "stock": null, "stock_delta": -2, "active": false}'

Back to the index

GET /api/v1/shop/policies

Read the shipping and returns policies

Works with a public key

Parameters

NameInTypeRequiredDescription
lang query string | null No en or fr.

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "lang": "en",
  "shipping": {
    "text": "We ship across New Brunswick for $12.00.",
    "paragraphs": [
      "We ship across New Brunswick for $12.00."
    ],
    "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
  },
  "returns": {
    "text": "We ship across New Brunswick for $12.00.",
    "paragraphs": [
      "We ship across New Brunswick for $12.00."
    ],
    "url": "https://demarkstudio.ca/salon-ana/shop/shipping"
  }
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/shop/policies" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Orders

Shop orders and their status.

GET /api/v1/orders

List orders

Secret key only

Parameters

NameInTypeRequiredDescription
status query string | null No Only orders in this status (pending_payment, paid, preparing, ready, shipped, delivered, cancelled, refunded, paid_after_cancel).
since query string | null No Created at or after this ISO date or datetime.
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 18,
      "number": "O-2026-0018",
      "status": "pending_payment",
      "customer": {
        "name": null,
        "email": null,
        "phone": null
      },
      "delivery": "pickup",
      "subtotal": "40.00",
      "shipping": "0.00",
      "tax": "6.00",
      "total": "46.00",
      "currency": "CAD",
      "items": [
        null
      ],
      "created_at": "2026-09-14T10:02:11-03:00",
      "customer_id": 31,
      "subtotal_cents": 4000,
      "shipping_cents": 0,
      "tax_cents": 600,
      "taxes": [
        null
      ],
      "total_cents": 4600,
      "lines": [
        null
      ],
      "allowed_transitions": [
        "paid",
        "cancelled"
      ]
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/orders" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/orders/{order_id}

Read an order

Secret key only

Parameters

NameInTypeRequiredDescription
order_id path integer Yes

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 18,
  "number": "O-2026-0018",
  "status": "pending_payment",
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "delivery": "pickup",
  "subtotal": "40.00",
  "shipping": "0.00",
  "tax": "6.00",
  "total": "46.00",
  "currency": "CAD",
  "items": [
    {
      "product_id": 9,
      "name": "Cedar candle",
      "price": "20.00",
      "qty": 2
    }
  ],
  "created_at": "2026-09-14T10:02:11-03:00",
  "customer_id": 31,
  "subtotal_cents": 4000,
  "shipping_cents": 0,
  "tax_cents": 600,
  "taxes": [
    {
      "name": "GST",
      "rate": 5.0,
      "registration_number": "123456789 RT0001",
      "amount_cents": 300
    }
  ],
  "total_cents": 4600,
  "lines": [
    {
      "product_id": 9,
      "variant_id": "honey|12x12",
      "name": "Bird's eye vinyl",
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "options_label": "Colour: Honey maple, Size: 12 x 12 in",
      "qty": 2,
      "unit_price_cents": 1500,
      "total_cents": 3000
    }
  ],
  "allowed_transitions": [
    "paid",
    "cancelled"
  ],
  "history": [
    {
      "status": "paid",
      "at": "2026-09-14T10:05:00-03:00",
      "source": "system"
    }
  ]
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/orders/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

PATCH /api/v1/orders/{order_id}/status

Change the status of an order

Secret key only

Parameters

NameInTypeRequiredDescription
order_id path integer Yes

Request body

NameTypeRequiredDescription
status string Yes preparing, ready, shipped, delivered, cancelled, refunded, or paid (only with manual_payment).
manual_payment boolean No Required (true) to mark an order paid: payment taken outside DeMark. Audited.
tracking string | null No Tracking number for shipped.

Example body

{
  "status": "shipped",
  "manual_payment": false,
  "tracking": "CA123456789"
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, scope or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 18,
  "number": "O-2026-0018",
  "status": "pending_payment",
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "delivery": "pickup",
  "subtotal": "40.00",
  "shipping": "0.00",
  "tax": "6.00",
  "total": "46.00",
  "currency": "CAD",
  "items": [
    {
      "product_id": 9,
      "name": "Cedar candle",
      "price": "20.00",
      "qty": 2
    }
  ],
  "created_at": "2026-09-14T10:02:11-03:00",
  "customer_id": 31,
  "subtotal_cents": 4000,
  "shipping_cents": 0,
  "tax_cents": 600,
  "taxes": [
    {
      "name": "GST",
      "rate": 5.0,
      "registration_number": "123456789 RT0001",
      "amount_cents": 300
    }
  ],
  "total_cents": 4600,
  "lines": [
    {
      "product_id": 9,
      "variant_id": "honey|12x12",
      "name": "Bird's eye vinyl",
      "options": {
        "colour": "honey",
        "size": "12x12"
      },
      "options_label": "Colour: Honey maple, Size: 12 x 12 in",
      "qty": 2,
      "unit_price_cents": 1500,
      "total_cents": 3000
    }
  ],
  "allowed_transitions": [
    "paid",
    "cancelled"
  ],
  "history": [
    {
      "status": "paid",
      "at": "2026-09-14T10:05:00-03:00",
      "source": "system"
    }
  ]
}

Try it with curl

curl -X PATCH "https://demarkstudio.ca/api/v1/orders/1/status" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"status": "shipped", "manual_payment": false, "tracking": "CA123456789"}'

Back to the index

Quotes

Quotes sent to customers.

GET /api/v1/quotes

List quotes

Secret key only

Parameters

NameInTypeRequiredDescription
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 14,
      "number": "Q-2026-0014",
      "status": "sent",
      "title": "Kitchen renovation",
      "customer": {
        "name": null,
        "email": null,
        "phone": null
      },
      "currency": "CAD",
      "base": "1200.00",
      "tax": "180.00",
      "taxes": [
        null
      ],
      "total": "1380.00",
      "valid_until": "2026-10-15",
      "lines": [
        null
      ],
      "public_url": null,
      "created_at": "2026-09-14T10:02:11-03:00",
      "updated_at": "2026-09-14T10:02:11-03:00"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/quotes" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/quotes/{quote_id}

Read a quote

Secret key only

Parameters

NameInTypeRequiredDescription
quote_id path integer Yes

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 14,
  "number": "Q-2026-0014",
  "status": "sent",
  "title": "Kitchen renovation",
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "currency": "CAD",
  "base": "1200.00",
  "tax": "180.00",
  "taxes": [
    {
      "name": "GST",
      "rate": 5.0,
      "registration_number": "123456789 RT0001",
      "amount": "60.00"
    }
  ],
  "total": "1380.00",
  "valid_until": "2026-10-15",
  "lines": [
    {
      "concept": "Kitchen cabinets",
      "detail": "",
      "qty": 1.0,
      "unit_price": "1200.00",
      "amount": "1200.00",
      "tax_exempt": false,
      "optional": false
    }
  ],
  "public_url": null,
  "created_at": "2026-09-14T10:02:11-03:00",
  "updated_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/quotes/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Invoices

Invoices and their payments.

GET /api/v1/invoices

List invoices

Secret key only

Parameters

NameInTypeRequiredDescription
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 42,
      "number": "2026-0042",
      "status": "issued",
      "customer": {
        "name": null,
        "email": null,
        "phone": null
      },
      "currency": "CAD",
      "base": "1200.00",
      "tax": "180.00",
      "taxes": [
        null
      ],
      "total": "1380.00",
      "due_date": "2026-10-14",
      "paid_at": null,
      "lines": [
        null
      ],
      "payments": [
        null
      ],
      "created_at": "2026-09-14T10:02:11-03:00"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/invoices" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/invoices/{invoice_id}

Read an invoice

Secret key only

Parameters

NameInTypeRequiredDescription
invoice_id path integer Yes

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 42,
  "number": "2026-0042",
  "status": "issued",
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "currency": "CAD",
  "base": "1200.00",
  "tax": "180.00",
  "taxes": [
    {
      "name": "GST",
      "rate": 5.0,
      "registration_number": "123456789 RT0001",
      "amount": "60.00"
    }
  ],
  "total": "1380.00",
  "due_date": "2026-10-14",
  "paid_at": null,
  "lines": [
    {
      "concept": "Kitchen cabinets",
      "detail": "",
      "qty": 1.0,
      "unit_price": "1200.00",
      "amount": "1200.00",
      "tax_exempt": false
    }
  ],
  "payments": [
    {
      "provider": "stripe",
      "amount": "1380.00",
      "currency": "CAD",
      "status": "paid",
      "reference": "pi_3Nk...",
      "created_at": "2026-09-14T10:02:11-03:00"
    }
  ],
  "created_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/invoices/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/invoices/{invoice_id}/mark-paid

Mark an invoice as paid

Secret key only

Parameters

NameInTypeRequiredDescription
invoice_id path integer Yes
Idempotency-Key header string No Retry safely: the first 2xx response is kept 24 hours and replayed with Idempotent-Replayed: true. Same key with a different body: 422 idempotency_key_reused.

Request body

NameTypeRequiredDescription
method string Yes
reference string | null No
paid_at string | null No ISO 8601; defaults to now.

Example body

{
  "method": "e-transfer",
  "reference": "ET-88213",
  "paid_at": null
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 409 Conflict
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 42,
  "number": "2026-0042",
  "status": "issued",
  "customer": {
    "name": "Lea Roy",
    "email": "lea@example.com",
    "phone": ""
  },
  "currency": "CAD",
  "base": "1200.00",
  "tax": "180.00",
  "taxes": [
    {
      "name": "GST",
      "rate": 5.0,
      "registration_number": "123456789 RT0001",
      "amount": "60.00"
    }
  ],
  "total": "1380.00",
  "due_date": "2026-10-14",
  "paid_at": null,
  "lines": [
    {
      "concept": "Kitchen cabinets",
      "detail": "",
      "qty": 1.0,
      "unit_price": "1200.00",
      "amount": "1200.00",
      "tax_exempt": false
    }
  ],
  "payments": [
    {
      "provider": "stripe",
      "amount": "1380.00",
      "currency": "CAD",
      "status": "paid",
      "reference": "pi_3Nk...",
      "created_at": "2026-09-14T10:02:11-03:00"
    }
  ],
  "created_at": "2026-09-14T10:02:11-03:00"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/invoices/1/mark-paid" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"method": "e-transfer", "reference": "ET-88213", "paid_at": null}'

Back to the index

Projects

Project portfolio entries with their photos.

GET /api/v1/works

List projects

Secret key only

Parameters

NameInTypeRequiredDescription
lang query string | null No
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": 3,
      "slug": "kitchen-in-maple",
      "url": "https://demarkstudio.ca/salon-ana/work/kitchen-in-maple",
      "title": "Kitchen in maple",
      "summary": "A full kitchen remodel in solid maple.",
      "detail": [
        "We replaced the cabinets..."
      ],
      "materials": "Solid maple, quartz counters",
      "measurements": "12 m2",
      "location": "Moncton, NB",
      "delivered": "March 2026",
      "delivered_iso": "2026-03",
      "duration": "3 weeks",
      "published": true,
      "cover": null,
      "photos": [
        null
      ]
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/works" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

GET /api/v1/works/{work_id}

Read a project

Secret key only

Parameters

NameInTypeRequiredDescription
work_id path integer Yes
lang query string | null No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "id": 3,
  "slug": "kitchen-in-maple",
  "url": "https://demarkstudio.ca/salon-ana/work/kitchen-in-maple",
  "title": "Kitchen in maple",
  "summary": "A full kitchen remodel in solid maple.",
  "detail": [
    "We replaced the cabinets..."
  ],
  "materials": "Solid maple, quartz counters",
  "measurements": "12 m2",
  "location": "Moncton, NB",
  "delivered": "March 2026",
  "delivered_iso": "2026-03",
  "duration": "3 weeks",
  "published": true,
  "cover": {
    "id": 9,
    "url": "https://demarkstudio.ca/m/12/40.webp",
    "caption": "Before",
    "alt": "Kitchen before the renovation",
    "width": 1600,
    "height": 1200
  },
  "photos": [
    {
      "id": 9,
      "url": "https://demarkstudio.ca/m/12/40.webp",
      "caption": "Before",
      "alt": "Kitchen before the renovation",
      "width": 1600,
      "height": 1200
    }
  ]
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/works/1" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Events

Upcoming event dates and registrations.

GET /api/v1/events

List upcoming event dates

Works with a public key

Parameters

NameInTypeRequiredDescription
from query string | null No First day, YYYY-MM-DD (today by default).
lang query string | null No en or fr; without it, Accept-Language.
cursor query string | null No
limit query integer No

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "id": "1-2026-10-14",
      "event_id": 1,
      "title": "Atelier de soins capillaires",
      "description": "Deux heures pour apprendre une routine qui dure.",
      "start": "2026-10-14T18:00:00-03:00",
      "end": "2026-10-14T20:00:00-03:00",
      "seats_left": 12,
      "price": "25.00",
      "currency": "CAD",
      "url": "https://demarkstudio.ca/salon-ana/events/hair-care-workshop?ocurrencia=2026-10-14&lang=fr"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/events" \
  -H "Authorization: Bearer dmk_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

POST /api/v1/events/{event_id}/registrations

Register for an event date

Works with a public key

Parameters

NameInTypeRequiredDescription
event_id path string Yes

Request body

NameTypeRequiredDescription
occurrence string Yes Occurrence id from /events, or its date YYYY-MM-DD.
name string Yes
email string Yes
phone string | null No
seats integer No
lang string | null No

Example body

{
  "occurrence": "4-2026-09-20",
  "name": "Lea Roy",
  "email": "lea@example.com",
  "phone": null,
  "seats": 2,
  "lang": "en"
}

Responses

  • 201 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (201)

{
  "id": 77,
  "status": "confirmed"
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/events/string/registrations" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"occurrence": "4-2026-09-20", "name": "Lea Roy", "email": "lea@example.com", "phone": null, "seats": 2, "lang": "en"}'

Back to the index

Chat

One turn of the triage chat, the same one the chat widget uses.

POST /api/v1/chat

Send one chat turn

Works with a public key

Request body

NameTypeRequiredDescription
session string | null No Omit on the first turn; send back the one you get.
message string No
action string | null No An option id from the previous reply.
fields object | null No Form values: nombre, email, detalle.
lang string | null No
page_url string | null No

Example body

{
  "session": "s8f7a6d5c4b3a2",
  "message": "Do you open on Sundays?",
  "action": null,
  "fields": null,
  "lang": "en",
  "page_url": "https://www.example.com/"
}

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 404 Not found
  • 422 Validation error
  • 429 Rate limited

Example response (200)

{
  "session": "s8f7a6d5c4b3a2",
  "reply": "We are open Sunday from 10 to 4.",
  "options": [
    {
      "id": "reservar",
      "label": "Book an appointment"
    }
  ],
  "form": [
    {
      "name": "email",
      "type": "email",
      "label": "Email",
      "value": ""
    }
  ],
  "state": "saludo",
  "handoff": false
}

Try it with curl

curl -X POST "https://demarkstudio.ca/api/v1/chat" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"session": "s8f7a6d5c4b3a2", "message": "Do you open on Sundays?", "action": null, "fields": null, "lang": "en", "page_url": "https://www.example.com/"}'

Back to the index

Webhooks

The events DeMark can send to your server.

GET /api/v1/webhooks/events

List webhook event names

Secret key only

Responses

  • 200 Successful Response
  • 401 Missing, invalid or revoked API key
  • 403 Forbidden: key type, permission, origin or plan
  • 429 Rate limited

Example response (200)

{
  "data": [
    {
      "name": "booking.created"
    }
  ],
  "next_cursor": null
}

Try it with curl

curl -X GET "https://demarkstudio.ca/api/v1/webhooks/events" \
  -H "Authorization: Bearer dmk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Back to the index

Models

The objects the API returns and the webhooks send. Dates carry your business offset, money is a decimal string and every value is the public English one.

Booking

Fields of Booking
NameTypeRequiredDescription
id integer Yes
status string Yes pending, confirmed, cancelled, completed or no_show.
start string Yes ISO 8601 with the business offset.
end string Yes
service ServiceRef | null Yes null when the service was deleted.
staff StaffRef | null Yes null when anyone can do it.
location LocationRef | null Yes Where it takes place (GET /locations). start and end carry the business offset; the location time zone is here.
customer CustomerRef Yes
notes string | null Yes
lang string | null Yes en or fr: the language of the booking and its emails.
source string | null Yes Where it was made: web, api, panel, portal...
created_at string | null Yes

Real example

{
  "id": 1,
  "status": "confirmed",
  "start": "2026-10-09T09:00:00-03:00",
  "end": "2026-10-09T09:45:00-03:00",
  "service": {
    "id": 3,
    "name": "Coupe"
  },
  "staff": {
    "id": 1,
    "name": "Julie"
  },
  "location": {
    "id": 2,
    "name": "Salon Ana",
    "timezone": "America/Moncton"
  },
  "customer": {
    "name": "Marie Tremblay",
    "email": "marie.tremblay@mail.test",
    "phone": "506 555 0123"
  },
  "notes": "First visit",
  "lang": "fr",
  "source": "api",
  "created_at": "2026-10-02T23:24:52-03:00"
}

NewBooking

Fields of NewBooking
NameTypeRequiredDescription
id integer Yes
status string Yes pending, confirmed, cancelled, completed or no_show.
start string Yes ISO 8601 with the business offset.
end string Yes
service ServiceRef | null Yes null when the service was deleted.
staff StaffRef | null Yes null when anyone can do it.
location LocationRef | null Yes Where it takes place (GET /locations). start and end carry the business offset; the location time zone is here.
customer CustomerRef Yes
notes string | null Yes
lang string | null Yes en or fr: the language of the booking and its emails.
source string | null Yes Where it was made: web, api, panel, portal...
created_at string | null Yes
token string Yes Management token. Shown once: store it to read or cancel the booking.
manage_url string Yes Page where the visitor manages the booking.
cancel_url string Yes Page where the visitor confirms the cancellation (GET, no key). Servers cancel with DELETE /bookings/{id}?token=... or with a secret key.
payment_url string | null Yes Deposit or card checkout, when the service asks for it.

Real example

{
  "id": 1,
  "status": "confirmed",
  "start": "2026-10-09T09:00:00-03:00",
  "end": "2026-10-09T09:45:00-03:00",
  "service": {
    "id": 3,
    "name": "Coupe"
  },
  "staff": {
    "id": 1,
    "name": "Julie"
  },
  "location": {
    "id": 2,
    "name": "Salon Ana",
    "timezone": "America/Moncton"
  },
  "customer": {
    "name": "Marie Tremblay",
    "email": "marie.tremblay@mail.test",
    "phone": "506 555 0123"
  },
  "notes": "First visit",
  "lang": "fr",
  "source": "api",
  "created_at": "2026-10-02T23:24:52-03:00",
  "token": "IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA",
  "manage_url": "https://demarkstudio.ca/citas/IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA",
  "cancel_url": "https://demarkstudio.ca/citas/IfnZPtmgcfpabtYFc_UU4AxdOWc1MHHGjdUU-jrbLAA/cancel",
  "payment_url": null
}

Request

Fields of Request
NameTypeRequiredDescription
id integer Yes
type string Yes quote, contact or support (also lead, booking, question, event, table_booking, other for requests that came from other channels).
status string Yes new, open, waiting_customer, resolved or closed.
subject string Yes
message string Yes What the customer wrote, without page or file lines.
customer CustomerRef Yes
page_url string | null Yes Page the request was sent from, when known.
attachments array[Attachment] Yes Empty when there are none.
lang string | null Yes
source string Yes api, web, site, chat, email...
created_at string Yes

Real example

{
  "id": 1,
  "type": "quote",
  "status": "new",
  "subject": "Wedding party, 6 people",
  "message": "Hair and make-up for six people on a Saturday morning.",
  "customer": {
    "name": "Marc Leblanc",
    "email": "marc.leblanc@mail.test",
    "phone": "506 555 0199"
  },
  "page_url": "https://www.your-salon.ca/weddings",
  "attachments": [
    {
      "id": 1,
      "filename": "inspiration.png",
      "content_type": "image/png",
      "size": 69,
      "url": "https://demarkstudio.ca/adjuntos/2/1?exp=1791253492&sig=c376ff521d8e24efa9fa372d1b46bd445c99acfc839ccd051a54d04c9dff4c3f",
      "expires_at": "2026-10-05T23:24:52-03:00"
    }
  ],
  "lang": "fr",
  "source": "api",
  "created_at": "2026-10-02T23:24:52-03:00"
}

Attachment

Fields of Attachment
NameTypeRequiredDescription
id integer Yes
filename string Yes
content_type string Yes
size integer Yes Bytes.
url string Yes Signed download link; no key needed until expires_at.
expires_at string Yes ISO 8601; ask for the request again to get a fresh url.

Real example

{
  "id": 1,
  "filename": "inspiration.png",
  "content_type": "image/png",
  "size": 69,
  "url": "https://demarkstudio.ca/adjuntos/2/1?exp=1791253492&sig=c376ff521d8e24efa9fa372d1b46bd445c99acfc839ccd051a54d04c9dff4c3f",
  "expires_at": "2026-10-05T23:24:52-03:00"
}

Customer

Fields of Customer
NameTypeRequiredDescription
id integer Yes
name string Yes
email string Yes
phone string Yes
company string Yes
lang string Yes
notes string Yes
marketing_consent boolean Yes
created_at string | null Yes

Real example

{
  "id": 2,
  "name": "Marie Tremblay",
  "email": "marie.tremblay@mail.test",
  "phone": "506 555 0123",
  "company": "",
  "lang": "fr",
  "notes": "Prefers mornings",
  "marketing_consent": false,
  "created_at": "2026-10-02T23:24:52-03:00"
}

Order

Fields of Order
NameTypeRequiredDescription
id integer Yes
number string Yes
status string Yes pending_payment, paid, preparing, ready, shipped, delivered, cancelled, refunded or paid_after_cancel.
customer CustomerRef Yes
delivery string Yes pickup, shipping, digital or empty.
subtotal string Yes
shipping string Yes
tax string Yes
total string Yes
currency string Yes
items array[OrderItem] Yes
created_at string Yes

Real example

{
  "id": 1,
  "number": "O-2026-0001",
  "status": "paid",
  "customer": {
    "name": "Lea Roy",
    "email": "lea.roy@mail.test",
    "phone": ""
  },
  "delivery": "pickup",
  "subtotal": "40.00",
  "shipping": "0.00",
  "tax": "0.00",
  "total": "40.00",
  "currency": "CAD",
  "items": [
    {
      "product_id": 1,
      "name": "Cedar candle",
      "price": "20.00",
      "qty": 2
    }
  ],
  "created_at": "2026-10-02T23:24:52-03:00"
}

Event

Fields of Event
NameTypeRequiredDescription
id string Yes Occurrence id: <event_id>-<YYYY-MM-DD>.
event_id integer Yes
title string Yes In the requested language.
description string Yes
start string Yes
end string Yes
seats_left integer | null Yes null = no capacity limit.
price string | null Yes
currency string Yes
url string Yes Event page in the requested language.

Real example

{
  "id": "1-2026-10-14",
  "event_id": 1,
  "title": "Atelier de soins capillaires",
  "description": "Deux heures pour apprendre une routine qui dure.",
  "start": "2026-10-14T18:00:00-03:00",
  "end": "2026-10-14T20:00:00-03:00",
  "seats_left": 12,
  "price": "25.00",
  "currency": "CAD",
  "url": "https://demarkstudio.ca/salon-ana/events/hair-care-workshop?ocurrencia=2026-10-14&lang=fr"
}

Service

Fields of Service
NameTypeRequiredDescription
id integer Yes
name string Yes In the requested language.
description string Yes
duration_min integer Yes
price string | null Yes Decimal as string, or null when the service has no price.
currency string Yes
staff array[StaffRef] Yes Who can do it; empty when the business has no team.
image_url string | null Yes

Real example

{
  "id": 3,
  "name": "Coupe",
  "description": "Lavage, coupe et coiffage.",
  "duration_min": 45,
  "price": "45.00",
  "currency": "CAD",
  "staff": [
    {
      "id": 1,
      "name": "Julie"
    }
  ],
  "image_url": null
}

Product

Fields of Product
NameTypeRequiredDescription
id integer Yes
name string Yes
description string Yes
price string | null Yes
currency string Yes
image_url string | null Yes
url string Yes Product page in the requested language.
stock integer | null Yes null = unlimited.
variants array[object] Yes Active variants (id, sku, price_cents, stock, options, active, available); empty when the product has no options.

Real example

{
  "id": 1,
  "name": "Bougie au cedre",
  "description": "",
  "price": "20.00",
  "currency": "CAD",
  "image_url": null,
  "url": "https://demarkstudio.ca/salon-ana/shop/cedar-candle?lang=fr",
  "stock": 10,
  "variants": []
}

Webhook bodies

Each body below was generated by DeMark itself from a test booking, request, order and customer. data is the same model the REST API returns, so one class in your code reads both.

Version 1 only adds fields: ignore the ones you do not know. created is kept as a UTC alias of created_at for older receivers.

POST booking.created

A booking is confirmed, whether it came from a widget, the API, your booking page or the back office. data is a WebhookBooking

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.567Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "created_at": "2026-10-02T23:24:52-03:00",
    "customer": {
      "email": "marie.tremblay@mail.test",
      "name": "Marie Tremblay",
      "phone": "506 555 0123"
    },
    "end": "2026-10-09T09:45:00-03:00",
    "id": 1,
    "lang": "fr",
    "location": {
      "id": 2,
      "name": "Salon Ana",
      "timezone": "America/Moncton"
    },
    "manage_url": "https://demarkstudio.ca/citas/c2-1-12f82226c4db44f694f2343dd1942d94",
    "notes": "First visit",
    "service": {
      "id": 3,
      "name": "Coupe"
    },
    "source": "api",
    "staff": {
      "id": 1,
      "name": "Julie"
    },
    "start": "2026-10-09T09:00:00-03:00",
    "status": "confirmed"
  },
  "id": "evt_2",
  "type": "booking.created"
}

Back to the index

POST booking.cancelled

A booking is cancelled, by the customer or by you. data is a WebhookBooking

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.609Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "created_at": "2026-10-02T23:24:52-03:00",
    "customer": {
      "email": "luc.arsenault@mail.test",
      "name": "Luc Arsenault",
      "phone": ""
    },
    "end": "2026-10-09T10:45:00-03:00",
    "id": 2,
    "lang": "en",
    "location": {
      "id": 2,
      "name": "Salon Ana",
      "timezone": "America/Moncton"
    },
    "manage_url": "https://demarkstudio.ca/citas/c2-2-dcd2cbc1d9ca3db5593cd8364bf6349b",
    "notes": "",
    "service": {
      "id": 3,
      "name": "Haircut"
    },
    "source": "api",
    "staff": {
      "id": 1,
      "name": "Julie"
    },
    "start": "2026-10-09T10:00:00-03:00",
    "status": "cancelled"
  },
  "id": "evt_5",
  "type": "booking.cancelled"
}

Back to the index

POST request.created

A quote, contact or support request arrives. data is a Request

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.624Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "attachments": [
      {
        "content_type": "image/png",
        "expires_at": "2026-10-05T23:24:52-03:00",
        "filename": "inspiration.png",
        "id": 1,
        "size": 69,
        "url": "https://demarkstudio.ca/adjuntos/2/1?exp=1791253492&sig=c376ff521d8e24efa9fa372d1b46bd445c99acfc839ccd051a54d04c9dff4c3f"
      }
    ],
    "created_at": "2026-10-02T23:24:52-03:00",
    "customer": {
      "email": "marc.leblanc@mail.test",
      "name": "Marc Leblanc",
      "phone": "506 555 0199"
    },
    "id": 1,
    "lang": "fr",
    "message": "Hair and make-up for six people on a Saturday morning.",
    "page_url": "https://www.your-salon.ca/weddings",
    "source": "api",
    "status": "new",
    "subject": "Wedding party, 6 people",
    "type": "quote"
  },
  "id": "evt_7",
  "type": "request.created"
}

Back to the index

POST order.paid

An order is paid. data is a Order

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.652Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "created_at": "2026-10-02T23:24:52-03:00",
    "currency": "CAD",
    "customer": {
      "email": "lea.roy@mail.test",
      "name": "Lea Roy",
      "phone": ""
    },
    "delivery": "pickup",
    "id": 1,
    "items": [
      {
        "name": "Cedar candle",
        "price": "20.00",
        "product_id": 1,
        "qty": 2
      }
    ],
    "number": "O-2026-0001",
    "shipping": "0.00",
    "status": "paid",
    "subtotal": "40.00",
    "tax": "0.00",
    "total": "40.00"
  },
  "id": "evt_10",
  "type": "order.paid"
}

Back to the index

POST customer.created

A new customer is added. data is a Customer

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.552Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "company": "",
    "created_at": "2026-10-02T23:24:52-03:00",
    "email": "marie.tremblay@mail.test",
    "id": 2,
    "lang": "fr",
    "marketing_consent": false,
    "name": "Marie Tremblay",
    "notes": "Prefers mornings",
    "phone": "506 555 0123"
  },
  "id": "evt_1",
  "type": "customer.created"
}

Back to the index

POST order.created

An order is placed, before it is paid. data is a Order

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.645Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "created_at": "2026-10-02T23:24:52-03:00",
    "currency": "CAD",
    "customer": {
      "email": "lea.roy@mail.test",
      "name": "Lea Roy",
      "phone": ""
    },
    "delivery": "pickup",
    "id": 1,
    "items": [
      {
        "name": "Cedar candle",
        "price": "20.00",
        "product_id": 1,
        "qty": 2
      }
    ],
    "number": "O-2026-0001",
    "shipping": "0.00",
    "status": "pending_payment",
    "subtotal": "40.00",
    "tax": "0.00",
    "total": "40.00"
  },
  "id": "evt_9",
  "type": "order.created"
}

Back to the index

POST invoice.paid

An invoice is marked as paid. data is a WebhookInvoice

{
  "business": {
    "id": 2,
    "slug": "salon-ana"
  },
  "created": "2026-10-03T02:24:52.731Z",
  "created_at": "2026-10-02T23:24:52-03:00",
  "data": {
    "base": "510.00",
    "created_at": "2026-10-02T23:24:52-03:00",
    "currency": "CAD",
    "customer": {
      "email": "marie.tremblay@mail.test",
      "name": "Marie Tremblay",
      "phone": null
    },
    "due_date": "2026-11-01",
    "id": 1,
    "lines": [
      {
        "amount": "510.00",
        "concept": "Wedding party",
        "detail": "Hair and make-up, 6 people",
        "qty": 6.0,
        "tax_exempt": false,
        "unit_price": "85.00"
      }
    ],
    "number": "2026-0001",
    "paid_at": "2026-10-02T23:24:52-03:00",
    "status": "paid",
    "tax": "76.37",
    "taxes": [
      {
        "amount": "25.50",
        "name": "GST",
        "rate": 5.0,
        "registration_number": "123456789 RT0001"
      },
      {
        "amount": "50.87",
        "name": "QST",
        "rate": 9.975,
        "registration_number": "1234567890 TQ0001"
      }
    ],
    "total": "586.37"
  },
  "id": "evt_11",
  "type": "invoice.paid"
}

Back to the index

Cookies

We use one cookie to see how you found us (an email, a search or an ad) and count the sign-ups each one brings. If you accept, it also keeps an ad click ID for 90 days; if you decline, only the campaign name. Privacy policy