Skip to content

APS Activation Runbook — taking the Model Derivative pipeline live (roadmap R1.1)

Status: DORMANT → ACTIVE runbook. The APS derivative pipeline is fully built and tested against fixtures, but no live Autodesk Platform Services credentials exist in any deployment, so every Autodesk-native upload is rejected at the door today.

Grounding: this runbook was written from the actual code — apps/server/src/modules/integrations/aps/client.ts, .../aps/derivative-job.ts, apps/server/src/config.ts, apps/server/src/modules/models/routes.ts, apps/server/src/jobs/reconcile-conversions.ts. Every step below cites what the code actually does.


1. What exists already (do not rebuild)

Capability Where
2-legged APS auth, OSS bucket create, signed-S3 upload (streamed), translate job, manifest poll, derivative download (file-streamed) apps/server/src/modules/integrations/aps/client.ts (ApsClient)
Full state machine: upload → translate → poll → download IFC → re-stage → local conversion apps/server/src/modules/integrations/aps/derivative-job.ts (runApsDerivative)
Queue + worker registration derivative-job.ts:125-133 (enqueueApsDerivative / registerApsDerivativeWorker), wired in apps/server/src/jobs/registry.ts:27,66
Upload gate + sourceType: 'aps' routing apps/server/src/modules/models/routes.ts:90-97 (rejection when unconfigured) and routes.ts:130,182-188 (branch that stages the source and enqueues)
Redelivery guard (at-least-once safety) derivative-job.ts:50-66
Bounded retries derivative-job.ts:126retryLimit: 2, retryDelay: 60, meaningful because runApsDerivative rethrows (derivative-job.ts:103-111)
Realtime progress setApsModel emits model:status with apsDerivativeStatus (derivative-job.ts:24-34) — the existing web UI needs zero changes

The code comment on client.ts:8-12 says it plainly: "Live translation is the only credential-blocked step." Everything else is exercised by tests.

2. Prerequisites

  • [ ] A deployment whose PUBLIC_URL and database/storage are production-shaped (the pipeline streams multi-GB files through temp dirs; see derivative-job.ts:68-70).
  • [ ] Operator access to the secrets store for the deployment env.
  • [ ] The worker process running — the derivative worker registers via registerApsDerivativeWorker() in jobs/registry.ts:66; it does not run inside the API process.

3. Step 1 — Create the APS app and credentials

  1. Sign in at https://autodesk.com/apis (APS developer portal) with the company Autodesk account.
  2. Create an app of type Server-to-Server (this pipeline uses only the client-credentials / 2-legged flow — client.ts:68-81, grant_type=client_credentials).
  3. Request/enable these services on the app:
  4. Model Derivative (translate + manifest)
  5. Object Storage Service / Data Management (bucket create, signed S3 upload/download)
  6. Webhooks only if ACC activation follows (see docs/runbooks/acc-procore-connect.md) — not needed for R1.1 alone.
  7. Record the Client ID and Client Secret. The default token scopes requested by getTwoLeggedToken are "data:read data:write data:create bucket:create bucket:read" (client.ts:69). If your APS account requires scope allow-listing per app, allow exactly those.
  8. Decide the OSS bucket key. Default if unset: bimzone-derivatives (derivative-job.ts:48, config.APS_OSS_BUCKET ?? "bimzone-derivatives"). Bucket policy created is transient (client.ts:88) — objects are deleted by Autodesk after ~24h, which is intended: the derived IFC is downloaded and stored in BimZone's own storage; APS OSS holds nothing durable.

Note: do NOT reuse the AUTODESK_CLIENT_ID / AUTODESK_CLIENT_SECRET sign-in OAuth pair here. Those are a separate 3-legged sign-in app (config.ts:105-110); the data pipeline uses its own server-to-server app.

4. Step 2 — Set environment variables

Exact names from apps/server/src/config.ts:127-131:

Env var Required Notes
APS_CLIENT_ID yes for activation optional in schema; its absence is what keeps the pipeline dormant — uploads of .rvt/.nwd/.nwc/.dwg are rejected with a 400 naming it (models/routes.ts:95-97)
APS_CLIENT_SECRET yes for activation ApsClient throws "APS_CLIENT_ID and APS_CLIENT_SECRET are required" without both (client.ts:60)
APS_BASE_URL no defaults to https://developer.api.autodesk.com (config.ts:130)
APS_OSS_BUCKET no defaults to bimzone-derivatives (derivative-job.ts:48)

Set them on BOTH processes that read config: API (upload route checks config.APS_CLIENT_ID) and worker (constructs ApsClient). Config is parsed and validated once at import (config.ts:189-198); a change means restart, not hot-reload.

Roll out: set vars in staging first, restart API + worker, confirm boot succeeds (an invalid value fails fast at first use, since the vars themselves are .optional()).

5. Step 3 — Webhooks/callback URLs

None required for R1.1. The pipeline polls the Model Derivative manifest (pollManifestUntilDone, client.ts:138-155: 5 s interval × 120 attempts ≈ 10 min ceiling). There is no callback URL to register for translation events. Webhook endpoints exist only for the ACC/Procore adapters — see the sibling runbook.

5b. Executed 2026-09-05 — client-level run against the live APS tenant

apps/server/scripts/aps-runbook.ts drives the exact client the worker uses (token → bucket → signed S3 upload → translate ifc+svf2 → manifest poll → IFC derivative download) against a local file and writes an evidence record. Run with the deployment's APS_* env plus the usual DATABASE_URL/ JWT_SECRET/APP_SECRET:

npx tsx scripts/aps-runbook.ts <file.rvt> <evidence.json>

