Skip to content
MakerOps

Integrations

It has to work with what you already run

No institution replaces everything at once, and no makerspace is the only system on campus. MakerOps connects five ways — live REST calls, event-driven webhooks, scheduled file exchange, an outbound-only channel for on-prem hardware, and campus single sign-on — so the integration can match what the other system is actually capable of.

A request, end to end

curl -H "Authorization: Bearer $TOKEN" \
  "https://your-space.makerops.ai/api/v1/equipment\
?updated_since=2026-07-28T00:00:00Z"

{
  "data": [
    {
      "id": 118,
      "name": "Summit Pro 48 #1",
      "status": "active",
      "equipment_type": { "id": 3, "name": "CNC Router" },
      "maintenance": {
        "open_tasks_count": 3,
        "overdue_tasks_count": 1
      }
    }
  ],
  "meta": {
    "server_time": "2026-07-28T19:04:22Z",
    "next_cursor": null,
    "deleted_ids": []
  }
}
integration patterns
5 integration patterns
documented endpoints
203+ documented endpoints
subscribable events
10 subscribable events
stable, versioned contract
v1 stable, versioned contract

I want to…

Use

Five patterns

Different systems need different doors

A card office wants a nightly file. A tool-control box in the basement cannot accept an inbound connection at all. A dashboard wants live reads. All of these are supported paths, designed for on purpose — not workarounds you have to discover.

01 Pull, live

Query and write over REST

For anything that needs the current answer right now.

A dashboard that shows machine downtime. A campus portal that lists which trainings a student has completed. A script that reconciles inventory against the purchasing system. Token-authenticated, permission-scoped, and shaped for incremental sync so a nightly job pulls only what changed.

Typically used for

  • Live dashboards and portals
  • Reconciliation scripts
  • Bulk loading during a migration
02 Push, event-driven

React to events with webhooks

For when something on your side must happen the moment something happens here.

Subscribe an HTTPS endpoint to normalized events and MakerOps posts to it as they occur. A member earns a credential and your tool-control system grants the machine within seconds. A card number changes and your door system updates. An item hits its reorder point and your procurement system opens a requisition. No polling, no lag, no cron job guessing at an interval.

Typically used for

  • Access-control provisioning
  • Procurement triggers
  • Downstream notification systems
03 Push, scheduled

Scheduled file exchange

For the systems that will only ever accept a file on a share.

A configurable roster CSV, generated on your schedule and delivered to your SFTP endpoint. You pick the columns and their order, the cadence, and the remote path. Plenty of campus systems — card offices, legacy tool control, some student information systems — have no API and never will. A file at 2am is not a workaround for them; it is the integration.

Typically used for

  • Card office roster feeds
  • Legacy systems with no API
  • Compliance and audit drops
04 Outbound only

Connectors for on-prem hardware

For boxes in the basement that cannot accept an inbound connection.

A small program runs inside your network and long-polls MakerOps for work, then reports results back. Everything is outbound from your side — no inbound firewall rule, no port forward, no VPN, no public endpoint on a machine that runs a laser. Failed jobs retry with backoff, and polling doubles as the liveness signal so an offline connector raises an alert on its own.

Typically used for

  • Tool control systems
  • Locker banks
  • Label and badge printers
05 Identity

Single sign-on and identity sync

For letting the campus stay the source of truth about people.

SAML single sign-on with per-tenant identity sync, so accounts, person identifiers, card numbers, and status follow the institution rather than being maintained twice. Configured from an administrator screen by importing your identity provider’s metadata — no code, and no ticket to us.

Typically used for

  • Campus SSO
  • Person-ID as the external key
  • Automatic deprovisioning

Scheduled exports

Sometimes a file at 2am is the right integration

Configure a roster export from the integrations settings screen: choose the columns and their order, pick the cadence, and point it at your SFTP host. MakerOps generates the CSV in your workspace's own timezone and delivers it. Credentials — password or private key — are stored encrypted, and the destination host is validated before every connection so a hijacked DNS record cannot redirect your roster somewhere else.

