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.
| Column | Aliases | Notes |
|---|---|---|
| child_part_number | part_number, pn, child_pn | Creates missing parts with domain other |
| qty | quantity | Must be > 0 |
| uom | unit, unit_of_measure | Defaults to each |
| refdes | reference_designators, reference, designators | Electronics reference designators |
| notes | note, comment, comments | Freeform 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).
| Column | Aliases | Notes |
|---|---|---|
| part_number | pn | Required; matches existing to upsert |
| name | — | Required |
| domain | — | Default other |
| uom | unit, unit_of_measure | Default each |
| lifecycle | life_cycle | Default prototype |
| tags | — | Comma or pipe separated |
| attributes | attrs,json | JSON 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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/submit | — | Submit part version (engineer+) |
| POST | /v1/parts/:id/versions/:versionId/approve | — | Approve 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/submit | — | Submit BOM (engineer+) |
| POST | /v1/boms/:id/approve | — | Approve 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/import | multipart: 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-settings | — | Get 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.
| Domain | Typical Required Fields | Validation |
|---|---|---|
| mechanical | material, finish, tolerance | Server validates on part create/update |
| electronic | package, value, voltage_rating | Import wizard shows domain-specific warnings |
| electrical | wire_gauge, connector_type, voltage | Required fields enforced via JSON schema |
| chemical | cas_number, concentration, hazard_class | MSDS 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
- Authenticate via the web app and keep session cookies
- Create or join a workspace via invitation
- Initialize your user folder: POST /v1/users/init
- Start streaming updates: GET /v1/events?workspace=&username=
- Upload a model: POST /v1/upload
Configuration
Frontend behavior (e.g., branch handling) adapts to server config.
| Method | Path | Description |
|---|---|---|
| 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.
| Method | Path | Description |
|---|---|---|
| GET | /api/auth/config | Returns { requiresInvitation: boolean } |
| GET | /api/session | Returns 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Notes |
|---|---|---|---|
| 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.
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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/:filename | — | Range-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.
| Method | Path | Query | Description |
|---|---|---|---|
| GET | /signed-url | path=/v1/serve/...&ttlMs=60000 | Returns { 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.
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| GET | /v1/versions?path=/v1/serve/... | — | List versions for a file (with nearest comment message) |
| GET | /v1/timeline?path=/v1/serve/...&types=version,comment,task | — | Aggregated 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.
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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.
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| GET | /v1/trash?workspace=&username= | — | List trashed items |
| POST | /v1/trash/restore | { workspace, username, key } | Restore item |
Design Reviews
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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/:id | — | Soft delete task (creator or manager) |
Workspace Analytics
| Method | Path | Query | Description |
|---|---|---|---|
| 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
| Method | Path | Body / Query | Description |
|---|---|---|---|
| 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)