v1

API Reference

Every endpoint in the Pulsar Public API, grouped by resource, and the conventions they share.

Browse endpoints using the sidebar, grouped by resource (Machines, Operators, SKUs, Production logs, Reports, and so on). Each endpoint page includes parameters, request/response schemas, and a live request builder with generated code samples.

Conventions

  • Path and query parameters are snake_case, and JSON bodies are camelCase. Exceptions: the signals endpoints also answer in snake_case, and GET /v1/shifts takes machineId, matching the field it returns.
  • Searching is a POST to a /filters sub-path, because the filter bodies nest.
  • Searches are paginated: page starts at 1 and limit defaults to 10, and the response wraps rows in a totalCount / totalPages / currentPage envelope. Other lists, such as GET /v1/machines and the GET /v1/shifts routes, return a plain JSON array without paging.
  • Creating records with a POST to a collection, such as /v1/operators or /v1/production-logs, takes a JSON array, even for a single record. Actions on one existing record, such as assigning an operator to a shift or adding defects to it, take a single JSON object.
  • Machine and operator ids are integers. Signal, SKU, work order, production log and nominal speed ids are UUID strings. The signals endpoints take the machine's uuid, not its integer id.
  • A time without a UTC offset is read as UTC.

Permissions

Each endpoint needs one scope, named section.action, such as shifts.read or production_logs.delete. Every endpoint page opens with its Required scope, and the shift write endpoints also name the related scopes an integration needs to check or undo what it sends. Keys created in the Pulsar app under Settings → Integrations carry every public scope. A key without the scope an endpoint needs gets 403 with the code missing_scope, and detail names the missing scope:

{ "code": "missing_scope", "detail": "Not enough permissions. Missing scope: shifts.read" }

Errors

Every error except 422 has the same body: a stable code to branch on, and a human-readable detail that may change, so don't parse it.

{ "code": "shift_not_found", "detail": "No shift of yours matches the request." }

Each endpoint's description and responses list the codes it returns, such as shift_not_found, insufficient_production or idempotency_key_reused. New codes may be added, so handle an unknown one by its HTTP status. Without a more specific code, each status has a generic one:

StatusCodeMeaning
400invalid_requestThe request was rejected.
401unauthenticatedThe x-api-key header is missing, or the key isn't valid in this environment. See Environments.
403forbiddenThe key can't perform this request. A missing scope is missing_scope.
404not_foundThe resource doesn't exist.
409conflictThe request conflicts with the current state.
429rate_limitedThe key's rate limit was reached. Wait the number of seconds in the Retry-After header, then send the request again.
5XXinternal_errorThe request failed on Pulsar's side, and its outcome is unknown. Read Retries before sending it again.

A 422 means the body or a parameter failed validation. Its detail is a list, and each item's loc points to the field that failed.

Retries

Whether a failed call recorded anything depends on how it failed:

  • A 4XX response means the request was rejected and nothing was recorded. Fix it and send it again.
  • A timeout, a dropped connection or a 5XX response leaves the outcome unknown. The request may have been recorded even though you never got the response.

Calls that record production or defects add to what is already there, so a request sent twice is recorded twice. Three endpoints accept an Idempotency-Key header that makes a retry safe: POST /v1/production-logs, POST /v1/shifts/defects and POST /v1/shifts/{shift_id}/defects. Generate a unique value for each request, such as a UUID, and send the same key and the same body on every retry of that request:

  • If the original request was recorded, the retry records nothing and returns the original response, with the header Idempotent-Replayed: true.
  • If the original was not recorded, the retry is processed as a new request.
  • Reusing a key with a different body or URL returns 409. So does a retry while the original is still being processed: wait a moment and retry again.

Keys are shared by all of your company's API keys, and each is kept for 24 hours. After that, a request with the same key is processed as a new one.

Without an Idempotency-Key, check what was recorded before you send the request again. GET /v1/shifts/{shift_id}/defects lists a shift's defects, and POST /v1/production-logs/filters finds production logs, also by shift with "shifts": {"include": [<shift id>]}.

Covering a long period

GET /v1/shifts accepts a range of up to 31 days. To cover a longer period, split it into consecutive ranges, where each range's to is the next one's from, and add match=start. Each shift then comes back exactly once. With the default match=overlap, a shift that crosses the boundary between two ranges comes back in both, so deduplicate by id before acting on it.

On this page