Cadence

Hourly, daily at a chosen hour, or weekly on a chosen day and hour — evaluated in your workspace's local time, so it does not drift with daylight saving.

Destination

Any SFTP path, with a {date} token if the receiving system expects one file per day rather than an overwrite.

Need it pulled rather than pushed, or in a different shape? The same roster is available live over the members endpoints, and any event on the list can also fire a webhook the moment it happens.

roster-2026-07-28.csv — wrapped for display

email,first_name,last_name,badge_id,
  integration_id,member_type,role,status

mchen@example.edu,Marcus,Chen,0004521,
  saml-person-8842,staff,coordinator,active

dwhit@example.edu,Dana,Whitfield,0004522,
  saml-person-8843,staff,technician,active

praman@example.edu,Priya,Raman,0004530,
  saml-person-8901,student,student,active

Selectable columns

emailfirst_namelast_namenamebadge_idintegration_idmember_typerolestatus

integration_id is the persistent identifier from your identity provider — the right primary key to join on, because unlike an email address or a card number it does not change when somebody marries or loses a badge.

Conventions

Learn it once, and the rest is the same

Every endpoint follows the same rules for authentication, shape, paging, change detection, and failure. There is no per-resource dialect to reverse-engineer.

01

Bearer tokens, workspace-scoped

Every request carries Authorization: Bearer <token>. The token resolves to a user, and the user resolves to a workspace — so a token can never read another institution’s data. Users who belong to more than one workspace select with an X-Tenant header.

02

One response envelope

Payloads are wrapped in data; list endpoints add a meta object carrying server_time and pagination cursors. That means a single client-side unwrapper, not a per-endpoint special case.

03

Cursor pagination

Lists take per_page (default 50, max 100) and return meta.next_cursor. Cursors are stable under concurrent writes, so a long sync will not skip or repeat rows the way page numbers do.

04

Built for incremental sync

Pass updated_since=<ISO 8601> to get only what changed, and read meta.deleted_ids for tombstones on soft-deleting resources. Store meta.server_time and hand it back on the next call — no clock-skew guessing.

05

Predictable failures

Validation errors are 422 with a field-keyed errors object. Unauthenticated is 401, missing permission is 403, and a module the workspace has turned off is 404 — so a disabled feature reads as "not here", never as "you are forbidden".

06

Permission-aware responses

Endpoints do not just reject — they shape. The dashboard returns only the widgets the caller may see alongside a can block, and the scan resolver returns the actions that particular user is allowed to take. Render from the response instead of hard-coding role checks.

Reference

Endpoints

The current v1 surface, grouped by resource. Paths are relative to https://your-space.makerops.ai/api. Everything under /v1 requires a bearer token unless marked otherwise.

Authentication & sessions

Exchange credentials for a bearer token, list the tokens a user holds, and register a device for push. SSO accounts authenticate through the identity provider instead and mint their token from the resulting session.

  • POST /v1/auth/login Email + password → bearer token rate limited
  • POST /v1/auth/logout Revoke the calling token
  • GET /v1/auth/sessions List the user’s active tokens
  • DELETE /v1/auth/sessions/{token} Revoke one session
  • POST /v1/devices Register a push token (idempotent)
  • DELETE /v1/devices Unregister a push token

Profile, notifications & preferences

The identity of the calling user: their membership, role, resolved permission map, notification inbox, and delivery preferences per channel.

  • GET /v1/me Profile, membership, permission map
  • PATCH /v1/me Update profile fields
  • POST /v1/me/photo Upload a profile photo
  • DELETE /v1/me/photo Remove the profile photo
  • GET /v1/me/credential-qr Rotating signed credential token 60s rotation
  • GET /v1/notifications Notification inbox + unread count
  • POST /v1/notifications/{id}/read Mark one notification read
  • POST /v1/notifications/read-all Mark everything read
  • GET /v1/notification-preferences Per-type channel preferences
  • PUT /v1/notification-preferences Update channel preferences

Equipment & fleet maintenance

