/api/v2 next to /api/v1. Nothing changes for a request you keep sending to /api/v1. Your API token works on both versions, and you can move one endpoint at a time.
This page lists what you need to change when you move a call from v1 to v2. Each endpoint page in the API reference documents the full v2 contract.
Before you start
1
Check your token's scopes
v2 splits some v1 scopes and adds new ones. See Scopes and request any you are missing from Origin One.
2
Treat every id as a string
All v2
id values are strings. See Identifiers.3
Update your error handling
A missing scope returns
403, not 401, and error code values are new. See Errors.4
Move each endpoint
Change the path from
/api/v1 to /api/v2 and apply the changes for that endpoint below.5
Move your webhook callbacks
v2 deliveries have a new body and signature. See Migrate webhooks to v2.
Endpoints
Most v1 endpoints have a v2 equivalent at the same path under/api/v2. Two are removed:
v2 adds these endpoints:
Scopes
Scope names keep theresource.action convention. These differ between versions:
Every other scope keeps its name. See Scopes for the full table.
Errors
A token that lacks the scope an endpoint requires gets403 Forbidden in v2. v1 returns 401 Unauthorized for the same request. In v2, 401 means only that the bearer token is missing, invalid, or expired.
A resource outside your token’s access no longer returns 403. Where v1 returns 403 for a location or package the token isn’t linked to, v2 returns 404 Not Found from a get endpoint and an empty page from a list filter such as locationId.
Every error a v2 endpoint returns, including 401, 403, and 404, has the same body as a validation error:
codevalues are new. v1 returns validation constraint names such asisNotEmptyandisString. v2 uses its own vocabulary, such asrequired,invalidType,invalidValue, andtooSmall. Don’t map v1 codes to v2 codes. Rely onstatusCode,source.pointer, anddetailinstead.sourcecan be omitted. It is present only on400errors that a specific request field caused.- The top-level
metafield is removed.
409 Conflict when a package name matches more than one package.
Identifiers
Every v2id is a string. In v1, company, location, package, and clinic ids are JSON numbers. The value stays the same: a location with "id": 456 in v1 has "id": "456" in v2. Order and service ids were already strings and don’t change.
The change applies wherever an id appears: response bodies, path parameters, query filters such as companyId and locationId, and request bodies such as locationId in Search clinics. Store ids as opaque strings. See Identifiers.
Response conventions
v2 applies the same rules to every resource. These change what v1 returns:- Every resource carries
object. Nested resources carry it too, includingaddress. See Resource type. - Every resource you can fetch carries
url, its canonical path. Follow it instead of building paths fromid. See Resource URLs. - Related resources are nested references, not flat fields. A location returns
company: { object, id, name, url }instead ofcompanyIdandcompanyName. - Empty values are
null. v1 can return an empty string for an absent address field. v2 returnsnull, and a field your token can’t see is omitted. See Null versus omitted. - Addresses have one shape:
object,line1,line2,city,state,postalCode, andcountry. Each value can benull. - Response enums are open. Values such as an order’s
statuscan gain new members. Handle values you don’t recognize. See Forward compatibility.
page and limit and return the { "data", "total", "page", "limit" } envelope. See Pagination.
Orders
Create an order
Create an order takes a reorganized body. Here is the same order in each version:
The
callback, orderMatching, and the remaining settings fields keep their names. settings.observedCollection must be true for a return-to-duty or follow-up test with a DOT urine service: v1 skips that check when the field is omitted, and v2 rejects the order. Registering a callback through v2 changes what its deliveries look like: see Migrate webhooks to v2.
Order responses
Create an order, Get an order, and List orders return the same v2 order. Cancel an order returns it too, without the participant’sidentifiers.
The v2 order also adds the order’s context, so you no longer need a second call for it:
url, agency, clinic, company, location, jobCategory, packages, participant, testReason, otherTestReason, createdAt, updatedAt, statusUpdatedAt, and expiresAt. Each service adds id, isDot, attributes, lab, specimenId, trackingNumber, courierServiceName, and screenHistory, and each statusHistory entry adds groupType.
participant.identifiers is present only for a token with the order.participant.read scope. services[].statusUpdatedAt can be null. See Order lifecycle for statuses and isFinal.
Searching orders
POST /api/v1/orders/search is replaced by GET /api/v2/orders, which takes query-string filters instead of a filter body:
v2 can’t filter orders by participant details (name, email, phone, date of birth, SSN, or driver license) or by
updatedAt. To follow status changes, register a callback when you create the order instead of polling.
The list has no implicit date window: it returns every order your token can see unless createdFrom or createdTo narrow it.
Cancel an order
Cancel an order keeps its path, its body, and its422 cases. It returns the v2 order without the participant’s identifiers: get the order for them.
Clinics
Search clinics
Search clinics returns only active clinics owned by your tenant.
Send
coordinates or address, not both. An address requires state and postalCode. In the response, clinicType and vendors are removed and isActive is added.
Clinic responses
List clinics, Get a clinic, and search results list a clinic’s services as references (object, id, name, type, url). services[].isDot and services[].attributes are removed from the clinic; fetch the service’s url for them.