ProductFlo.io

Overview

ProductFlo Sync exposes a pragmatic REST API plus Server-Sent Events (SSE) for real-time collaboration: uploads with optimistic concurrency, workspace-scoped presence, locks, conflicts, history, comments, design reviews, and tasks.

  • Base URL: same origin as the web app (e.g. https://prod.productflo.io)
  • All /api/* and /v1/* routes are served by the same Express app

BOM Import CSV format

CSV/XLSX columns are case-insensitive. Required: child_part_number, qty. Optional: uom (defaults to each), refdes, notes. Download a sample CSV.

ColumnAliasesNotes
child_part_numberpart_number, pn, child_pnCreates missing parts with domain other
qtyquantityMust be > 0
uomunit, unit_of_measureDefaults to each
refdesreference_designators, reference, designatorsElectronics reference designators
notesnote, comment, commentsFreeform notes

Use POST /v1/boms/import with multipart body: file (CSV/XLSX), bomId, dryRun?. On dry-run, the API returns { rows, valid, errors[] } without committing changes.

Parts Import (CSV/XLSX)

Columns are case-insensitive. Required: part_number, name. Optional: description, domain (mechanical/electronic/electrical/chemical/other), uom (default each), lifecycle (prototype/released/on_hold/obsolete), manufacturer_name, manufacturer_part_number, tags (comma or | separated), attributes (JSON string).

ColumnAliasesNotes
part_numberpnRequired; matches existing to upsert
nameRequired
domainDefault other
uomunit, unit_of_measureDefault each
lifecyclelife_cycleDefault prototype
tagsComma or pipe separated
attributesattrs,jsonJSON string validated by domain templates

Use POST /v1/parts/import with multipart body: file (CSV/XLSX), dryRun?, workspace. Dry-run returns { rows, valid, errors[] }. Apply commits atomically (creates/updates).

Parts & BOM

MethodPathBody / QueryDescription
POST/v1/parts{ workspace, partNumber, name, domain, ... }Create part (engineer+)
GET/v1/parts?workspace=&q=&domain=&lifecycle=&tag=&sort=&dir=&page=&pageSize=List parts (supports sorting)
GET/v1/parts/:id?workspace=Part detail (with versions)
PATCH/v1/parts/:id{ name?, description?, domain?, uom?, lifecycle?, tags?, attributes? }Update part (engineer+; lifecycle manager+)
POST/v1/parts/:id/versions{ revision, changeNote?, data? }Create version (engineer+)
GET/v1/parts/:id/versions?workspace=List part versions
POST/v1/parts/:id/versions/:versionId/submitSubmit part version (engineer+)
POST/v1/parts/:id/versions/:versionId/approveApprove part version (manager+)
POST/v1/parts/:id/versions/:versionId/reject{ reason? }Reject part version (manager+)
GET/v1/parts/:id/where-used?workspace=Assemblies referencing a part
POST/v1/boms{ workspace, assemblyPartId, assemblyVersionId, type, revision, changeNote? }Create BOM (engineer+)
GET/v1/boms?workspace=&type=&status=&assemblyPartId=&assemblyPn=&page=&pageSize=List BOMs
GET/v1/boms/:id?workspace=Get BOM header + tree
POST/v1/boms/:id/items{ items: [{ id?, parentItemId?, childPartId?, childVersionId?, qty?, uom?, refdes?, notes?, sortIndex?, attributes?, _op? }] }Bulk upsert/delete BOM items (engineer+)
POST/v1/boms/:id/submitSubmit BOM (engineer+)
POST/v1/boms/:id/approveApprove BOM (manager+)
POST/v1/boms/:id/reject{ reason? }Reject BOM (manager+)
GET/v1/boms/:id/diff?workspace=&leftRev=&rightRev=Compare two revisions
POST/v1/boms/importmultipart: file (CSV), bomId, dryRun?Import items from CSV
GET/v1/boms/:id/export.csv?workspace=Export BOM to CSV
GET/v1/boms/:id/export.xlsx?workspace=Export BOM to Excel (XLSX)
GET/v1/boms/:id/export.erp.csv?workspace=Export ERP-friendly flattened CSV
GET/v1/boms/:id/cost-rollup?workspace=Compute simple unit cost rollup
POST/v1/boms/:id/items/:itemId/alternates{ alternates: [{ partId, approval, rationale }] }Set alternates for BOM item (engineer+)
POST/v1/boms/:id/derive-mbom{ revision, changeNote? }Derive MBOM from EBOM (engineer+)
POST/v1/boms/:id/effectivity/validate{ effectivity, itemId?, partId? }Validate effectivity conflicts (engineer+)
GET/v1/workspaces/:name/bom-settingsGet workspace BOM settings (viewer+)
POST/v1/workspaces/:name/bom-settings{ numbering_rules, domain_templates }Save settings (manager+)

BOM Advanced Features

Effectivity Management

BOM items support effectivity control by date, serial number, and lot.

  • date_from/date_to: ISO date strings for date-based effectivity
  • serial_from/serial_to: Numeric serial number ranges
  • lot_from/lot_to: String-based lot effectivity
  • Use POST /v1/boms/:id/effectivity/validate to check for conflicts

Compliance Tracking

Parts include compliance attributes for regulatory requirements.

  • rohs_compliant: Boolean RoHS compliance status
  • reach_compliant: Boolean REACH compliance status
  • conflict_minerals: String indicating conflict mineral status
  • Warnings automatically generated for non-compliant items in BOMs

Alternates Management

Each BOM item can have approved alternates with rationale.

  • approval: approved | conditional | rejected
  • rationale: String explaining why alternate is suitable
  • Manager approval required for alternate changes
  • Import wizard supports alternate assignment via CSV

Cost Management

Simple cost rollup calculations with currency support.

  • unit_cost: Decimal cost per part
  • currency: ISO currency code (USD, EUR, etc.)
  • Rollup includes quantity × unit_cost for all items
  • Export includes cost columns when available

Domain Templates & Validation

Workspace-level domain templates enforce required attributes per engineering domain.

DomainTypical Required FieldsValidation
mechanicalmaterial, finish, toleranceServer validates on part create/update
electronicpackage, value, voltage_ratingImport wizard shows domain-specific warnings
electricalwire_gauge, connector_type, voltageRequired fields enforced via JSON schema
chemicalcas_number, concentration, hazard_classMSDS and safety data validation

Configure templates via POST /v1/workspaces/:name/bom-settings with domain_templates object.

BOM Export Formats

Multiple export formats optimized for different use cases.

Standard CSV/XLSX

  • Hierarchical structure with level indicators
  • All BOM item attributes included
  • Alternates listed as comma-separated values
  • Effectivity dates formatted as ISO strings

ERP-friendly CSV

  • Flattened structure with parent/child relationships
  • Optimized column order for ERP import
  • Normalized unit of measure values
  • Cost information when available

Parts Catalog — Filters, Sorting, Bulk, Shortcuts

The Parts Catalog modal provides a searchable, filterable list with bulk actions and keyboard shortcuts.

Filters & Query

  • q: matches part_number (prefix), name (contains), manufacturer_part_number (prefix)
  • domain: mechanical | electronic | electrical | chemical | other
  • lifecycle: prototype | released | on_hold | obsolete
  • tag: requires JSON_CONTAINS of tag
  • UI shows clearable filter chips; URL persists parts_q, parts_domain, parts_lifecycle, parts_tag, parts_sort, parts_dir, parts_page

Sorting

  • sort: part_number | name | domain | lifecycle | manufacturer_part_number | updated_at
  • dir: asc | desc
  • UI toggles aria-sort on column headers and forwards sort/dir to GET /v1/parts

Bulk Actions

  • Apply tags to selected parts (engineer+)
  • Set lifecycle on selected parts (manager+ enforcement in UI)
  • Export selected

Shortcuts

  • / focuses the Parts search when the Parts modal is open; otherwise focuses file filter
  • P opens Parts Catalog; B opens BOMs
  • Esc clears current selection in Parts Catalog

Quick start

  1. Authenticate via the web app and keep session cookies
  2. Create or join a workspace via invitation
  3. Initialize your user folder: POST /v1/users/init
  4. Start streaming updates: GET /v1/events?workspace=&username=
  5. Upload a model: POST /v1/upload

Configuration

Frontend behavior (e.g., branch handling) adapts to server config.

MethodPathDescription
GET /v1/config Returns { branchSuffixWrites: boolean }

Authentication

Send requests with the browser session cookie; server-side integrations can forward authenticated cookies to mint tokens for the browser flow.

MethodPathDescription
GET/api/auth/configReturns { requiresInvitation: boolean }
GET/api/sessionReturns current user or { user: null }

CORS for file viewers

Static APIs are same-origin. Range-enabled file serving under /v1/serve/* supports CORS and Range requests.
Configure CORS_ORIGINS (comma-separated) to allow specific origins or *.

Rate limits

  • Invitations: /api/invitations/send → 5/min per IP
  • Uploads: /v1/upload → 30/min per workspace/IP
  • Comments: /v1/comments → 120/min per workspace/IP
  • Lock edit requests: /v1/locks/request → 60/min per workspace/IP

Permissions & Roles

Workspace roles are viewer < engineer < manager < owner.
Many endpoints below require at least engineer.
Invitation sending and some admin-like actions require manager+.

Workspaces & Presence

MethodPathBody / QueryDescription
GET/v1/workspaces?username=List workspaces for the current user
POST/v1/workspaces{ name }Create workspace; creator becomes owner
GET/v1/events?workspace=&username=SSE stream for updates, presence, locks, comments, tasks
GET/v1/users?workspace=Users with online flag in workspace
POST/v1/users/init{ workspace?, username? }Initialize user folder and backfill

SSE example

const es = new EventSource(`/v1/events?workspace=default&username=alice`);
es.onmessage = (e) => {
  const evt = JSON.parse(e.data);
  // handle { type: 'update'|'version'|'presence'|'comment'|'lock'|'unlock'|'task' ... }
};

Invitations

MethodPathBody / QueryNotes
POST/api/invitations/send{ email, invitedBy?, workspace, role? }Manager+ in workspace; 429 on rate limit
GET/api/invitations?status=all|pending|accepted&workspace=&limit=List my invites; managers can scope by workspace
POST/api/invitations/validate{ invitationCode }Open (pre-auth) to validate codes
POST/api/invitations/accept{ invitationCode }Accept and attach membership

Files & Uploads

All file routes are workspace-scoped. Allowed extensions include common CAD formats (.step, .stl, .obj, .glb, etc.). Max size defaults to MAX_UPLOAD_MB=200.

MethodPathBody / QueryDescription
GET/v1/files?workspace=&username=List a user’s files (with etag)
GET/v1/files/all?workspace=List all files across users
GET/v1/file?workspace=&username=&filename=Small text preview
GET/v1/filemeta?workspace=&username=&filename=Metadata (size, contentType, etag, sha256)
POST/v1/upload?workspace=&username=multipart: file; baseVersion?; replace?; message?Optimistic concurrency; conflicts materialized as _conflict_ files
POST/v1/uploads?workspace=&username=multipart: files[]; replace?Batch upload (max 20)
DELETE/v1/file?workspace=&username=&filename=Soft delete to workspace trash
GET/v1/serve/:workspace/:username/:filenameRange-enabled serving (CORS-aware)

Upload examples

# Replace-only when you know the current etag
curl -X POST -F "file=@model.step" -F "baseVersion=ETAG" \
  "/v1/upload?workspace=default&username=alice"

# Force replace (skips optimistic check)
curl -X POST -F "file=@model.step" -F "replace=true" \
  "/v1/upload?workspace=default&username=alice"

Signed URLs

For cross-origin or time-limited sharing, create signed URLs then fetch via /v1/serve-signed.

MethodPathQueryDescription
GET/signed-urlpath=/v1/serve/...&ttlMs=60000Returns { url, expiresAt }
GET/v1/serve-signed/:workspace/:username/:filename?expires=&sig=Validates signature and serves

APS Integration

Autodesk Platform Services (APS) is used for viewing and diffs. Translate models and query translation status.

MethodPathBody / QueryDescription
POST /api/aps/translate-file { workspace, filename } Queue a translation for the given file (respecting branch naming)
GET /api/aps/status-map ?workspace= Return array of { name, status, progress? }
GET /api/aps/models/lookup ?workspace=&filename= Lookup APS URN for a translated filename (used by Diff Tool)

History, Timeline & Revert

MethodPathBody / QueryDescription
GET/v1/versions?path=/v1/serve/...List versions for a file (with nearest comment message)
GET/v1/timeline?path=/v1/serve/...&types=version,comment,taskAggregated events
POST/v1/revert{ path, versionId, message?, author? }Revert across all users (respects locks)

Branches & Merges

Branching supports Phase 1 (filename suffix) and Phase 2 (first-class). Toggle via PF_BRANCH_SUFFIX_WRITES. Use these endpoints to manage branches.

MethodPathBody / QueryDescription
GET /v1/branches ?workspace=&username=&base= List branches for a base filename
POST /v1/branches/create { workspace, username, base, branch } Create a branch (name: [a-z0-9_-]+)
POST /v1/branches/delete { workspace, username, base, branch } Delete a branch (not main)
POST /v1/merge { workspace, username, filename, fromBranch, message? } Merge a branch into main and create a new version

Comments

MethodPathBody / QueryDescription
GET/v1/comments?path=/v1/serve/...List comments for file
POST/v1/comments{ path, author, body }Create comment; mentions with @username

Locks

Locks are soft checkouts with TTL (default 10 minutes, minimum 1 minute). Many mutating operations respect locks and return 423 when locked by another user.

MethodPathBody / QueryDescription
GET/v1/locks?path=/v1/serve/...Get current lock info or null
POST/v1/locks/lock{ path, owner, ttlMs? }Acquire lock (409 if someone else holds a valid lock)
POST/v1/locks/unlock{ path, lockId }Release lock
POST/v1/locks/force-unlock{ path, author, message? }Manager-style override with optional comment and notifications
POST/v1/locks/request{ path, from }Notify current owner via SSE
POST/v1/locks/lock-all{ workspace, owner, ttlMs? }Bulk lock every file (manager+)
POST/v1/locks/unlock-all{ workspace }Bulk unlock (manager+)

Conflicts

MethodPathBody / QueryDescription
GET/v1/conflicts?workspace=&username=List active conflicts for workspace
POST/v1/conflicts/resolve{ baseName, acceptedPath, message?, author? }Accept one version and propagate to all users

Trash

MethodPathBody / QueryDescription
GET/v1/trash?workspace=&username=List trashed items
POST/v1/trash/restore{ workspace, username, key }Restore item

Design Reviews

MethodPathBody / QueryDescription
GET/v1/reviews?path=/v1/serve/...List reviews for a file (with nested tasks)
POST/v1/reviews{ path, reviewerEmail, message?, dueAt?, versionId? }Create a design review (engineer+)
PATCH/v1/reviews/:id{ status?, reviewerEmail?, dueAt? }Update a review
POST/v1/reviews/:id/tasks{ title, description?, dueAt? }Create a review task (reviewer or manager+)
PATCH/v1/reviews/:id/tasks/:taskId{ status }Update review task status

Standalone Tasks

MethodPathBody / QueryDescription
GET/v1/tasks?path=/v1/serve/...List tasks for file
POST/v1/tasks{ path, title, description?, ownerEmail?, dueAt?, status? }Create task; statuses: open|in_progress|done|canceled
PATCH/v1/tasks/:id{ status?, title?, description?, ownerEmail?, dueAt?, sortIndex? }Update task (creator/owner/manager)
DELETE/v1/tasks/:idSoft delete task (creator or manager)

Workspace Analytics

MethodPathQueryDescription
GET/v1/workspace/stats?workspace=Counts of reviews and tasks by status
GET/v1/workspace/reviews?workspace=&status=all|open|in_review|completed|canceled&limit=List reviews across workspace
GET/v1/workspace/tasks?workspace=&status=all|open|in_progress|done|canceled&limit=List tasks across workspace

Review Templates

MethodPathBody / QueryDescription
GET/v1/review-templates?workspace=List templates (engineer+)
POST/v1/review-templates{ name, message?, checklist[] }Create template (manager+)
DELETE/v1/review-templates/:id?workspace=Delete template (manager+)

Errors & Status Codes

  • 400: Missing or invalid parameters
  • 401/403: Unauthorized or forbidden
  • 404: Not found
  • 409: Conflict (e.g. lock held by another user)
  • 410: Signed URL expired
  • 413: Payload too large
  • 415: Unsupported media type (text preview)
  • 423: Locked
  • 429: Rate limited
  • 500: Server error
  • 503: Database unavailable (some admin/user endpoints)