Machines, equipment types (a two-level category → model hierarchy), their maintenance programs, and the service history attached to each unit. Fleet endpoints act on every machine of a type at once.

  • GET /v1/equipment Search / filter machines cursor paginated
  • GET /v1/equipment/{id} One machine + maintenance rollup
  • PATCH /v1/equipment/{id} Update machine fields edit_equipment
  • GET /v1/equipment/{id}/maintenance-history Completed maintenance, newest first
  • GET /v1/equipment/{id}/open-maintenance Open maintenance tasks
  • POST /v1/equipment/{id}/maintenance-log/draft Draft a service log from free text
  • POST /v1/equipment/{id}/maintenance-log Commit the log, close matched tasks
  • POST /v1/equipment/{id}/resources Attach a manual or link
  • PATCH /v1/equipment/{id}/resources/{resource} Update a resource
  • DELETE /v1/equipment/{id}/resources/{resource} Remove a resource
  • GET /v1/equipment-types Types with machine counts
  • GET /v1/equipment-types/maintenance-overview Fleet-wide maintenance health
  • GET /v1/equipment-types/{id}/maintenance-sweep Same task across every unit
  • POST /v1/equipment-types/{id}/maintenance-log Log one job across the fleet
  • GET /v1/equipment-resource-files/{id} Download a manual signed URL

Inventory & suppliers

Stock, consumables, and the taxonomy around them — categories, functional areas, units, tags, and supplier records with part numbers. Quantities are absolute values, not deltas, and are written transactionally.

  • GET /v1/inventory/items Search / filter items cursor paginated
  • POST /v1/inventory/items Create an item
  • GET /v1/inventory/items/{id} One item with locations, tags, suppliers
  • PATCH /v1/inventory/items/{id} Update item fields
  • POST /v1/inventory/items/{id}/quantity Set on-hand quantity absolute
  • GET /v1/inventory/low-stock Items at or below reorder point
  • GET /v1/inventory/categories Category tree
  • GET /v1/inventory/areas Functional-area tree
  • GET /v1/inventory/units Units of measure
  • GET /v1/inventory/tags Tags
  • GET /v1/suppliers Supplier directory
  • POST /v1/catalog/ask Ask what to use, answered from the shop’s own stock rate limited
  • POST /v1/catalog/ask/{id}/feedback Thumbs up / down on an answer

Tasks & task lists

One task model covers ad-hoc work and generated equipment maintenance, discriminated by task_type. Task lists group them into checklists and reusable templates.

  • GET /v1/tasks Filter by status, priority, area, equipment, assignee
  • POST /v1/tasks Create a task
  • GET /v1/tasks/{id} One task with relations
  • GET /v1/tasks/assignees Assignable users and groups
  • POST /v1/tasks/{id}/complete Complete with an optional note
  • POST /v1/tasks/bulk-complete Complete many at once
  • PATCH /v1/tasks/{id}/status Move status
  • GET /v1/task-lists Lists with progress counts
  • POST /v1/task-lists Create a list
  • GET /v1/task-lists/{id} One list and its tasks
  • PATCH /v1/task-lists/{id} Update a list
  • DELETE /v1/task-lists/{id} Delete a list
  • POST /v1/task-lists/{id}/tasks Attach tasks to a list
  • GET /v1/task-lists/{id}/template-items Reusable list template items
  • POST /v1/task-lists/{id}/template-items Add a template item
  • DELETE /v1/task-lists/{id}/template-items/{item} Remove a template item

Purchasing & approvals

Internal purchase requests move through an explicit state machine — requested, approved, ordered, received, stored — and each transition is a distinct endpoint with its own permission. Member item requests are a separate, lighter intake queue.

  • GET /v1/purchasing/requests Queue, filtered by status / supplier / area
  • POST /v1/purchasing/requests File a request
  • GET /v1/purchasing/requests/{id} One request
  • GET /v1/purchasing/stage-counts Counts per pipeline stage
  • POST /v1/purchasing/requests/{id}/approve Approve approve_purchase
  • POST /v1/purchasing/requests/{id}/reject Reject with a reason reject_purchase
  • POST /v1/purchasing/requests/{id}/order Mark ordered order_purchase
  • POST /v1/purchasing/requests/{id}/receive Receive goods receive_items
  • POST /v1/purchasing/requests/{id}/receive-and-store Receive and shelve in one call
  • POST /v1/purchasing/requests/{id}/store-placement Record where it was shelved
  • GET /v1/purchasing/item-requests Member requests to stock something new
  • POST /v1/purchasing/item-requests Submit an item request
  • GET /v1/purchasing/item-requests/{id} One item request

