Migration Playbook and FAQ

This page is the recipe for moving a working V1 integration over to V2 on a JS Vision-migrated company, plus answers to the common questions.

👍

You can migrate incrementally

V1 and V2 run side-by-side against the same data. You can migrate one caller at a time — there is no big-bang requirement. Pick V2 for new code, keep V1 for anything you don't want to touch. There is no deprecation pressure on V1.


If you're staying on V1, here's what you still need to do

Even if you have no intention of migrating to V2, the underlying company switch to JS Vision changes one thing in V1 responses: the shape of newly-issued shift IDs.

Before migrationAfter migration
Shift IDs are 24-char BSON: 507f1f77bcf86cd799439011New shifts return slot IDs (~61 chars, with :): 507f1f77bcf86cd799439011:7a4e8a18-...

Your existing stored IDs continue to work on V1 — the server keeps a legacyShiftId mapping and resolves them automatically. But your storage has to be ready for the new format:

  • Widen any 24-char ID column to VARCHAR(64) or TEXT (or remove the BSON type constraint) before the company is migrated. Otherwise new IDs silently truncate.
  • Don't mix legacy and slot IDs in a single bulk request. V1 returns 400 Bad Request ("must be all new format ids ... or all legacy ids") on mixed lists. Batch them by type.

That's the entire V1 maintenance burden on a migrated company. The rest of this page is only relevant if you're moving callers from V1 to V2.


Before you start (V2 migration)

  1. Confirm the company is migrated. Call any V2 endpoint:

    curl --request GET \
      --url 'https://api.connecteam.com/scheduler/v2/schedulers/{schedulerId}/shifts?startTime=1736899200&endTime=1737504000' \
      --header 'X-API-KEY: YOUR_API_KEY'

    If you get a 403 with "V2 endpoints require JS Vision migration. Contact support to migrate.", stop here and contact Connecteam Support.

  2. Decide your scope. Identify which callers to migrate first. Read-only list-shifts callers are usually the lowest-risk starting point.


Step 1 — Update your shift ID storage

Any shift IDs your system has saved from V1 (slot IDs, with a :) need to be converted to base IDs before calling V2. To convert, strip everything from the first : onward:

base_id = slot_id.split(":")[0] if ":" in slot_id else slot_id
⚠️

Watch out for key collisions

Slot IDs from a single base shift all share the same prefix. A slot-ID-keyed table that tracks group members will collapse to fewer rows when keyed by base ID. Plan for that before the data migration so you don't lose per-user state.


Step 2 — Update list-shifts callers

GET /v2/.../shifts returns one row per base shift, not one row per user. Expect:

  • Fewer rows for the same query (one per group, not one per user).
  • A non-empty assignedUserIds array on group shifts — loop over it instead of treating each row as a separate user shift.
  • Pagination semantics changed: limit=10 no longer means "10 user-shifts", it now means "10 base shifts". If your code does "fetch a page, count users on the page" — re-check that math.

Step 3 — Update create callers

You now have new capabilities on POST /v2/.../shifts:

  • Multiple assignedUserIds in a single create — one request body item creates one group shift with everyone on it.
  • openSpots > 1 on open shifts (up to 100 per shift).

Existing single-user creates work without changes once you call the V2 path.


Step 4 — Update update callers

🚧

This is the section that breaks most often — read carefully

  • A simple property change (title, time, location, etc.) on a group base ID applies to every user in the group by default. You do not need to set isEditForAllUsers: true — that's the V2 default for non-open group shifts when no assignedUserIds is provided.
  • To edit a single user inside a group, set isEditForAllUsers: false and pass assignedUserIds with the user you want to change.
  • You can now bulk-toggle openSpots directly in PUT.
  • The response can now contain createdShifts and deletedShiftIds arrays when assignedUserIds changes. Handle both: store the new slot IDs (or base IDs, depending on how you key your storage) and drop references to the deleted ones.

Step 5 — Update delete callers

  • Pass base IDs to V2 delete (DELETE /v2/.../shifts/{baseId} or DELETE /v2/.../shifts with a base ID array).
  • Deleting by base ID removes the entire group — every user's slot is gone.
  • "Delete one user from a group" cannot be done with V2 DELETE. Use V2 PUT with the remaining users, OR use V1 DELETE with the specific slot ID.

Step 6 — Test and roll out

👍

Sanity-check checklist

Run these four checks against your real (low-traffic) scheduler before flipping production traffic to V2:

  1. Create a 3-user group shift via V2 and verify the response has one shift with assignedUserIds.length === 3.
  2. Update its title via V2 with only { shiftId, title } (no isEditForAllUsers). Then GET it back and verify all three users still see the new title.
  3. Update assigned users via V2 by swapping one user. Verify the response includes the expected entries in createdShifts and deletedShiftIds.
  4. Delete the base ID via V2. Verify all three slots are gone (a follow-up GET returns 404).

If all four pass, the rest is mechanical.


FAQ

📘

What about webhooks?

Outbound webhooks have their own V1/V2 versioning, configured separately when you set up the integration. The REST API V1/V2 split documented in this section does not affect which webhook payload version you receive. See the existing Webhooks guides for details.

📘

Can I use V1 and V2 simultaneously?

Yes. They share the same data. An ID returned by one path can be converted to the format the other expects (slot_id.split(":")[0] to go from V1 to V2). The two paths are not mutually exclusive — pick V2 for new code, keep V1 for anything you don't want to touch.

📘

Do repeating shifts behave differently?

Both V1 and V2 currently reject API edits to repeating shifts. This is a known limitation and not specific to Vision.

📘

What's the "root open shift with multiple open spots" error?

This V1-only error appears when you try to edit an open shift that has multiple unclaimed spots. V1's data model cannot express "edit all spots of a multi-spot open shift" — use V2 to do this safely.

📘

Will V1 ever be deprecated on Vision companies?

Not in the foreseeable future. V1 is the stable backward-compatibility surface and will keep working as-is. V2 is the recommended path for new integrations, especially those that need group shifts or multi-spot open shifts. We will give explicit notice well in advance of any deprecation.

📘

Are there V2 endpoints for things other than shifts?

Currently only /shifts and the related auto-assign endpoints are versioned. Scheduler listing, unavailabilities, shift layers, custom fields, and user-unavailability remain V1-only. You can call them under /scheduler/v1/... regardless of whether the rest of your integration is on V2.

📘

Do my pre-migration shift IDs still work on V1?

Yes. Every 24-char BSON shift ID issued before the company was migrated is still accepted by V1 — the server keeps a mapping (legacyShiftId) from the old ID to the new slot it lives in, and resolves it automatically. You don't need to convert or re-fetch anything for existing references.

The only thing that changed: new shifts (created after migration) come back from V1 as slot IDs (bson:uuid, ~61 chars). So as you create new shifts, your storage starts receiving the longer format alongside any legacy IDs you already have.

📘

Why does V1 reject a base ID (no :) of a post-migration shift?

A bare BSON ID is ambiguous on Vision: it could be a base shift ID (which maps to a group of multiple slots) or a pre-migration legacy ID (which maps to one specific slot). Rather than guessing and potentially returning the wrong data, V1 only resolves bare BSON IDs via the legacyShiftId lookup. If you pass a base ID of a shift that was created after migration, V1 returns 404 Not Found because there's no legacyShiftId match. To address post-migration shifts on V1, use the slot ID (bson:uuid) you got back when the shift was created.


See also