Developers

Learn

Recipes — common workflows

The reference documents one endpoint at a time. Real features chain several together. Here are three end-to-end flows — pulled from production-style examples — that show how one call's output feeds the next.

ℹ️ Written for PatientNow Essentials (PNE)

These workflows lean on PNE resources — customers, products, and the appointment ShowAvailable search — that the VISH subset doesn't expose. The auth, paging, ID and date conventions are identical on both APIs, so the shapes below transfer; just swap in the resources your API actually has. VISH-specific differences are flagged inline.

The setup these recipes share

Every snippet below assumes you've already built an authenticated client, exactly as in the PNE quickstart — so we don't repeat auth in each step. In Python that's a requests.Session that carries your gateway apikey on every call and sets the Basic Authorization header:

import base64, requests

base = "https://api.envisiongo.com/api/v1"
s = requests.Session()
s.params = {"apikey": "YOUR_API_KEY"}            # sent on every request
s.headers["Authorization"] = "Basic " + base64.b64encode(b"USERNAME:PASSWORD").decode()
# Each curl below omits these for brevity — add them to every request:
#   ?apikey=YOUR_API_KEY     (gateway key, on the query string)
#   -u "USERNAME:PASSWORD"   (cURL turns this into the Basic header)
⚠️ Recipes 2 and 3 write to live data

Creating appointments and orders changes real records immediately — there's no sandbox. Run writes against an account you control, with disposable records you can clean up afterward.

Build a customer 360 view

Pull a customer's profile together with their upcoming appointments and their purchase history — the data behind a "client card" screen. The thread that ties the calls together is the CustomerId: you look it up once, then reuse it.

  1. Find the customerGET /customers, filtered.
  2. Read their appointmentsPOST /customers/{id}/Appointments.
  3. Read their purchasesPOST /customers/{id}/Purchases.

1 · Look up the customer

Search by any combination of FirstName, LastName, Email or MobilePhone. Grab the CustomerId off the first match — it's an opaque string, so pass it back verbatim.

match = s.get(f"{base}/customers", params={"LastName": "Client", "Rows": 1}).json()
cust_id = match[0]["CustomerId"]     # e.g. "aFBoL3M3ODJoSFNhdDNSb3hDK0hNdz09"
company_id = match[0]["CompanyId"]   # carry this for scoping later calls
curl "https://api.envisiongo.com/api/v1/customers?apikey=YOUR_API_KEY&LastName=Client&Rows=1" \
  -u "USERNAME:PASSWORD"
# → [ { "CustomerId": "aFBoL3M3...", "CompanyId": "WlBiYmF...", "FirstName": "Test", ... } ]

2 · Read their appointments

📎 This read is a POST

The customer sub-resources — Appointments, Purchases, CancelledAppointments, GiftCertificates — are fetched with POST, not GET, even though they only read. Send an empty body. Optional StartDate/EndDate (plus Page/Rows) narrow the window.

appts = s.post(f"{base}/customers/{cust_id}/Appointments",
               params={"StartDate": "2026-06-01", "EndDate": "2026-06-30"}).json()
for a in appts:
    print(a["StartDate"], a["Service"], "with", a["Employee"])
curl -X POST "https://api.envisiongo.com/api/v1/customers/CUSTOMER_ID/Appointments?apikey=YOUR_API_KEY" \
  -u "USERNAME:PASSWORD" \
  --data ""
# → [ { "AppointmentId": "...", "StartDate": "2026-05-21T12:00:00",
#       "Service": "test service", "Employee": "Andrew Ault", ... } ]

3 · Read their purchase history

Same shape, different sub-resource. Each row carries the human-readable Item, Employee and Customer names alongside the IDs, so a history list needs no extra lookups.

purchases = s.post(f"{base}/customers/{cust_id}/Purchases").json()
total_spent = sum(p["Amount"] for p in purchases)
print(f"{len(purchases)} purchases, ${total_spent:,.2f} lifetime")
curl -X POST "https://api.envisiongo.com/api/v1/customers/CUSTOMER_ID/Purchases?apikey=YOUR_API_KEY" \
  -u "USERNAME:PASSWORD" \
  --data ""
# → [ { "OrderId": "...", "OrderNumber": "2", "Item": "test service",
#       "OrderDate": "2025-09-08T14:04:04", "Qty": 1, "Amount": 100.0 } ]