Training, credentials & waivers

The credentialing chain: courses with prerequisite graphs, a block-based native player, SCORM 1.2 launch, in-person issuance, scheduled sessions with waitlists, and signed waivers that themselves issue credentials.

  • GET /v1/lms/trainings Catalog with per-user lock state
  • GET /v1/lms/credentials Credentials held, with expiry
  • GET /v1/lms/history Attempt history and scores
  • POST /v1/lms/trainings/{id}/launch Start an attempt (SCORM or native)
  • GET /v1/lms/attempts/{id} Blocks + completion state
  • POST /v1/lms/attempts/{id}/blocks/{block}/complete Complete a slide or video block
  • POST /v1/lms/attempts/{id}/blocks/{block}/answer Answer a question block
  • POST /v1/lms/attempts/{id}/blocks/{block}/watch Report watched seconds server-clamped
  • POST /v1/lms/attempts/{id}/finish Grade, issue credentials, return review
  • GET /v1/lms/blocks/{block}/media/{key} Stream block media range requests
  • GET /v1/lms/sessions Upcoming in-person sessions
  • POST /v1/lms/sessions/{id}/signup Sign up or join the waitlist
  • POST /v1/lms/sessions/{id}/cancel Cancel a signup
  • POST /v1/lms/sessions/{id}/check-in Self check-in at the session
  • GET /v1/lms/manage/sessions Sessions you instruct issue_credentials
  • GET /v1/lms/manage/sessions/{id}/roster Roster and attendance
  • POST /v1/lms/manage/sessions/{id}/attendance Set attendance
  • POST /v1/lms/manage/sessions/{id}/bulk-issue Issue to everyone who attended
  • GET /v1/lms/issuable-trainings What you may issue in person
  • GET /v1/lms/issue/user-search Find a member to issue to
  • POST /v1/lms/credentials/issue Issue by user id or scanned QR
  • POST /v1/lms/credentials/{id}/revoke Revoke a credential admin
  • POST /v1/lms/action-codes Open a projected self-service code
  • POST /v1/lms/action-codes/redeem Redeem a projected code
  • GET /v1/waivers Pending and signed waivers
  • GET /v1/waivers/{id} Current published version + blocks
  • POST /v1/waivers/{id}/sign Sign with answers and a signature

Machine access & controllers

The IoT half: bind a controller to a machine, start and extend credentialed sessions, open a bank of machines for a class, restrict a machine set to a group on a schedule, and push firmware. Controllers themselves speak MQTT over TLS, not this API.

  • GET /v1/equipment-controllers Controllers and live health
  • POST /v1/equipment-controllers/bind Pair a controller to a machine
  • POST /v1/equipment-controllers/{id}/assign Reassign to a different machine
  • POST /v1/equipment-controllers/{id}/command Enable / disable / cycle
  • POST /v1/equipment-controllers/{id}/push-firmware Push an OTA update
  • GET /v1/firmware-releases Available firmware builds
  • POST /v1/equipment-sessions Start a session at a machine
  • GET /v1/equipment-sessions/{id} Session state
  • POST /v1/equipment-sessions/{id}/extend Extend a running session
  • DELETE /v1/equipment-sessions/{id} End a session
  • GET /v1/iot/offline-tokens Cacheable device-signed unlock tokens 24h validity
  • GET /v1/iot/open-access Instructor open-access windows
  • POST /v1/iot/open-access Open machines for a class
  • DELETE /v1/iot/open-access/{id} Close a window early
  • GET /v1/iot/group-access Scheduled group-access windows
  • POST /v1/iot/group-access Restrict machines to a group on a schedule
  • PATCH /v1/iot/group-access/{id} Update a window
  • DELETE /v1/iot/group-access/{id} Delete a window

