What you need
Exactly two values, both from the management portal under API credentials: a client id and a client secret. The secret is shown once, when you create the credential.
Everything else — the token endpoint, the issuer, and the base URL of each API — is fixed for your environment and listed below. You do not need to look any of it up, and you should not be asked to invent it.
Connect it to the Mesh MCP server and have it call get_connection_details. That returns the token endpoint, the issuer and every API base URL already resolved for your environment, so the only thing you paste in is the client id and secret. If an assistant asks you for a token URL or a scope, it has not called that tool.
Get a token
Exchange the credential at the token endpoint using the OAuth 2.0 client_credentials grant. The request is form-encoded and has three fields.
curl -X POST https://auth.mesh.locate2u.app/realms/mesh/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$MESH_CLIENT_ID" \
-d "client_secret=$MESH_CLIENT_SECRET"
The response contains access_token and expires_in. Cache the token until shortly before it expires rather than requesting one per call.
There is no scope parameter to add, and adding one makes the whole request fail with invalid_scope. The audience every Mesh API checks is attached automatically, and the permissions you granted the credential travel inside the token as claims.
A Mesh permission is not an OAuth scope. If your credential grants maps.read, that is already in the token — sending scope=maps.read asks the identity provider for something it has never heard of, and the request is rejected before any Mesh service sees it.
Call an API
Send the token as a bearer token. Every Mesh API accepts the same token.
curl https://stops-api.mesh.locate2u.app/api/stops \
-H "Authorization: Bearer $ACCESS_TOKEN"
Each API lives on its own hostname, following one pattern: <module>-api.<environment>. The management API is management-api.<environment>.
Environments
Hostnames differ only by the environment label. Production has no label.
| Environment | Base | Token endpoint |
|---|---|---|
| Production | mesh.locate2u.app | auth.mesh.locate2u.app/realms/mesh/protocol/openid-connect/token |
| Test | test-mesh.locate2u.app | auth.test-mesh.locate2u.app/realms/mesh/protocol/openid-connect/token |
The issuer is https://auth.<environment>/realms/mesh, and every Mesh service compares it as an exact string. A valid token from the right server with the wrong issuer path is refused as though its signature were broken — which reads like a key problem and is not one.
When it fails
Three failures cover almost everything.
- invalid_scope from the token endpoint — you sent a scope. Remove it.
- 401 from a Mesh API — the token is missing, expired, or came from a different environment than the API you are calling. Check the environment label matches on both hostnames.
- 403 from a Mesh API — the token is valid, but the credential lacks the permission that endpoint needs, or the account does not have the module that endpoint belongs to. The response body names which.
If a 403 says the module is not enabled, no amount of permission-granting will change it. Which modules an account has is agreed in its Locate2u agreement and applied by Locate2u — contact your account manager.