Evidence: docs/runbooks/evidence/aps-2026-09-05.jsonrac_basic_sample_project.rvt (17.1 MB, the public Autodesk sample): token 0.5 s · bucket 1.3 s · upload done at 57.6 s · translate accepted at 59.3 s · manifest success (svf2 + thumbnail + ifc) at 157.3 s · derived IFC 19.7 MB downloaded at 159.4 s.

Defect the first run surfaced (fixed in the same commit): the streamed S3 presigned PUT went out with Transfer-Encoding: chunked and no length, and S3 answered 501. ApsClient.uploadToOss now requires a contentLength for stream bodies and sends Content-Length; derivative-job.ts passes the staged file's size. Unit-tested in tests/aps-client.test.ts. Sources over 5 GB still need the multi-part (?parts=N) variant — not built; the current 5 GB/part cap is noted in the client.

Still open from §6: steps 2, 3, 5–8 need the whole stack (API + worker + storage) on a staging deployment; the 30-day production sync (P2-12) is a calendar item once one account is linked.

6. Step 4 — Staging verification checklist

Run end-to-end in staging before production:

  1. [ ] Upload gate flips. POST /api/projects/:projectId/models with a small .rvt returns 201 (previously 400 "…requires Autodesk Platform Services"). Row shows sourceType: 'aps', status: processing, apsDerivativeStatus: queued (models/routes.ts:182-188).
  2. [ ] Job picked up. Worker log shows the pg-boss job from queue QUEUES.apsDerivative (singletonKey = modelId).
  3. [ ] State transitions observable. Via websocket model:status or the models list, watch: queued → uploading → translating → converting (derivative-job.ts:72-96). Confirm apsUrn is populated on the row after upload (base64url of the OSS objectId, client.ts:63-66).
  4. [ ] IFC handoff. After the manifest reports success, findDerivativeUrn(manifest, "ifc") resolves and the IFC downloads to disk (downloadDerivativeToFile, client.ts:172-185). The derived IFC is staged under storageKey.modelIfc(...) and ifcKey is rewritten; the original RVT blob is best-effort deleted (derivative-job.ts:91-101).
  5. [ ] Conversion completes. enqueueConversion runs the existing IFC→frag pipeline; model ends status: ready, viewer renders.
  6. [ ] Auto-clash fires. On ready, scheduleAutoClash runs (modules/models/conversion.ts:175) and enqueues clash-run jobs for eligible auto-clash tests bound to the version group (lib/auto-clash.ts:21-55).
  7. [ ] Retry path. Simulate a transient failure (e.g. revoke the secret mid-flight once): the job should retry up to 2 more times at 60 s spacing, and the redelivery guard must resume rather than restart once past translating (derivative-job.ts:63-66).
  8. [ ] Failure path. Upload a corrupt file that fails translation: row lands status: failed, apsDerivativeStatus: failed with the manifest error text surfaced via describeManifestError (client.ts:207-213); UI offers retry.

7. Rollback & failure modes

  • Kill switch: unset APS_CLIENT_ID (+ restart). New Autodesk-file uploads immediately 400 (models/routes.ts:95-97). In-flight jobs will fail at new ApsClient() and exhaust their bounded retries into failed — acceptable and visible.
  • Vendor incident: there is no separate flag for APS alone; the global outbound-integration kill switch INTEGRATIONS_ENABLED (config.ts:126) governs the integration sync workers, not the model pipeline. Treat credential removal as the APS off-switch.
  • Stuck derivatives — the known monitoring gap. The startup reconcile sweep explicitly EXCLUDES APS models: notInArray(models.sourceType, ["pointcloud", "aps"]) (jobs/reconcile-conversions.ts:131). The comment (:126-130) explains why: re-enqueueing an APS model through the IFC path would convert the binary RVT source and flap failed→ready. "Their quiet-period recovery belongs to the APS job" — i.e. recovery rests entirely on pg-boss retries (retryLimit: 2). Gap: after 3 attempts, nothing sweeps an APS model stranded in processing/translating by a crash or a >10-minute translation. Until a dedicated APS sweep exists, monitor manually:
  • Query: models where sourceType='aps' AND status non-terminal AND updated_at older than 15 minutes (same liveness signal the IFC sweep uses, reconcile-conversions.ts:33-43).
  • Remediation per state: stuck at converting/done → safe to re-enqueue conversion directly (the guard itself proves idempotence, derivative-job.ts:57-66); stuck earlier → requeue the derivative job (singletonKey dedupes) or fail the row so the user sees the retry affordance.
  • Data-loss hazard (already guarded, do not weaken): after converting, the original RVT/NWD is deleted; a naive retry would destroy the model. This is precisely what the redelivery guard protects (derivative-job.ts:50-62).

8. Go-live checklist

  • [ ] Production secrets set on API + worker; both restarted; boot green.
  • [ ] One real .rvt uploaded in production by a test project → reaches ready, viewer renders, auto-clash scheduled.
  • [ ] One deliberate failure exercised (bad file) → clean failed state, error message readable.
  • [ ] Stuck-model monitoring query/alert scheduled (Section 7 gap) — or a ticket filed for the dedicated APS reconcile sweep before volume arrives.
  • [ ] APS portal: spend limits / usage alerts configured (translation is metered per GB).
  • [ ] Client secret stored in the secrets manager only; never committed (gitleaks runs in CI, .github/workflows/security.yml).
  • [ ] This runbook updated if any cited line moves — scripts/check-control-matrix.mjs will catch drift only for paths cited in the SOC 2 matrix, not here.