Facility access & occupancy

Door and room access. Badge readers use a separate unversioned hardware API authenticated by a device token; people-facing check-in lives under /v1.

  • GET /v1/my-access Doors currently unlocked for you
  • GET /v1/facility-access/check-in Your current presence state
  • POST /v1/facility-access/check-in Check in to the facility
  • POST /v1/facility-access/check-out Check out
  • GET /facility-access/{facility}/approved-cards Card allowlist for a reader device token
  • POST /facility-access/scan Report a badge scan device token

Scheduling, reservations & time

Three distinct booking models: staff-mediated equipment appointments, self-service resource reservations, and the staff shift rota with clock in / out and swap claiming.

  • GET /v1/appointments Your appointment requests
  • POST /v1/appointments Propose windows for training or assisted use
  • POST /v1/appointments/{id}/confirm Confirm the assigned slot
  • POST /v1/appointments/{id}/cancel Cancel
  • GET /v1/appointment-queue Staff triage queue manage_appointments
  • POST /v1/appointment-queue/{id}/assign Assign staff and a time
  • POST /v1/appointment-queue/{id}/decline Decline a request
  • POST /v1/appointment-queue/{id}/complete Mark complete
  • GET /v1/reservations/resources Reservable rooms, benches, machines module: reservations
  • GET /v1/reservations/availability Free slots for a resource
  • POST /v1/reservations Book a slot
  • POST /v1/reservations/{id}/cancel Cancel a booking
  • GET /v1/time/status Clock state for the caller module: time_tracking
  • POST /v1/time/clock-in Clock in (optional geolocation)
  • POST /v1/time/clock-out Clock out
  • GET /v1/time/entries Time entries for a period
  • GET /v1/time/claimable-shifts Shifts offered for pickup
  • POST /v1/time/shifts/{id}/offer Offer your shift atomic
  • POST /v1/time/shifts/{id}/claim Claim an offered shift first-tap-wins
  • GET /v1/time/rota Read the rota manager
  • POST /v1/time/rota Create a shift
  • PATCH /v1/time/rota/{shift} Move or reassign a shift
  • DELETE /v1/time/rota/{shift} Delete a shift

Loans

Tool and item checkout. The desk names the borrower by user id or by their scanned badge; borrowers see their own loans and acknowledge a desk checkout with a drawn signature. Self-serve assets let the borrower check out and return by scanning the tag.

  • GET /v1/loans Desk list: out, overdue, or returned module: loans
  • GET /v1/loans/mine Your loans, active first
  • GET /v1/loans/availability One asset's loan policy and what is out
  • GET /v1/loans/assets Search loanable equipment and items loans.manage
  • GET /v1/loans/members Search borrowers by name, email, or badge
  • POST /v1/loans/resolve-member Resolve a scanned badge to a borrower
  • POST /v1/loans/checkout Check out to a member (or yourself, on a self-serve asset)
  • POST /v1/loans/{id}/confirm Borrower acknowledges with a signature
  • POST /v1/loans/{id}/return Return with condition and accessories
  • POST /v1/loans/{id}/lost Close a loan as lost loans.manage
  • PATCH /v1/loans/{id}/due Change the due date
  • POST /v1/loans/{id}/photos Condition photos for checkout or return
  • PUT /v1/loans/assets/{kind}/{id} Set an asset's loan policy loans.manage

Spaces & operating hours

