Ping fails or the connection is refused
The API site, the IIS web server, or the network is down entirely.
Check network connectivity, routing, and the IIS host status. If the site itself is down, contact B2W Support.
B2W API Help · Ops API
How to reach your Ops API, get the right credentials, generate a bearer token in Postman, make your first calls, and solve problems when a call fails. Paste your Ops URL into the card to fill in every example.
What the Ops API is, what every call is made of, and how to find your environment’s API and its documentation.
The Ops API is how other software reads and writes B2W Ops data, such as jobs, employees, and equipment, without anyone opening an Ops screen. It is a REST API that sends and receives JSON, and every B2W environment has its own.
An API (application programming interface) is a published way for programs, not people, to ask another system to do something over a network. Think of it as three parts of the same building:
Stores the raw data and permanent records behind the scenes.
Screens that people use to find and change records.
A written menu that outside software uses to request records.
Send requests to the API.
Checks permissions and validation on every call.
Only changed through those rules.
Companies use it to bring data in from ERP systems, pull data out for reporting dashboards, connect field telematics, and automate repeatable tasks. B2W uses the same API internally for native job imports and true-up batches.
It enforces the same security permissions and database validation rules as the application. Nothing bypasses them to write to the database directly, so Ops stays the system of record.
Anything you can do in the application, for the functional areas the API exposes, within the permissions of whoever signed in. The version 26.1.2.1 catalog lists 63 functional areas and 272 operations (see question 16).
Some integrations still use the older SOAP Web Services. They are a different product, so check which one you are working with first.
| Ops API 2.0 (REST) | Legacy Web Services (SOAP) | |
|---|---|---|
| Address | One URL per resource, such as /Employee | .asmx service URLs |
| Methods | GET POST PUT DELETE | POST for every call |
| Data format | JSON | XML envelopes, with the operation name inside |
| Querying | OData: $filter, $select, $orderby, $top, $skip | Parameters inside the XML |
| Page size | Up to 100 records per GET | Not applicable |
Use the Ops API wherever you can. In one example, adding 10 employee records took 10 separate SOAP calls, while the Ops API handled the same work in one call.
Every call has an address (the URL), a method (GET, POST, PUT, or DELETE), headers (including who you are), and for create and update calls, a JSON body. Every response comes back with a status code and, usually, JSON data.
| Part | What it does | Example |
|---|---|---|
| 1. Address | Where the request goes: your environment’s server and the resource | https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/Employee |
| 2. Method | The action to take | GET read · POST create · PUT update · DELETE remove |
| 3. Headers | Extra instructions, including who is calling | Authorization: Bearer <token>Content-Type: application/json |
| 4. Body | The data you send, as JSON, when you create or update | { "LastName": "Newman", … } |
The four methods map to the four basic data actions, often called CRUD: Create (POST), Read (GET), Update (PUT), and Delete (DELETE). Here is a complete create call with every part labeled:
POST /OpsAPI_<environment>/Employee HTTP/1.1 # 1 address + 2 method
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken> # 3 headers
Content-Type: application/json
{ # 4 body
"EmployeeID": "90001",
"LastName": "Employee",
"BusinessUnitUniqueName": "Organization"
}
The response always carries a status code, such as 201 Created or 401 Unauthorized, and plain text shaped as JSON. You will see the same responses in Postman, in Swagger, and in integration logs. Question 20 explains every code.
Every cloud API URL follows one formula: https://{cluster}/OpsAPI_{environment}. Take your Ops site address and put OpsAPI_ in front of the environment name. For <environment>, that gives https://<cluster>.b2w.trimble.com/OpsAPI_<environment>.
<cluster>.b2w.trimble.com.https://{cluster}/{environment}. Your API is the same address with OpsAPI_ added before the environment.opsapi_<environment> works the same as OpsAPI_<environment>.| For <environment> | Address |
|---|---|
| Ops website | https://<cluster>.b2w.trimble.com/<environment> |
| Ops API (base URL) | https://<cluster>.b2w.trimble.com/OpsAPI_<environment> |
| API docs (Swagger) | https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/doc/index.html |
EstAPI_ (Estimate API) and MRAPI_ (Management Reporting API) addresses are different APIs with their own credentials. Each has its own guide; switch to it at the top of the page.
Paste your Ops address. The checker builds all three API addresses and sends a sample request to each one to see whether it answers.
If the environment name is wrong, the server still responds, but with an HTML “404” error page instead of a result. If /Ping/hello returns that page, recheck the spelling and the OpsAPI_ prefix.
Add /doc/index.html to the end of the Ops API URL. Each environment hosts its own Swagger catalog listing every endpoint, the fields it accepts, and the codes it returns.
https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/doc/index.htmlOpenScroll down to API Documentation to see every functional area. Click an area to expand its operations, then click an operation to see its parameters, example values, and response codes.
/schema call.The same catalog is also available as a machine-readable OpenAPI 3 file at /doc/v2/OpsAPI.json. Postman can import it to generate requests, but the B2W Ops API collection in question 6 already has authentication set up, so start there.
Install Postman, import the B2W Ops API collection, pick a login method, and find the credentials it needs.
Download the free Postman desktop app from postman.com/downloads, install it, and sign in.
Postman sends requests and shows responses in a friendly window. Anything you do in it, you can also do from PowerShell or cURL. Where it helps, this guide shows all three.
Download the B2W Ops API collection (a JSON file), then import it into Postman with Ctrl+O. It appears as B2W Ops API in your sidebar, with a request for every area of the API.
B2W-Ops-API.postman_collection.json.Download the Postman collection
It only groups the login requests. Each one calls GET /Login (or GET /LoginWithTID) with a different pair of credential headers.
Use a TID UUID and User API Secret when calls should act as a specific person. That keeps an audit trail and follows their security role. Use a client ID and client secret for unattended integrations, which run as the built-in System Administrator. Username and password still work but are being retired.
Every call except /Ping and /Version needs proof of identity. You prove it once, by sending credentials to the login endpoint in HTTP headers, never in the URL or the JSON body. The response gives you an access token to use on every other call.
| Method | Headers to send | Calls run as | Use it for |
|---|---|---|---|
| TID UUID + User API Secret Recommended |
tiduuidapiSecret |
That specific Ops user, limited by their security role | People testing the API, and integrations that need an audit trail of who changed what |
| Client ID + client secret | clientIdclientSecret |
The built-in System Administrator (elevated access) | Unattended integrations, such as scheduled overnight imports and middleware |
| Username + password Being retired |
userNamepassword |
That Windows / Ops user | Older integrations. Plan to move them to TID or client credentials. |
| LoginWithTID Separate endpoint |
A Trimble ID access token as the bearer token | The Ops user linked to that Trimble ID | Apps where the person already signed in with Trimble ID |
A client ID and secret give system-administrator-level access. TID keeps an audit trail of what was changed and takes on the persona of the user, following the limits set in their security role.
GET /Login looks for one complete pair of credentials, in this order, and uses the first pair it finds:
userNamepasswordclientIdclientSecrettiduuidapiSecretGET /Login returns 400 with {"Error":"Authentication information must be specified in the request header"}. That is the exact response the API gives.The userName header must match the Windows account name stored on the Ops user, written as DOMAIN\user or user@domain. Ops checks the format, verifies the password with the Windows domain controller, then confirms the account exists in the Ops user table. The token it issues is tied to that person, so every action is traceable to them.
This is a different endpoint, GET /LoginWithTID. Instead of a credential pair, you send a Trimble ID access token that the person got by signing in to Trimble ID. Ops exchanges it for an Ops access token.
The caller’s existing Trimble ID token.
Pulls out the user ID and email.
Linked to the matching Ops person.
A 404 here means Trimble accepted the person, but no Ops user matches their Trimble ID or mobile email address. Fix the Ops user record (its TID / Mobile E-mail Address). Do not reset the password; the Trimble sign-in is already valid.
Client ID/secret and Trimble ID logins work when the site runs in Trimble Identity mode. To check a site, open /SystemInfo. A site in Trimble Identity mode returns "IsTIDEnabled": true.
Whichever way you sign in, every call is still checked against the API permissions and Ops validation rules.
In B2W Ops, open your user record and click User API Secret. The dialog shows your TID ID, which is the tiduuid, and your User API Secret, which is the apiSecret.
The User API Secret dialog opens:
tiduuid. Put it in the tidUuid variable in Postman.apiSecret. Put it in the apiSecret variable.If an integration already uses this user’s secret, coordinate with its owner before generating a new one.
You may see this pair written as “tidUID and apiSecret”. In the login headers they are tiduuid and apiSecret. In the Postman collection they are the tidUuid and apiSecret variables.
In B2W Ops, go to Integrations & Add-ins and click Manage API Key. That dialog holds the client ID and client secret for the environment.
clientId and clientSecret variables, then send Auth → Login with client ID and secret.These credentials log in as the built-in System Administrator, with full access to your data. Share them only through a secure channel, and only with people who need them.
Ops validates logins against the key stored in its settings. If a new key is generated, integrations still using the old secret start failing with 401 until they are updated.
When to use it: scheduled overnight jobs and middleware that shouldn’t depend on one person’s credentials. Two responses are specific to this method:
Click the B2W Ops API collection itself (the top level), open the Variables tab, and fill in baseUrl plus the credential pair you are using. Save with Ctrl+S.
https://<cluster>.b2w.trimble.com/OpsAPI_<environment>.| Variable | What goes in it | Example or default |
|---|---|---|
baseUrl | Your Ops API URL | https://<cluster>.b2w.trimble.com/OpsAPI_<environment> |
accessToken | The AccessToken from the login response | Filled in after you log in |
tidUuid · apiSecret | TID ID and User API Secret (question 8) | Your own values |
clientId · clientSecret | From Manage API Key (question 9) | The environment’s values |
userName · password | Windows account credentials (being retired) | DOMAIN\user |
trimbleToken | A Trimble ID access token, for LoginWithTID | Optional |
objectId | The ObjectID of a record to update or delete | a4066d42-4027-4d5d-87a0-a30600f8d4cf |
pageSize | Records per page | 100 |
pingInput | Text that /Ping echoes back | hello |
trueUpBatchNumber · categoryType · jobNumber · maintenanceRequestId | Inputs for specific requests in those folders | As needed |
Don’t share or export the collection while apiSecret, clientSecret, password, or accessToken hold real values. Clear them first.
Get a token, use it, check connectivity, query data, and make changes safely.
Send GET {baseUrl}/Login with your credentials in the headers. The AccessToken in the JSON response is your bearer token. In Postman: Auth → Login with TID uuid and API secret → Send.
GET request to {{baseUrl}}/Login.tiduuid and apiSecret pull their values from your variables.accessToken variable before you call other endpoints.{{baseUrl}}/LoginHeaders
tiduuid
{{tidUuid}}
apiSecret
{{apiSecret}}
Body
none
Login never takes a body.
$baseUrl = "https://<cluster>.b2w.trimble.com/OpsAPI_<environment>"
# Credentials go in the headers, never in the URL or the body
$login = Invoke-RestMethod -Method Get -Uri "$baseUrl/Login" -Headers @{
tiduuid = "<your TID ID>"
apiSecret = "<your User API Secret>"
}
$token = $login.AccessToken # this is your bearer token
curl -s 'https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/Login' \
-H 'tiduuid: <your TID ID>' \
-H 'apiSecret: <your User API Secret>'
GET /OpsAPI_<environment>/Login HTTP/1.1
Host: <cluster>.b2w.trimble.com
tiduuid: <your TID ID>
apiSecret: <your User API Secret>
A successful login returns 200 OK and a small JSON object:
{
"AccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi…",
"RefreshToken": "<refresh token>"
}
Security:JWT:Lifetime controls this.RefreshToken. The catalog has no refresh endpoint, so when your token expires, simply log in again.Is a token expired, or whose is it? Paste it into the . It shows when the token was issued and expires, and who it belongs to. It decodes the token on this page only and clears it when you close the window.
Anyone holding it can act as you until it expires. Don’t paste tokens into emails, chats, support cases, or screenshots.
Send it in the Authorization header as the word Bearer, one space, then the token. Add Content-Type: application/json when the call has a body.
Passwords, client secrets, and Trimble tokens go here only.
Lasts 1 day by default.
Required on every other call.
Signing in and calling data are separate steps. Your secrets only ever go to the login endpoint; every other call carries the token instead. Here is a first data call that reads five employees:
{{baseUrl}}/Employee?$top=5Authorization
Type
Bearer Token, inherited from the collection
Token
{{accessToken}}
$headers = @{ Authorization = "Bearer $token" }
# For GET, -Body becomes the query string: /Employee?$top=5
$employees = Invoke-RestMethod -Method Get -Uri "$baseUrl/Employee" -Headers $headers -Body @{ '$top' = 5 }
$employees | Select-Object EmployeeID, FirstName, LastName
curl -s -G 'https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/Employee' \
--data-urlencode '$top=5' \
-H 'Authorization: Bearer <AccessToken>'
GET /OpsAPI_<environment>/Employee?$top=5 HTTP/1.1
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken>
The response is a JSON array of employee records. This sample comes from the catalog’s own example:
[
{
"ObjectID": "a4066d42-4027-4d5d-87a0-a30600f8d4cf",
"EmployeeID": "12865",
"FirstName": "Ralph",
"LastName": "Newman",
"JobTitle": "Field Employee",
"IsFieldEmployee": true,
"IsInactive": false,
"BusinessUnitUniqueName": "Northern Division\\Paving",
"RowVersion": 1
}
]
The collection’s Authorization tab sends {{accessToken}} as a bearer token, and its requests inherit that setting. If a call returns 401 right after you logged in, check that accessToken holds your newest token and that the request’s Authorization tab is set to inherit from its parent.
Authorization: Bearer eyJhbGciOi… uses the word Bearer, a single space, and the token with no quotes around it.
Open {baseUrl}/Ping/hello in a browser. If you get the server name back, the API is reachable. /Version returns the API version. Neither one needs a login.
| Call | Example response (version 26.1.2.1) | What it tells you | Open |
|---|---|---|---|
GET /Ping/hello | (server-name) hello| | The site is up. It echoes your text with the name of the server that answered. | Open |
GET /Version | 26.1.2.1 | The Ops API build the environment is running. | Open |
GET /SystemInfo | {"IsTIDEnabled":true,"IsEMSLicensed":false} | Whether the site runs in Trimble Identity mode. In version 26.1.2.1 it also answered without a login. | Open |
| You see | It means |
|---|---|
| The server name and your text | The site and network are healthy. Move on to the login. |
| A timeout or “connection refused” | The API site, the IIS web server, or the network is down. Check connectivity, routing, and the IIS host. |
| An HTML “404” page | The server is up but that address doesn’t exist. Check the environment name and the OpsAPI_ prefix. |
401 on /Employee or /Employee/schema | Expected without a token. Every data call, including /schema, needs one. |
In the Postman collection, the Ping request uses the pingInput variable, which defaults to hello.
GET endpoints accept OData query options: $filter, $select, $orderby, $top, and $skip. Each call returns at most 100 records, so use $skip to fetch the next page.
| Option | What it does | Example from the catalog |
|---|---|---|
$filter | Returns only matching records | $filter=EmployeeID eq '12865' |
$select | Returns only the fields you list | $select=EmployeeID |
$orderby | Sorts the results | $orderby=EmployeeID, LastName, FirstName asc |
$top | Limits how many records come back (100 at most) | $top=1 |
$skip | Skips records, for paging | $skip=1 |
# One employee by ID
{{baseUrl}}/Employee?$filter=EmployeeID eq '12865'
# Just three fields, sorted by last name
{{baseUrl}}/Employee?$select=EmployeeID,FirstName,LastName&$orderby=LastName
# Active jobs, page 2 (records 101-200)
{{baseUrl}}/Job?$filter=IsActive eq true&$orderby=JobNumber&$top=100&$skip=100
| Page | Add to the URL | Records |
|---|---|---|
| 1 | $top=100&$skip=0 | 1–100 |
| 2 | $top=100&$skip=100 | 101–200 |
| 3 | $top=100&$skip=200 | 201–300 |
Keep going until a page comes back with fewer than 100 records. Sort with $orderby so the pages stay in a stable order.
A GET that returns exactly 100 records has hit the page size, not the end of the data. Ask for the next page before assuming records are missing.
LastName eq 'Newman'.eq, ne, gt, ge, lt, and le. Combine conditions with and and or.&. Postman and browsers encode the spaces for you.$filter for you.POST creates, PUT updates, and DELETE removes. Before you write anything, GET a record from the same area so you can copy its exact shape.
Write calls change real data. If you have a test or sandbox environment, try them there first, and never run them against someone else’s environment unless they asked for the change.
Send the new record as JSON. For an employee, the catalog requires EmployeeID, LastName, and BusinessUnitUniqueName. A success returns 201 Created; a body that isn’t a valid record returns 422.
{{baseUrl}}/EmployeeBody · raw · JSON
EmployeeID
"90001"
FirstName
"Test"
LastName
"Employee"
BusinessUnitUniqueName
"Organization"
IsFieldEmployee
true
$body = @{
EmployeeID = "90001"
FirstName = "Test"
LastName = "Employee"
BusinessUnitUniqueName = "Organization"
IsFieldEmployee = $true
} | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "$baseUrl/Employee" -Headers $headers -Body $body -ContentType "application/json"
curl -s -X POST 'https://<cluster>.b2w.trimble.com/OpsAPI_<environment>/Employee' \
-H 'Authorization: Bearer <AccessToken>' \
-H 'Content-Type: application/json' \
-d '{"EmployeeID":"90001","FirstName":"Test","LastName":"Employee","BusinessUnitUniqueName":"Organization","IsFieldEmployee":true}'
POST /OpsAPI_<environment>/Employee HTTP/1.1
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken>
Content-Type: application/json
{
"EmployeeID": "90001",
"FirstName": "Test",
"LastName": "Employee",
"BusinessUnitUniqueName": "Organization",
"IsFieldEmployee": true
}
The catalog’s rule: first GET the record by its key, change the fields you need, then PUT the whole record back. Keep its ObjectID and RowVersion exactly as you received them. A success returns 200 OK.
# 1. GET the current record
$record = Invoke-RestMethod -Uri "$baseUrl/Employee" -Headers $headers -Body @{ '$filter' = "EmployeeID eq '90001'" } | Select-Object -First 1
# 2. Change only what you need
$record.JobTitle = "Field Employee"
# 3. PUT the whole record back, ObjectID and RowVersion included
Invoke-RestMethod -Method Put -Uri "$baseUrl/Employee" -Headers $headers -Body ($record | ConvertTo-Json -Depth 5) -ContentType "application/json"
If a PUT returns 409 Conflict, the update clashes with the record’s current state, for example because someone changed it after you read it. GET it again and reapply your change.
Send the record’s ObjectID as a query parameter or as a header named ObjectID. A success returns 204 No Content.
DELETE /OpsAPI_<environment>/Employee?ObjectID=<ObjectID> HTTP/1.1
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken>
If you want to put data into Ops, run a GET in that area first. It shows you the full outline of a record, and you can build your POST around it. GET /{Area}/schema returns the formal JSON schema too (it needs a token).
The version 26.1.2.1 catalog lists 63 functional areas and 272 operations. Most areas support GET, POST, PUT, and DELETE plus a /schema call. Search the list below, then open Swagger for the fields.
Each card’s Build button opens that endpoint in the , which writes a ready-to-run request for Postman, PowerShell, cURL, or raw HTTP.
The list reflects the version 26.1.2.1 catalog, checked October 2, 2026. Environments on other versions may differ, and their own Swagger page is always the source of truth.
A repeatable way to find what’s wrong, an error decoder, an interactive troubleshooter, and fixes for the errors you will see most.
Ask the five core diagnostic questions in order. They separate the API surface, the connection, the login, and the data call, so you find the failing step quickly.
Is it the Ops API (JSON) or the older Web Services (SOAP XML)?
Is the base URL right, and does GET /Ping/hello succeed? Always check connectivity first.
Which sign-in method does the call use: user (username and password), TID, or client ID and secret?
Does the login return a token? Look at the login’s status code and body separately from the data call’s.
Does the data call carry the correct AccessToken in an Authorization: Bearer header?
GET /JobScreenshots or logs of the request and response help, with credentials and tokens hidden. Never send a client ID, secret, password, or token by email, chat, or support case.
Send the details above to support_b2w@trimble.com or call +1 (888) 390-8822. The error decoder’s Copy a summary button writes most of it for you.
Paste the error into the decoder: a status line, a response body, a Postman or PowerShell error, or the request URL. It works out which API and call the error came from, names the likely cause, and links to the fix.
The URL tells the decoder the API, the environment, and the endpoint. It then checks the endpoint name against the catalog, and Check it runs the environment checker on that environment.
The decoder reads the messages the Ops API sends, such as the InternalMessage on a 403 that names the missing privilege, plus errors from Postman, PowerShell, IIS, and the network. Decoding runs entirely in your browser. Still, take tokens and secrets out of anything you copy into an email or support case.
Answer a few questions about what you see. The troubleshooter walks the five diagnostic questions and lands on the likely cause and fix.
Prefer to read it all at once? Every cause and fix the troubleshooter can reach is also listed in question 21.
2xx means it worked. 4xx means the request was wrong: bad input, a bad or missing token, missing permissions, or something not found. 5xx means Ops hit an unexpected error. The same code can mean different things on the login call and on a data call.
| Code | Meaning | On /Login | On a data call |
|---|---|---|---|
| 200 OK | Success | Token issued | Data returned or record updated |
| 201 Created | Record created | – | A POST succeeded |
| 204 No Content | Done, nothing to return | – | A DELETE succeeded |
| 400 Bad Request | Incomplete or malformed request | No credentials in the headers, or the user name is invalid | Missing parameters or a malformed query |
| 401 Unauthorized | Unsigned caller or bad token | Credentials rejected | Bearer token missing, invalid, or expired |
| 403 Forbidden | Not enough privileges | – | The security role lacks that API permission |
| 404 Not Found | Resource or user missing | No matching Ops user, or the System Administrator record is missing | Record not found. An HTML 404 page means a wrong URL. |
| 406 Not Acceptable | Unexpected format | The System Administrator’s Windows account name isn’t in the expected format | – |
| 409 Conflict | Conflicting update | – | A PUT clashes with the record’s current state |
| 422 Unprocessable | Body isn’t a valid record | – | A POST or PUT body is missing required fields or is malformed |
| 500 Server Error | Unexpected internal Ops error | Capture the request, response, and time, then contact B2W Support | |
Find the symptom below. Each card gives the likely cause and the fix. Isolate the failing step first: connection, login, or data call.
A 403 almost always means the user’s Ops security role doesn’t grant the API permission the call needs. The response names the missing privilege:
ApiEmployee.Read, and the user’s role doesn’t grant it.GET /Employee works (View granted), but POST /Employee returns 403 (Create not granted).A read-only license cannot make changes in Ops, even if the security role allows them. It cannot even run a GET on employees.
The API site, the IIS web server, or the network is down entirely.
Check network connectivity, routing, and the IIS host status. If the site itself is down, contact B2W Support.
The address is wrong: a typo in the environment name, or a missing OpsAPI_ prefix.
Rebuild the URL with the formula in question 3 and try /Ping/hello again.
No complete credential pair in the headers. The values may be in the URL or body, or a header name is misspelled.
Send exactly one pair as headers: tiduuid + apiSecret, clientId + clientSecret, or userName + password.
With a user name: Windows rejected the password. With a client ID: the ID or secret doesn’t match Ops system settings. With a TID UUID: the TID ID or User API Secret is wrong.
Re-copy the values from their source (question 8 or question 9). Ask whether anyone generated a new secret or key recently.
With a user name: the account isn’t set up as an Ops user. With client credentials: the built-in System Administrator record is missing in Ops.
Confirm the user exists and is active in Ops. If the System Administrator record is missing, contact B2W Support.
Trimble accepted and authenticated the person, but no Ops user matches their Trimble ID or mobile email address.
Fix the link on the Ops user record (TID / Mobile E-mail Address). Do not reset the password; the Trimble sign-in is valid.
The bearer token is missing from the Authorization header, or it has expired.
Check the header reads Authorization: Bearer <AccessToken>. Log in again for a fresh token; they last 1 day.
The default page size, not missing data.
Page with $top and $skip until a page has fewer than 100 records (question 14).
XML message bodies and .asmx URLs belong to the older SOAP Web Services. They don’t map to Ops API 2.0 endpoints.
Use the Web Services documentation and support path for it. Where it makes sense, move the integration to the Ops API.
The body isn’t a valid record: a required field is missing, a field name is wrong, or the JSON is malformed.
Compare the body with a GET of an existing record or with /schema, and check the required fields in Swagger.
Security rules, answers to common questions, a one-page cheat sheet, and definitions.
Answers to the questions people ask most.
A client ID and secret give system-administrator-level access. TID keeps an audit trail of what was changed and takes on the persona of the user, following the limits set in their security role.
No. A read-only license does not have permission to make changes in Ops. It cannot even run a GET on employees.
Likely not. That would be a very large undertaking for the development team. Today, API permissions are set per area and per action (Create, View, Edit, Delete).
No. The ObjectID is the record’s permanent key. You use it to target a PUT or DELETE, but you cannot change it.
Whatever you can do with the application, for the areas the API covers and within the caller’s permissions. See the endpoint explorer.
Yes. The login response calls it AccessToken; you send it in a header as Authorization: Bearer <AccessToken>.
One day by default, set by the Security:JWT:Lifetime site setting. Generating a new token also retires the old one, so use your latest.
Yes. It is going to sunset soon. Move people to TID UUID + User API Secret, and unattended integrations to client ID + secret.
No. /Ping/hello and /Version are the two calls that need no authentication, which makes them the first check for any problem. In version 26.1.2.1, /SystemInfo also answered without a login.
No. /OpsAPI_<environment>, /OPSAPI_<environment>, and /opsapi_<environment> all reach the same API.
Three places: the area’s section in Swagger, GET /{Area}/schema, or a GET of an existing record in that area. The last one is often the quickest.
It is only a folder that groups the four login requests. They all call /Login or /LoginWithTID.
Your environment’s own Swagger page, linked under Links and resources. It always matches your version.
Everything on one screen. It prints on a single page if you print this section alone.
https://{cluster}/OpsAPI_{environment}{api}/doc/index.html{api}/doc/v2/OpsAPI.jsonhttps://<cluster>.b2w.trimble.com/OpsAPI_<environment>/Ping/hello → server name + your text/Version → API version/SystemInfo → TID modetiduuid + apiSecret → acts as the userclientId + clientSecret → System AdministratoruserName + password → being retiredGET /LoginWithTID with a Trimble ID tokenAuthorization: Bearer <AccessToken>Content-Type: application/json$filter $select $orderby $top $skip · 100 per pageOpsAPI_ before the site name/Ping/hello answer?Authorization: Bearer …. Lasts 1 day by default.https://<cluster>.b2w.trimble.com/OpsAPI_<environment>.<cluster>.b2w.trimble.com.GET /Employee.Content-Type: application/json.$filter, $select, $orderby, $top, and $skip..asmx URLs, POST-only, XML envelopes./doc/index.html that lists every endpoint and field.B2W API Help · Estimate API
How to reach your Estimate API, sign in, send the right headers, read and update estimate data, and solve problems when a call fails. Paste your Ops URL into the card to fill in every example.
What the Estimate API covers, how it differs from the Ops API, and where to find it.
The Estimate API lets other software read and write B2W Estimate data: estimates and their items and components, plus the resources, organizations, and categories behind them. It is a REST API that speaks JSON, like the Ops API, but it signs in differently and needs a few extra headers.
The Estimate API v1 catalog lists 56 functional areas and 227 operations. It supports GET, POST, and PUT. There is no DELETE.
| Ops API | Estimate API | |
|---|---|---|
| Address prefix | OpsAPI_ | EstAPI_ |
| Login | TID UUID + secret, client ID + secret, or user name + password | Active Directory user name + password only |
| Extra headers | None beyond the token | DatabaseName on every data call; ClientID and ClientSecret when Client ID Security is on |
| Results | A JSON array | An object with an Items array |
| Updates | PUT the whole record | PUT the whole record, including its AntiTamperToken |
| Delete | Supported | Not supported |
Use the same formula as the Ops API with the EstAPI_ prefix: https://{cluster}/EstAPI_{environment}. For <environment>, that is https://<cluster>.b2w.trimble.com/EstAPI_<environment>.
The path is not case-sensitive, so estapi_<environment> works too.
Add /doc/index.html to the Estimate API URL. It is a standard Swagger page with the setup notes at the top and every functional area below.
https://<cluster>.b2w.trimble.com/EstAPI_<environment>/doc/index.htmlOpenThe machine-readable catalog is at /doc/v1/EstAPI.json. The setup sections (Post-Installation Step and Configuration) describe on-premises installs, so for cloud environments the ones to read are Usage, Testing, and Troubleshooting.
Get a token, then send it with the headers every Estimate call needs.
Send GET {baseUrl}/Login with userName and password headers for an Active Directory account that has access to Estimate. The AccessToken in the response is your bearer token.
userName is the Windows account name of an Active Directory user who can access the Estimate data, written as DOMAIN\user.userName and password are required.Authorization: Bearer <AccessToken> on every other call. The response also has a RefreshToken, which the docs say to ignore; it is there for future use.{{BaseURL}}/LoginHeaders
userName
{{UserName}} (DOMAIN\user)
password
{{Password}}
Body
none
Login never takes a body.
$baseUrl = "https://<cluster>.b2w.trimble.com/EstAPI_<environment>"
# Windows (Active Directory) account of an Estimate user, as DOMAIN\user
$login = Invoke-RestMethod -Method Get -Uri "$baseUrl/Login" -Headers @{
userName = "DOMAIN\jsmith"
password = "<password>"
}
$token = $login.AccessToken # this is your bearer token
curl -s 'https://<cluster>.b2w.trimble.com/EstAPI_<environment>/Login' \
-H 'userName: DOMAIN\jsmith' \
-H 'password: <password>'
GET /EstAPI_<environment>/Login HTTP/1.1
Host: <cluster>.b2w.trimble.com
userName: DOMAIN\jsmith
password: <password>
| Login returns | Meaning |
|---|---|
| 200 OK | A JWT with the AccessToken (and a RefreshToken to ignore). |
| 400 | A header is missing or invalid. With no headers, the API answers {"Error":"Both userName and password must be specified in the request header"}. |
| 401 | The user is not authorized. |
| 500 | The server hit an internal error. Capture the details and contact B2W Support. |
Is a token expired, or whose is it? Paste it into the . It shows when the token was issued and expires, and who it belongs to. It decodes the token on this page only and clears it when you close the window.
Never send a password by email, and never paste one into a support case or chat. To test, use an account you are allowed to use.
Every data call needs Authorization: Bearer <AccessToken> and a DatabaseName header naming your Estimate database. Add ClientID and ClientSecret when Client ID Security is on, and EstimateREF on the lookups that belong to one estimate.
| Header | When | Value |
|---|---|---|
Authorization | Every data call | Bearer <AccessToken> from Login |
DatabaseName | Every data call | Your Estimate database. The official collection’s placeholder is B2WSample. |
ClientID · ClientSecret | When Client ID Security is on | The values set in the API |
EstimateREF | 15 estimate-specific lookups (below) | The ObjectID of the estimate |
Content-Type | POST and PUT | application/json |
Client ID Security is optional and set when the API is installed. When it is on, every call must include ClientID and ClientSecret headers that match the API’s values. If they don’t match, the call returns 401, the same code as a bad token.
These lookups return the values that can be used on one estimate, so they need the estimate’s ObjectID in an EstimateREF header:
/Estimate/BidAs/Estimate/EquipmentRateClass/Estimate/EstimateType/Estimate/EstimateStatus/Estimate/JobCostID/Estimate/LaborRateClass/Estimate/MinorityType/Estimate/UserDefinedField/Estimate/WorkRule…/Organization/{Type}/Category for Competitor, Customer, EngineerArchitect, Manufacturer, Subcontractor, and VendorThat is the 401 message the API returns when a data call has no valid credentials. Check the bearer token first, then the ClientID and ClientSecret if Client ID Security is on.
Download the official EstAPI collection from the docs, import it into Postman, and fill in six collection variables. Its pre-request script logs in for you before every request, so you never copy a token by hand.
| Variable | What goes in it | Example |
|---|---|---|
BaseURL | The Estimate API URL (the file ships with http://localhost/EstAPI) | https://<cluster>.b2w.trimble.com/EstAPI_<environment> |
DatabaseName | Your Estimate database | B2WSample (placeholder) |
UserName | Active Directory account, as domain\username | DOMAIN\jsmith |
Password | That account’s password | Your own value |
ClientID · ClientSecret | Only when Client ID Security is on | Leave blank otherwise |
Token | Set automatically by the pre-request script | Leave it |
Before each request it calls {{BaseURL}}/login with your user name and password, saves the AccessToken as the collection’s bearer token, and adds your ClientID and ClientSecret headers. Every request already sends DatabaseName.
Read and filter estimate data, update records safely, and find the right endpoint.
GET returns an object whose Items array holds the records, up to 100 per call. Filter, sort, and page with the same OData options as the Ops API: $filter, $select, $orderby, $top, and $skip.
{{BaseURL}}/Estimate?$top=5Headers
DatabaseName
{{DatabaseName}}
ClientID · ClientSecret
Added by the pre-request script
Authorization
Bearer Token
{{Token}}, set by the pre-request script
$headers = @{
Authorization = "Bearer $token"
DatabaseName = "<Estimate database name>"
# ClientID = "<client ID>" # only when Client ID Security is on
# ClientSecret = "<client secret>"
}
# For GET, -Body becomes the query string: /Estimate?$top=5
$result = Invoke-RestMethod -Uri "$baseUrl/Estimate" -Headers $headers -Body @{ '$top' = 5 }
$result.Items | Select-Object Number, Name, EstimateStatus
curl -s -G 'https://<cluster>.b2w.trimble.com/EstAPI_<environment>/Estimate' \
--data-urlencode '$top=5' \
-H 'Authorization: Bearer <AccessToken>' \
-H 'DatabaseName: <Estimate database name>'
GET /EstAPI_<environment>/Estimate?$top=5 HTTP/1.1
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken>
DatabaseName: <Estimate database name>
The in the request builder lists each endpoint’s fields and writes the $filter for you, with text quoted, IDs left unquoted, and dates in the format the API expects.
{
"Items": [
{
"AntiTamperToken": "string",
"ObjectID": "32b66b04-0fdc-409f-8e10-aa6f5cf0fc6e",
"Number": "0006",
"Name": "Elm Street Bridge Improvements, Manchester",
"EstimateStatus": "Signed",
"EstimateType": "Public",
"BidAs": "General Contractor",
"BidDate": "2018-12-28T13:00:00",
"PrimaryCustomerName": "City Of Manchester",
"EstimatorName": "Paul Smith"
}
]
}
# One estimate by number
{{BaseURL}}/Estimate?$filter=Number eq '0006'
# A few fields, sorted
{{BaseURL}}/Estimate?$select=ObjectID,Number,Name&$orderby=Number
# From the official collection: GUIDs go without quotes
{{BaseURL}}/Estimate/MinorityParticipationRequirement?$filter=EstimateREF eq 02E2BD02-B667-4478-A1C6-76DEDFB8958F
The page size is 100 by default. Add $top=100&$skip=100, then $skip=200, until a page has fewer than 100 records.
POST creates and PUT updates. To update, GET the record, change only the fields you need, and PUT the entire record back with its AntiTamperToken unchanged.
Every GET returns an AntiTamperToken. A PUT must send it back with the same value. It protects properties that are returned for information only, such as ObjectID, from being changed. The docs also warn that a PUT with missing fields can delete values from the fields you left out, which is why you always send the whole record.
# 1. GET the record you want to change
$result = Invoke-RestMethod -Uri "$baseUrl/Estimate" -Headers $headers -Body @{ '$filter' = "Number eq '0006'" }
$estimate = $result.Items | Select-Object -First 1
# 2. Change only what you need. Leave AntiTamperToken and ObjectID as they are.
$estimate.BidLocation = "Manchester, NH"
# 3. PUT the whole record back
Invoke-RestMethod -Method Put -Uri "$baseUrl/Estimate" -Headers $headers -Body ($estimate | ConvertTo-Json -Depth 10) -ContentType "application/json"
| PUT returns | Meaning |
|---|---|
| 200 OK | The record was updated. |
| 400 | The body doesn’t contain the record. |
| 403 | The operation is not allowed, for example one of the unsupported changes in question 1. |
| 404 | The record wasn’t found. |
| 422 | The body isn’t a valid record. Compare it with a fresh GET or with /schema. |
POST and PUT change real estimates. Try them in a test environment first, and never run them against someone else’s environment unless they asked for the change.
The Estimate API v1 catalog lists 227 operations. Most areas support GET, POST, and PUT plus a /schema call; some are read-only. Search below, then check Swagger for the fields.
Each card’s Build button opens that endpoint in the , which adds the DatabaseName and EstimateREF headers for you and writes the request for Postman, PowerShell, cURL, or raw HTTP.
The basic tests from the Estimate API docs, and fixes for the errors you will see most.
Paste the error into the decoder: a status line, a response body, a Postman or PowerShell error, or the request URL. It names the likely cause and links to the fix.
It knows the Estimate API’s own messages, such as “The user is not authorized for this API.” and “Operation is not allowed”. It also reads SQL Server errors like “Login failed for user ‘NT AUTHORITY\NETWORK SERVICE’”, and checks pasted request headers for a missing DatabaseName, EstimateREF, or Bearer. Decoding runs entirely in your browser.
Run the two basic tests in order: an app-up test with /Ping/hello, then a login test. Don’t go further until both pass. Then check the data call’s headers.
GET /Ping/hello should return 200 and (server) hello|. If it doesn’t, check that IIS is running, that the EstAPI application pool is running, and that the URL is correct. The environment checker in question 2 runs this test for you.
GET /Login with userName and password headers should return 200 and an AccessToken. Any other code points at the account (question 4).
Check Authorization: Bearer, DatabaseName, the ClientID and ClientSecret if Client ID Security is on, and EstimateREF for estimate-specific lookups.
One or both headers are missing, or they were sent in the URL or body.
Send both userName (DOMAIN\user) and password as headers.
The domain controller rejected the account, or the account has no access to the Estimate data.
Confirm the account name format and password, and that the user can access Estimate.
The bearer token is missing, invalid, or expired, or the ClientID or ClientSecret is wrong when Client ID Security is on.
Log in again for a fresh token and check the Authorization header. Then check the ClientID and ClientSecret headers.
The DatabaseName header is missing or names the wrong database.
Confirm the Estimate database name and resend.
The API runs as the Network Service account, which needs db_owner access to the Estimate database (the docs’ Post-Installation Step).
On-premises: grant that access using the ConfigureDB commands. Cloud: contact B2W Support.
The change is one the API doesn’t support, such as cost structure, container components, or organizations in an estimate.
Make that change in Estimate itself. There is no API route for it.
The body isn’t a valid record, or a PUT left out fields or changed the AntiTamperToken.
GET the record again and PUT the whole record back with its AntiTamperToken unchanged.
Email support_b2w@trimble.com or call +1 (888) 390-8822. Include the environment name, the endpoint and method, the status code and response with secrets removed, and the date, time, and time zone. The error decoder’s Copy a summary button writes most of it for you.
B2W API Help · Management Reporting API
How to reach your Management Reporting (MR) API, sign in, send the right headers, and pull report data from the Data Warehouse. Paste your Ops URL into the card to fill in every example.
What the MR API covers, where its data comes from, and where to find it.
The MR API is a read-only REST API over the B2W Management Reporting Data Warehouse. It returns report data such as estimates, pay items, tasks, cost components, bid results, change orders, and vendor and subcontractor quotes. Every operation is a GET.
The MR API v2 catalog lists 42 functional areas and 106 operations: a data call and a /schema call for each area, plus Login and Ping.
Where estimators do their work.
Loaded from the source databases.
GET requests only.
Records come from the Data Warehouse, not straight from Estimate. Each record shows where it came from (SourceDatabaseName) and when (LastRetrievedFromSourceDatabaseOn). If the API shows old numbers, check those fields first. To change data, use Estimate or the Estimate API.
Use the same formula with the MRAPI_ prefix: https://{cluster}/MRAPI_{environment}. For <environment>, that is https://<cluster>.b2w.trimble.com/MRAPI_<environment>.
Add /doc/index.html to the MR API URL. Like the Estimate API, it is a standard Swagger page with setup notes at the top and every functional area below.
https://<cluster>.b2w.trimble.com/MRAPI_<environment>/doc/index.htmlOpenThe machine-readable catalog is at /doc/v2/MRAPI.json.
Get a token, then send it with the headers every MR call needs.
Exactly like the Estimate API: send GET {baseUrl}/Login with userName (DOMAIN\user) and password headers for an Active Directory account with access to Management Reporting. The AccessToken in the response is your bearer token.
{{BaseURL}}/LoginHeaders
userName
{{UserName}} (DOMAIN\user)
password
{{Password}}
$baseUrl = "https://<cluster>.b2w.trimble.com/MRAPI_<environment>"
$login = Invoke-RestMethod -Method Get -Uri "$baseUrl/Login" -Headers @{
userName = "DOMAIN\jsmith"
password = "<password>"
}
$token = $login.AccessToken
curl -s 'https://<cluster>.b2w.trimble.com/MRAPI_<environment>/Login' \
-H 'userName: DOMAIN\jsmith' \
-H 'password: <password>'
GET /MRAPI_<environment>/Login HTTP/1.1
Host: <cluster>.b2w.trimble.com
userName: DOMAIN\jsmith
password: <password>
The responses match the Estimate API: 200 with the AccessToken, 400 when a header is missing (the API answers "Both userName and password must be specified in the request header"), 401 when the user is not authorized, and 500 for server errors.
Is a token expired, or whose is it? Paste it into the . It shows when the token was issued and expires, and who it belongs to. It decodes the token on this page only and clears it when you close the window.
Every data call needs Authorization: Bearer <AccessToken> and a DatabaseName header naming the Data Warehouse database. Add ClientID and ClientSecret when Client ID Security is on, and EstimateREF on the eight calls that return one estimate’s items.
| Header | When | Value |
|---|---|---|
Authorization | Every data call | Bearer <AccessToken> from Login |
DatabaseName | Every data call | The Management Reporting Data Warehouse database. The official collection’s placeholder is B2WDataWarehouse. |
ClientID · ClientSecret | When Client ID Security is on | The values set in the API; a mismatch returns 401 |
EstimateREF | The eight estimate-specific calls below | The ObjectID of the estimate |
| For one estimate (send EstimateREF) | Across all estimates (no EstimateREF) |
|---|---|
/Estimate/PayItem · /Estimate/PayItemDetail | /Estimate/PayItemAll |
/Estimate/IndirectItem · /Estimate/IndirectItemDetail | /Estimate/IndirectItemAll |
/Estimate/Task · /Estimate/TaskDetail | /Estimate/TaskAll |
/Estimate/WBSLevel · /Estimate/WBSLevelDetail | /Estimate/WBSLevelAll |
The Detail versions return the same items with full information. Get an estimate’s ObjectID from GET /Estimate first.
Download the official MRAPI collection from the docs, import it, and fill in its variables. Like the Estimate collection, its pre-request script logs in for you before every request.
| Variable | What goes in it | Example |
|---|---|---|
BaseURL | The MR API URL (the file ships with http://localhost/MRAPI) | https://<cluster>.b2w.trimble.com/MRAPI_<environment> |
DatabaseName | The Data Warehouse database | B2WDataWarehouse (placeholder) |
UserName · Password | Active Directory account, as domain\username | Your own values |
ClientID · ClientSecret | Only when Client ID Security is on | Leave blank otherwise |
EstimateREF | An estimate’s ObjectID, for the estimate-specific requests | DA45D31C-05FC-4C64-B1C8-8CE3E8E69D23 (sample) |
Token | Set automatically by the pre-request script | Leave it |
Pull report data, page through it, and find the right endpoint.
Every GET returns an Items array and a Pagination block that tells you how many records exist. Use OData to filter and page; contains() works for text searches.
{{BaseURL}}/Estimate/PayItem?$filter=contains(Description, 'Mobilization')Headers
DatabaseName
{{DatabaseName}}
EstimateREF
{{EstimateREF}}
Authorization
Bearer Token
{{Token}}, set by the pre-request script
$headers = @{
Authorization = "Bearer $token"
DatabaseName = "<Data Warehouse database name>"
EstimateREF = "<ObjectID of the estimate>"
}
$result = Invoke-RestMethod -Uri "$baseUrl/Estimate/PayItem" -Headers $headers -Body @{ '$filter' = "contains(Description, 'Mobilization')" }
$result.Items
$result.Pagination # CurrentPage, ItemsOnPage, PageSize, TotalItems
curl -s -G 'https://<cluster>.b2w.trimble.com/MRAPI_<environment>/Estimate/PayItem' \
--data-urlencode "\$filter=contains(Description, 'Mobilization')" \
-H 'Authorization: Bearer <AccessToken>' \
-H 'DatabaseName: <Data Warehouse database name>' \
-H 'EstimateREF: <ObjectID of the estimate>'
GET /MRAPI_<environment>/Estimate/PayItem?$filter=contains(Description, 'Mobilization') HTTP/1.1
Host: <cluster>.b2w.trimble.com
Authorization: Bearer <AccessToken>
DatabaseName: <Data Warehouse database name>
EstimateREF: <ObjectID of the estimate>
The in the request builder lists each endpoint’s fields and writes the $filter for you, with text quoted, IDs left unquoted, and dates in the format the API expects.
{
"Items": [
{
"Title": "Fox Hill Development",
"ObjectID": "02e2bd02-b667-4478-a1c6-76dedfb8958f",
"EstimateNumber": "0012",
"BidNumber": "64687",
"EstimateStatus": "Pending",
"SourceDatabaseName": "B2WSample",
"LastRetrievedFromSourceDatabaseOn": "2025-12-07T23:40:17.823",
"TotalBidPriceWithTax": 12627661.59
}
],
"Pagination": {
"CurrentPage": "…/Estimate?$orderBy=Title&$skip=0&$top=5",
"ItemsOnPage": 1,
"PageSize": 5,
"TotalItems": 1
}
}
Pagination.TotalItems tells you how many records match. Keep adding $skip in steps of your $top (100 at most) until you have them all.
The MR API v2 catalog lists 106 operations, all GET. Each area has a data call and a /schema call. Search below, then check Swagger for the fields.
Each card’s Build button opens that endpoint in the , which adds the DatabaseName and EstimateREF headers for you and writes the request for Postman, PowerShell, cURL, or raw HTTP.
The basic tests from the MR API docs, and fixes for the errors you will see most.
Paste the error into the decoder: a status line, a response body, a Postman or PowerShell error, or the request URL. It names the likely cause and links to the fix.
It knows the MR API’s messages and SQL Server errors such as “Login failed for user ‘NT AUTHORITY\NETWORK SERVICE’”. Paste a successful response and it reads the Pagination block and LastRetrievedFromSourceDatabaseOn too, which answers “why is data missing?” and “why are the numbers old?”. Decoding runs entirely in your browser.
Run the app-up test with /Ping/hello, then the login test. Don’t go further until both pass. Then check the headers, especially DatabaseName and EstimateREF.
GET /Ping/hello should return 200 and (server) hello|. If not, check that IIS and the MRAPI application pool are running and that the URL is correct. The environment checker in question 2 runs this for you.
GET /Login with userName and password headers should return an AccessToken.
Check Authorization: Bearer, DatabaseName, the client values if Client ID Security is on, and EstimateREF on estimate-specific calls.
The bearer token is missing, invalid, or expired, or the ClientID or ClientSecret is wrong when Client ID Security is on.
Log in again for a fresh token, then check the client headers.
The DatabaseName header is missing or names the wrong Data Warehouse database.
Confirm the Data Warehouse database name and resend.
The EstimateREF header is missing or isn’t an estimate in this Data Warehouse.
Get the estimate’s ObjectID from GET /Estimate, or use the …All version of the call.
The Data Warehouse hasn’t been reloaded from the source Estimate database since the change.
Check LastRetrievedFromSourceDatabaseOn on the record, and the Data Warehouse load for that source database.
The API’s Network Service account lacks db_owner access to the Data Warehouse database, or the API’s SQL Server connection string is wrong.
On-premises: grant the access and verify the connection string (the docs’ Post-Installation Step). Cloud: contact B2W Support.
Email support_b2w@trimble.com or call +1 (888) 390-8822. Include the environment name, the endpoint and method, the status code and response with secrets removed, and the date, time, and time zone. The error decoder’s Copy a summary button writes most of it for you.