Full field lists: Customers reference →

Book a visit from an open slot

Turn "this customer wants a service with this provider" into a booked appointment. You assemble three IDs — service, employee, company — ask the API which slots are free, then write the appointment into one of them.

  1. Choose a serviceGET /services (gives ServiceId + its duration).
  2. Choose a providerGET /employees (gives EmployeeId).
  3. Find open slotsGET /appointments/ShowAvailable.
  4. Book onePOST /appointments.

1 · Pick the service and provider

List services and employees (both scope to a location with CompanyId). A service's InitialMinutes / ProcessMinutes / CompleteMinutes become the appointment's Init / Delay / Complete in step 4.

svc = s.get(f"{base}/services", params={"CompanyId": company_id, "IsActive": True}).json()[0]
emp = s.get(f"{base}/employees", params={"CompanyId": company_id, "IsActive": True}).json()[0]
service_id, employee_id = svc["ServiceId"], emp["EmployeeId"]
duration = svc["InitialMinutes"]   # e.g. 15
curl "https://api.envisiongo.com/api/v1/services?apikey=YOUR_API_KEY&CompanyId=COMPANY_ID&IsActive=true" \
  -u "USERNAME:PASSWORD"
curl "https://api.envisiongo.com/api/v1/employees?apikey=YOUR_API_KEY&CompanyId=COMPANY_ID&IsActive=true" \
  -u "USERNAME:PASSWORD"

2 · Find an open slot

ShowAvailable needs all four query params — CompanyId, EmployeeId, ServiceId and a StartDate (the day to search). It returns the bookable AvailableSlot times for that provider/service on that day.

slots = s.get(f"{base}/appointments/ShowAvailable", params={
    "CompanyId": company_id, "EmployeeId": employee_id,
    "ServiceId": service_id, "StartDate": "2025-09-11T12:00:00",
}).json()
first_slot = slots[0]["AvailableSlot"]   # e.g. "2025-09-11 13:15:00"
curl "https://api.envisiongo.com/api/v1/appointments/ShowAvailable?apikey=YOUR_API_KEY&CompanyId=COMPANY_ID&EmployeeId=EMPLOYEE_ID&ServiceId=SERVICE_ID&StartDate=2025-09-11T12:00:00" \
  -u "USERNAME:PASSWORD"
# → [ { "AvailableSlot": "2025-09-11 13:15:00", "EmployeeName": "Lev Kurts", "Initial": 15, ... }, ... ]

3 · Book the appointment

Now POST /appointments with the IDs you gathered and the slot you chose. A 200 OK returns the new appointment's ID, e.g. {"id": "U3V2Z1BZ…"}.

⚠️ Send the appointment shape, not the customer shape

The create body is an appointment — the Appointment model: CustomerId, CompanyId, EmployeeId, ServiceId, StartDate, the Init/Delay/Complete minutes, Notes and BookedOnline. (Some older examples paste the customer field list here by mistake — ignore that; use the fields below.)

booked = s.post(f"{base}/appointments", json={
    "CompanyId": company_id,
    "CustomerId": cust_id,
    "EmployeeId": employee_id,
    "ServiceId": service_id,
    "StartDate": "2025-09-11T13:15:00",   # a slot from step 2
    "Init": duration, "Delay": 0, "Complete": 0,
    "Notes": "Booked via API",
    "BookedOnline": True,
}).json()
print("new appointment:", booked)   # {"id": "U3V2Z1BZ..."}
curl -X POST "https://api.envisiongo.com/api/v1/appointments?apikey=YOUR_API_KEY" \
  -u "USERNAME:PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{
        "CompanyId": "COMPANY_ID",
        "CustomerId": "CUSTOMER_ID",
        "EmployeeId": "EMPLOYEE_ID",
        "ServiceId": "SERVICE_ID",
        "StartDate": "2025-09-11T13:15:00",
        "Init": 15, "Delay": 0, "Complete": 0,
        "Notes": "Booked via API",
        "BookedOnline": true
      }'
📎 VISH note

On VISH, appointments is read-only and there's no ShowAvailable search, so this booking flow is PNE-only. VISH clients can still list services, employees and appointments — see the VISH reference.

Ring up a retail order