Navigate the facility hierarchy, and read or edit operating hours. Hours are typed recurring events unioned together — open hours, club hours, staffed hours — with closures overriding them.

  • GET /v1/spaces Functional-area hierarchy
  • GET /v1/spaces/{id} One space with equipment and supplies
  • GET /v1/spaces/pinned Spaces pinned by the caller
  • POST /v1/spaces/{id}/pin Pin a space
  • DELETE /v1/spaces/{id}/pin Unpin
  • GET /v1/schedule Open-now state and today’s hours
  • GET /v1/schedule/days Hours across a date range
  • GET /v1/schedule/types Hour types (open, club, staffed…)
  • POST /v1/schedule/events Add a recurring hours event edit_settings
  • PATCH /v1/schedule/events/{id} Edit an hours event
  • DELETE /v1/schedule/events/{id} Delete an hours event
  • POST /v1/schedule/overrides Add a closure or one-off change

Members, groups & events

The member directory, group membership, and public events with RSVP. Group membership drives scheduled machine access and task assignment.

  • GET /v1/members Member directory view_members
  • GET /v1/members/{id} One member with credentials
  • GET /v1/groups Groups
  • POST /v1/groups Create a group
  • GET /v1/groups/{id} One group with members
  • PATCH /v1/groups/{id} Rename or update a group
  • DELETE /v1/groups/{id} Delete a group
  • POST /v1/groups/{id}/members Add a member
  • DELETE /v1/groups/{id}/members/{user} Remove a member
  • GET /v1/events Upcoming public events
  • GET /v1/events/mine Events you signed up for
  • POST /v1/events/{id}/signup RSVP
  • DELETE /v1/events/{id}/signup Cancel an RSVP
  • GET /v1/mentionable-users Users mentionable in comments

Comments, dashboard, scanning & support

Cross-cutting surfaces: threaded comments on any record, the operations dashboard, the QR scan resolver that classifies any scanned code, and support / feedback intake.

  • GET /v1/comments/{type}/{id} Comments on any commentable record
  • POST /v1/comments/{type}/{id} Post a comment with @mentions
  • GET /v1/dashboard Permission-gated operations rollup
  • POST /v1/scan/resolve Classify any scanned QR or SKU
  • POST /v1/scan/report-low-stock Member low-stock report idempotent
  • GET /v1/announcements/banner Active announcement banner
  • POST /v1/announcements Send an announcement permissioned
  • POST /v1/assistant/dictation-draft Draft an action from dictated text
  • GET /v1/support-tickets Your support tickets
  • POST /v1/support-tickets Open a ticket
  • POST /v1/feedback Submit in-product feedback

Public & unauthenticated

The only endpoints that need no token. Both are rate limited, and the contact endpoint is CORS-scoped to the origins you configure.

  • GET /v1/public/tenants/{slug}/branding Workspace name, logo, colors
  • POST /v1/contact Contact / demo-request intake

Connector API (on-prem systems)

A deliberately unversioned, outbound-only channel for programs running inside your network — tool control, lockers, label printers. The connector long-polls for work and posts results back; nothing inbound has to reach your network.

  • GET /connector/jobs Long-poll for work (held up to 25s)
  • POST /connector/jobs/{id}/result Report success or failure
  • GET /connector/jobs/{id}/artifact Fetch a job’s binary artifact
  • GET /connector/config Your registration, events, field mappings

Data types

What comes back on the wire

The core resources, abbreviated. Timestamps are ISO 8601 in UTC, money is integer cents, and decimal database columns serialize as strings — so nothing silently loses precision on the way through a JSON parser.

