Migrating from v2 to v4

The legacy v2 API shuts down on 1 Jan 2027. This page is the translation guide: what your key becomes, which capabilities replace your scopes, and which contracts change in ways your code will notice.

Work through it in order. The scope mapping is a security decision, not a find-and-replace.

The key changes shape

Legacy v2v4
PrefixGW.…grk_sk_…
Bound toA companyA user
ReachWhatever the company's key was grantedWhatever that user's role can see, across every company they administer
LifetimeIndependent of any personStops working if the bound user is disabled

There is no in-place upgrade. A GW. key cannot become a grk_sk_ key; you mint a new one. Sending the old key to /v4, /mcp or the CLI fails with 401 and "This API key is not bound to a user", which is the most common false alarm during a migration.

Bind the new key to a dedicated, non-personal user. A v4 key inherits its reach from the bound user's live claims. Bound to a manager, it is silently narrowed to that manager's teams and regions; bound to someone who later leaves, it stops working the moment their account is disabled.

Scope mapping

v2 gates each endpoint on its own scope string, around thirty of them. v4 gates on ten capabilities: a read and a write for each of employees, orders, inventory, teams and company.

Collapsing the first set into the second is a security change, not a rename. One v4 capability can cover several v2 scopes, so a key translated row-by-row usually ends up broader than the key it replaces. Grant only the rows your integration actually calls.

The starkest case is the delivery address: three separate v2 scopes collapse into the two employee capabilities, so a key that could previously only edit an address can now edit the whole employee record.

Employees

v2 scopev2 endpointv4 capability
api.employees.getAllGET /employeesmcp:employees:read
api.employee.getGET /employees/:idmcp:employees:read
api.employees.createPOST /employeesmcp:employees:write
api.employee.updatePATCH /employees/:idmcp:employees:write
api.employee.products.getGET /employees/:id/productsmcp:inventory:read
api.employee.products.createPOST /employees/products/productInUsemcp:inventory:write

Note the two crossings into inventory: reading an employee's assigned devices and registering an in-use product are inventory operations in v4, even though v2 filed them under employees. A key that only reads employee records no longer gets their devices for free.

Delivery address

v2 scopev2 endpointv4 capability
api.employee.deliveryAddress.getGET /employees/:id/deliveryAddressmcp:employees:read
api.employee.deliveryAddress.createPOST /employees/:id/deliveryAddressmcp:employees:write
api.employee.deliveryAddress.updatePATCH /employees/:id/deliveryAddressmcp:employees:write

The address is not a sub-resource in v4. It is a set of fields on the employee, read with GET /v4/employees/{id} and written with PATCH /v4/employees/{id}.

Teams

v2 scopev2 endpointv4 capability
api.teams.getAllGET /teamsmcp:teams:read
api.team.getGET /teams/:teamIdmcp:teams:read
api.teams.createPOST /teamsmcp:teams:write
api.team.updatePATCH /teams/:teamIdmcp:teams:write

Orders

v2 scopev2 endpointv4 capability
api.orders.getAllGET /ordersmcp:orders:read
api.orders.createPOST /orders/assignToEmployeemcp:orders:write
api.orders.createPOST /orders/offboardingmcp:orders:write
api.orders.createPOST /orders/collectionmcp:orders:write
api.orders.createPOST /orders/swapmcp:orders:write
api.orders.createPOST /orders/purchaseOrdermcp:orders:write

Products

v2 scopev2 endpointv4 capability
api.products.getAllGET /productsmcp:inventory:read
api.product.getGET /products/:productIdmcp:inventory:read
api.products.createPOST /products/uploadProductsmcp:inventory:write
api.product.updatePOST /products/addPinCodemcp:inventory:write

Products are inventory in v4. There is no mcp:products:* pair, because splitting them would put reading one device and searching your inventory behind different capabilities.

Company

v2 scopev2 endpointv4 capability
api.company.addresses.getGET /companyAddressesmcp:company:read
api.company.addresses.createPOST /companyAddressesmcp:company:write

Not migrated yet

These v2 scopes have no v4 successor. They stay valid against v2, so a client that uses them keeps its v2 key for those calls while moving everything else. The cutover is per endpoint group, not per API version.

v2 scopeWhy
api.catalogs.getAll, api.catalogs.items.getAllCatalogs and collections are outside the migrated resource groups
api.company.get, api.company.updateNo v4 company read or update operation
api.orders.updateNo v4 order-update operation
api.employee.offBoardingOffboarding is an order type in v4, covered by mcp:orders:write
api.organization.*The Organization API authenticates an organization across many companies; v4 has no organization-level principal

What changes in your code

Lists paginate, and truncate at 50

v2 list endpoints return a bare JSON array with everything in it. v4 returns an envelope with page, pageSize, totalCount and nextCursor, and caps a page at 50 records.

This is the change most likely to break a migrated client quietly: it truncates rather than errors, so a job that used to process every employee silently starts processing the first 50. Page with page and limit, and check totalCount.

One order endpoint instead of four

assignToEmployee, offboarding, collection and swap all become a single POST /v4/orders, discriminated by an orderType field (Deployment, Offboarding, Collection, Swap from Inventory). You switch from picking a URL to setting a field.

purchaseOrder is the exception: it maps to POST /v4/orders/purchase, because it buys from the catalog rather than moving inventory you already own.

Renamed and reshaped fields

v2v4Note
secondaryEmailpersonalEmailSame stored field, renamed at the edge. Passing null or "" still clears it
teamIdteamNamev4 addresses teams by name, creating the team on demand
deliveryAddress.* sub-resourceFields on the employeeRead and written through the employee endpoints
deliveryAddress.country (required)country (optional)v4 is looser here

v4 also adds fields v2 never had (department, division, jobTitle, taxId), plus a guard on PATCH /v4/employees/{id} that rejects an email change for an employee who already has an active login.

Capabilities are not hierarchical

mcp:orders:write does not imply mcp:orders:read. A client that places an order and then reads it back needs both. v2's per-endpoint scopes had the same property, but the coarser v4 names make it easy to assume otherwise.

Not yet available on v4

A few v2 behaviours have no v4 equivalent today. If you depend on one, keep that call on v2 for now:

  • Filtering employees by teamId on the list endpoint
  • ?invitation=true on employee read, which creates and returns an invitation URL
  • startDate on employee creation
  • addressTwo and phone on the delivery address at creation time

Reissue checklist

  1. Provision a dedicated, non-personal user in the company for the integration.
  2. Mint the key from the app's Developers → API Keys page as that user, selecting only the capabilities the mapping above calls for. The raw grk_sk_ key is shown once.
  3. Send it as X-API-KEY, not Authorization: Bearer, which on a v4 route is only ever a Firebase ID token.
  4. Verify with GET /v4/me, which needs no capability and echoes back exactly what the key resolves to.
  5. Update your calls for the contract changes above, pagination first. The Postman collection is the quickest way to rebuild and compare requests side by side: import it, paste your new key into the apiKey variable, and work through the endpoints you use.
  6. Keep the v2 key active until you have observed the new one working, then deactivate it.

If the integration you are moving is an AI assistant rather than a backend service, the same capabilities are reachable over MCP, and connect.growrk.com has the per-client setup steps for Claude, ChatGPT, Cursor and custom agents.

See Authentication & API keys for the full capability list and the v4 REST API for the endpoint reference.