Record a sale: find what's being sold, then post an order that bundles its line items and payments in one call. An order is a composite document, so you build the whole thing client-side and submit it together.

  1. Find the itemGET /products (or a service from recipe 2).
  2. Create the orderPOST /orders with line items + payments.
  3. Read it backGET /orders.

1 · Find the product

List products for the location. Per-location pricing and stock live in the nested ProductDetails array (matched by CompanyId), so read the price from the entry for the company you're selling at.

prod = s.get(f"{base}/products", params={"CompanyId": company_id, "IsActive": True}).json()[0]
item_id = prod["ProductId"]
price = next(d["Price"] for d in prod["ProductDetails"] if d["CompanyId"] == company_id)
curl "https://api.envisiongo.com/api/v1/products?apikey=YOUR_API_KEY&CompanyId=COMPANY_ID&IsActive=true" \
  -u "USERNAME:PASSWORD"
# → [ { "ProductId": "...", "Description": "Test Product",
#       "ProductDetails": [ { "CompanyId": "...", "Price": 20.0, "QtyOnHand": 0 } ] } ]

2 · Create the order

Each line item declares its kind with InvType, and each payment its method with a numeric PaymentType. The two enums:

InvType (line item)PaymentType (payment)
R Retail · S Service · P Package · O Other · G Gift1 Cash · 2 Check · 3 Gift · 4 Visa · 5 MC · 6 AMEX · 7 Discover · 18 Debit
📎 Make the money reconcile

The order's SubTotal/Total should equal the sum of its line items, and the Payments should cover the Total. The example sells one retail item at $20 and pays $20 cash. Most cost/tax fields can be 0.0 when you're just recording a straightforward sale.

order = s.post(f"{base}/orders", json={
    "CompanyId": company_id,
    "CustomerId": cust_id,
    "EmployeeId": employee_id,
    "OrderDate": "2025-09-08T14:04:04",
    "SubTotal": price, "Total": price,
    "LineItems": [{
        "CompanyId": company_id, "CustomerId": cust_id, "EmployeeId": employee_id,
        "ItemId": item_id, "InvType": "R",          # R = Retail
        "Qty": 1, "RetailPrice": price, "ExtendedPrice": price,
    }],
    "Payments": [{
        "CompanyId": company_id, "PaymentType": 1,   # 1 = Cash
        "Amount": price, "PaymentDate": "2025-09-08T14:04:00",
    }],
}).json()
print("new order:", order)   # {"id": "aEd0K3Jz..."}
curl -X POST "https://api.envisiongo.com/api/v1/orders?apikey=YOUR_API_KEY" \
  -u "USERNAME:PASSWORD" \
  -H "Content-Type: application/json" \
  -d '{
        "CompanyId": "COMPANY_ID",
        "CustomerId": "CUSTOMER_ID",
        "EmployeeId": "EMPLOYEE_ID",
        "OrderDate": "2025-09-08T14:04:04",
        "SubTotal": 20.0, "Total": 20.0,
        "LineItems": [ { "CompanyId": "COMPANY_ID", "ItemId": "PRODUCT_ID",
          "InvType": "R", "Qty": 1, "RetailPrice": 20.0, "ExtendedPrice": 20.0 } ],
        "Payments": [ { "CompanyId": "COMPANY_ID", "PaymentType": 1,
          "Amount": 20.0, "PaymentDate": "2025-09-08T14:04:00" } ]
      }'

3 · Read the order back

Confirm it landed with GET /orders (filter by CustomerId, CompanyId or a date range), or fetch the one you just created with GET /orders/{id}. The response echoes the full document — line items, payments, and any Coupons or Tips.

orders = s.get(f"{base}/orders", params={"CustomerId": cust_id}).json()
print(orders[0]["OrderNumber"], orders[0]["Total"])
curl "https://api.envisiongo.com/api/v1/orders?apikey=YOUR_API_KEY&CustomerId=CUSTOMER_ID" \
  -u "USERNAME:PASSWORD"
📎 VISH note

products is PNE-only, so this retail flow is written for PNE. VISH exposes orders and services — check the VISH reference for exactly what its order surface supports before adapting this.

ℹ️ Patterns these recipes rely on

All three lean on the same conventions: opaque IDs you pass back verbatim, paging on the list calls, and status-code-first error handling. Learn those once in Guides & concepts and every recipe reads the same way.