type Me The calling user, their membership in the resolved workspace, and the permission map clients render from.
{
  "data": {
    "id": 42,
    "name": "Marcus Chen",
    "email": "mchen@example.edu",
    "auth_type": "saml",
    "tenant":     { "slug": "ridgeline", "name": "Ridgeline Fabrication Lab" },
    "membership": { "role": "coordinator", "roles": ["coordinator","student"],
                    "member_type": "staff", "badge_id": "0004521" },
    "permissions": { "view_equipment": true, "approve_purchase": false, "...": "..." },
    "unread_notification_count": 3
  }
}
type Equipment A machine. `resolved_*` fields fall back to the equipment type when the unit does not override them; `maintenance` is a rollup, present on the detail endpoint only.
{
  "id": 118,
  "name": "Summit Pro 48 #1",
  "status": "active",
  "asset_tag": "RFL-CNC-004",
  "manufacturer": "Summit",
  "model_number": "Pro 48",
  "serial_number": "SP48-2291",
  "functional_area":  { "id": 7, "name": "Wood Shop" },
  "equipment_type":   { "id": 3, "name": "CNC Router" },
  "hazard_class": null,
  "resolved_hazard_class": "high",
  "resolved_access_type": "credentialed",
  "image_url": "https://…/equipment/118.jpg",
  "comments_count": 4,
  "maintenance": {
    "open_tasks_count": 3,
    "overdue_tasks_count": 1,
    "active_templates": [
      { "id": 55, "title": "Check nozzle wear", "frequency_type": "weeks",
        "frequency_interval": 2, "next_due_at": "2026-08-04T13:00:00Z", "status": "active" }
    ]
  },
  "updated_at": "2026-07-28T18:02:11Z"
}
type InventoryItem A stocked item. Reorder logic is a nested object rather than loose columns, and locations carry a relationship type — where it is stored versus where it is used.
{
  "id": 904,
  "name": "M5 Hex Nut, Zinc",
  "sku": "FAST-M5-NUT",
  "status": "active",
  "quantity_tracked": true,
  "current_quantity": 1450,
  "storage_location": "Bin C4",
  "storage_unit": { "id": 2, "name": "Each", "abbreviation": "ea" },
  "reorder": {
    "condition": "at_or_below",
    "trigger_quantity": 250,
    "default_order_quantity": 1000,
    "needs_reorder": false
  },
  "category":  { "id": 12, "name": "Fasteners" },
  "locations": [{ "id": 7, "name": "Wood Shop", "relationship_type": "stored_in",
                  "location_detail": "Bin C4" }],
  "tags":      [{ "id": 3, "name": "Consumable", "color": "amber" }],
  "suppliers": [{ "id": 5, "name": "McMaster-Carr", "part_number": "90592A090",
                  "is_preferred": true }],
  "image_url": "https://…/items/904.jpg",
  "updated_at": "2026-07-27T09:14:02Z"
}
type Task Ad-hoc work and generated equipment maintenance share one shape, discriminated by `task_type`. Never compare that field with `!=` — a null drops rows.
{
  "id": 3311,
  "title": "Replace laser lens — Lumen L-60 #2",
  "status": "todo",
  "priority": "high",
  "task_type": "equipment_maintenance",
  "due_at": "2026-07-30T16:00:00Z",
  "is_overdue": false,
  "recurring_task_template_id": 55,
  "checklist_progress": { "total": 4, "done": 1 },
  "assigned_users":  [{ "id": 42, "name": "Marcus Chen" }],
  "assigned_groups": [],
  "functional_area": { "id": 9, "name": "Laser Lab" },
  "equipment":       { "id": 121, "name": "Lumen L-60 #2" },
  "inventory_item":  null,
  "comments_count": 2,
  "completed_at": null,
  "completed_by": null,
  "created_at": "2026-07-21T11:00:00Z",
  "updated_at": "2026-07-26T08:31:44Z"
}
type PurchaseRequest Snapshots the requested item at filing time so later edits to the catalog never rewrite history. Relations are lazily loaded — a key that is absent was simply not requested by that endpoint.
{
  "id": 771,
  "item_name_snapshot": "1/4\" Carbide Endmill, 4-flute",
  "requested_quantity": 10,
  "priority": "normal",
  "status": "awaiting_approval",
  "request_note": "Down to two in the drawer.",
  "approval_note": null,
  "rejection_reason": null,
  "supplier_order_number": null,
  "requested_by": { "id": 61, "name": "Dana Whitfield" },
  "supplier":     { "id": 5, "name": "McMaster-Carr" },
  "created_at": "2026-07-24T15:20:00Z",
  "updated_at": "2026-07-24T15:20:00Z"
}
type Training & Credential Prerequisites are groups of alternatives: OR within a group, AND across groups. A credential is what actually unlocks a machine.
// GET /v1/lms/trainings
{
  "id": 14,
  "name": "CNC Router Certification",
  "state": "locked",
  "credential_type": "in_person",
  "grants_credentials": ["CNC Router Access"],
  "requirements": [
    [{ "id": 2, "name": "Make 101",        "held": true  },
     { "id": 6, "name": "Wood Shop Access","held": false }],
    [{ "id": 9, "name": "Laser Access",    "held": false }]
  ]
}

// GET /v1/lms/credentials
{
  "id": 880,
  "credential_name": "Laser Cutter Access",
  "earned_at":  "2026-02-11T00:00:00Z",
  "expires_at": "2027-02-11T00:00:00Z",
  "is_active": true,
  "is_expired": false
}
type EquipmentSession A credentialed run at a physical machine. Sessions reconcile against what the controller reports over MQTT, so a session can close from either side.
{
  "id": 5521,
  "equipment_id": 118,
  "user_id": 42,
  "status": "running",
  "started_at": "2026-07-28T18:04:00Z",
  "expires_at": "2026-07-28T19:04:00Z",
  "ended_at": null,
  "start_source": "qr",
  "open_window_id": null
}

Webhooks

Events, the moment they happen

Register an HTTPS endpoint, choose the events it cares about, and MakerOps posts to it as they occur. The events are normalized — a credential issued by finishing an online course, by a trainer in person, or by signing a waiver all arrive as the same credential.issued payload, so your integration does not need to know how the credential was earned.

Subscribable events

  • user.created A member joined the workspace
  • user.updated Profile or identity fields changed
  • user.card_changed Badge / card number changed
  • user.deactivated Member was deactivated
  • credential.issued A credential was issued
  • credential.expired A credential expired
  • credential.revoked A credential was revoked
  • inventory_item.created An inventory item was created
  • inventory.low_stock An item hit its low-stock threshold
  • print_job.created A label print was requested

Delivery headers

X-Makerspace-Event:     credential.issued
X-Makerspace-Delivery:  4211
X-Makerspace-Signature: t=1785014568,
                        v1=<HMAC-SHA256 of "t.body">

Verify by recomputing the HMAC with your shared secret over timestamp + "." + raw body, then comparing in constant time. Reject anything with a stale timestamp.

POST to your endpoint — user.card_changed

{
  "event": "user.card_changed",
  "previous_badge_id": "0004498",
  "user": {
    "id": 42,
    "name": "Marcus Chen",
    "email": "mchen@example.edu",
    "member_type": "staff",
    "status": "active",
    "role": "coordinator",
    "integration_id": "saml-person-8842",
    "badge_id": "0004521",
    "mapped": {
      "PersonID": "saml-person-8842",
      "CardNumber": "4521"
    }
  }
}

mapped is the same data re-keyed into your field names. Configure the mapping once in settings and the receiving system can consume the payload without a translation layer of its own.

Signed

HMAC-SHA256 over timestamp + body, in an X-Makerspace-Signature header

Idempotent

Every delivery carries a stable job id — safe to receive twice

Retried

Backoff at 1, 5, 15, then 60 minutes before a job is marked failed

Self-disabling

A webhook that keeps failing is switched off instead of hammering a dead host

Monitored

State-transition alerts only — one notice on down, one on recovery

Recoverable

A crashed connector’s claimed jobs requeue after 10 minutes, without burning a retry

Cannot expose an endpoint? Take the same events by polling.

The events above are also deliverable through the connector channel. A small program inside your network long-polls for work and posts results back — no inbound firewall rule, no public endpoint, no VPN. That is how tool-control systems, locker banks, and label printers integrate today, and it is why an integration never requires opening a hole in your network.

Discuss a connector

Getting a token

API access is included in every plan — there is no integration tier and no per-call metering. Workspace administrators create tokens from settings; connector and webhook secrets are shown once and stored only as hashes. If you are building something specific, tell us what you need and we will point you at the right surface.

Bring your own tools — we will meet them

Book a walkthrough and we will map your existing stack: identity provider, student information system, tool control, label printers, and whatever else the shop already depends on.