Connect Cypress
Let an organization's admin connect your app to Cypress, sign in every member of that organization as themselves, get tokens for the agents your app adds, and know who is using your app.
TL;DRAI summary
This chapter explains how your app can securely connect to Cypress using OAuth 2.1, allowing fleet managers and other users to sign in with their own roles. It covers how to register your app and the steps for users to grant your app access to their organization's data.
An API key belongs to one organization and acts as that organization. Connect Cypress is for an app that many organizations use: an organization's admin approves your app once, on a Cypress screen, and your app gets tokens for that organization. After that, every other member of the organization can sign in to your app as themselves, with their own role, through the same flow. It is OAuth 2.1 with PKCE. Since 1.5.0; members since 1.9.0.
Your app can also ask to add agents to the organization's team. An agent is
a member of the organization with its own name and the role AGENT. Every
action it takes is recorded under its own name, and it works under the same
rules as everyone else, plus a few of its own (see What agents may not do).
Register your app
Apps are registered by Cypress. Send us:
| Field | What it is |
|---|---|
| Name and description | What the admin reads on the consent screen |
| Redirect addresses | Where Cypress sends the admin back. https only (http only for localhost), matched exactly; an http address on the person's own computer matches on any port |
| Scopes | The most your app will ever ask for, from the key scopes |
| Agents | Optional. The agents your app may add, each with a key, a name and its scopes, for example sell, "Cedar Sell", offers:write |
You receive a client_id (cyp_app_…) and, for an app that runs on a server,
a client_secret (cyp_cs_…), shown once. An app without a secret (a browser
or mobile app) uses PKCE alone and cannot add agents.
The server's settings are published at /.well-known/oauth-authorization-server.
Without registering: a client ID metadata document
An MCP client, or any app that runs on a person's own computer, can connect
without registering. Its client_id is the https address of a JSON document
it publishes about itself, as the MCP authorization flow (2026-07-28) has it.
The server's settings say client_id_metadata_document_supported: true.
{
"client_id": "https://desk.example.com/oauth/client.json",
"client_name": "Fleet Desk",
"client_uri": "https://desk.example.com",
"redirect_uris": ["http://127.0.0.1/callback", "https://desk.example.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}- The address is
https, names a document (a path, not a whole site), carries no user name, password or fragment, and is written in its plain form. The document'sclient_idis that address, exactly. - Cypress reads it when an authorization starts, again after five minutes at most. It must be served at the address itself (a redirect is not followed), from a public address, as JSON of at most 5 KB, within five seconds.
- It is a public app: no secret (
token_endpoint_auth_methodisnoneor left out), PKCE always, and no agents. Its token acts for the admin who approved it, with the scopes they approved. - Redirect addresses are
https, orhttpon the person's own computer (127.0.0.1,localhost,[::1]). An address on the computer matches on any port, so a desktop client listshttp://127.0.0.1/callbackand listens on the port it picks. - The admin sees the address. The consent screen shows it beside the name, marks the app Not registered with Cypress and says where the browser returns; Settings → Connected apps shows it too. Cypress can switch an app off, and it then stays off whatever its document says.
The flow below is the same: send client_id (the address) and no secret when
you exchange the code.
Registering itself: dynamic client registration
An app that registers itself (RFC 7591), as Microsoft Copilot Studio's
automatic OAuth does, posts its settings to POST /api/oauth/register, the
server settings' registration_endpoint, and gets its client_id at once:
{
"client_name": "Fleet Desk",
"redirect_uris": ["https://desk.example.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"scope": "offers:read crm:read"
}- It answers
201with theclient_id(cyp_dyn_…), and for an app that asks forclient_secret_basicorclient_secret_post(the default) aclient_secret(cyp_cs_…), shown once and never expiring. - What it may ask for: the
authorization_codegrant, withrefresh_token;response_typescode; any of the scopes (all of them whenscopeis left out), of which an admin approves a part. It never adds agents. - Redirect addresses follow the same rule as above:
https, orhttpon the person's own computer, without a fragment. - A refusal answers
400withinvalid_redirect_uriorinvalid_client_metadataand says why. Twenty registrations an hour per caller. - The admin sees it as unregistered. The consent screen marks it Not registered with Cypress, says it registered itself and where the browser returns; Settings → Connected apps shows where it returns too.
The flow
1. Send the admin to Cypress.
GET /api/oauth/authorize
?response_type=code
&client_id=cyp_app_…
&redirect_uri=https://your.app/callback
&scope=offers:write webhooks
&agents=sell
&state=<your value>
&code_challenge=<base64url(SHA-256(verifier))>
&code_challenge_method=S256
&organization_id=<optional>PKCE with S256 is required. agents is a comma list of your agent keys and
may be left out. Every scope an agent has must also be in scope.
organization_id is optional: which of the person's organizations to sign in
for (the ids are in organizations). Without it,
Cypress uses the person's default organization. An organization they are not an
active member of is refused. A person in more than one organization can also
switch on the consent screen.
2. The person decides. Who is signed in decides what happens:
- An admin of the organization connects the app. Cypress shows the app, the organization, what the app will be able to do and the agents it will add. Cypress staff acting for the organization can connect it too (on its behalf, and the audit log says so). A connection staff approve creates the agents, but your person token acts for nobody, since staff are not members of the organization: an app that must act as a person is connected by one of its people. Approving again later updates the same connection.
- Any other member signs in. Once the organization has connected your app,
a Fleet Manager, a Billing Admin or any other member sees "Continue to Fleet
Desk as Bo Ruiz" (your app's name, theirs), with their role and what the app
will be able to do. Continuing changes nothing about the connection: the member gets a
code for themselves, with the scopes you asked for that the connection holds
(the others are dropped; none at all answers
error=invalid_scope).agentsis ignored, since agents are the admin's decision. Each sign-in is in the organization's activity log. - A member of an organization that has not connected your app is told to
ask an admin of the organization to connect it, and sent back to you with
error=access_denied.
3. Cypress sends the person back to your redirect address with code and
your state. A refusal comes back as error=access_denied. A code works once,
for 10 minutes.
4. Exchange the code.
curl -X POST https://app.getcypress.ai/api/oauth/token \
-u "cyp_app_…:cyp_cs_…" \
-d grant_type=authorization_code \
-d code=cyp_ac_… \
-d redirect_uri=https://your.app/callback \
-d code_verifier=<the verifier>{
"access_token": "cyp_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "cyp_rt_…",
"scope": "offers:write webhooks",
"connection_id": "…",
"organization_id": "…",
"agents": [{ "id": "…", "key": "sell", "name": "Cedar Sell", "scopes": ["offers:write"] }]
}This token acts for the person who approved or signed in, with their role in the organization, while they remain a member of it. It never holds more than the connection does: if the admin later narrows the connection, every token on it narrows at once.
5. Get a token for an agent. Agent tokens are never handed out in step 4. Ask for one with your secret and the agent's id:
curl -X POST https://app.getcypress.ai/api/oauth/token \
-u "cyp_app_…:cyp_cs_…" \
-d grant_type=client_credentials \
-d agent_id=<agent id>The token is bound to that agent and lasts an hour. There is no refresh token: ask again.
6. Refresh. grant_type=refresh_token returns a new access token and a new
refresh token; the old refresh token stops working. Presenting a used refresh
token again is treated as a leak: every token for that person on that
connection is revoked.
7. Revoke a token you no longer need at POST /api/oauth/revoke with
token=… and your client credentials.
Calling the API with a token
Send Authorization: Bearer cyp_at_…. A token works on the app's actions,
the MCP servers and most of the API, under the scope rules for API keys,
applied to the scopes the organization approved. A few routes belong to the
system that runs an integration and take an API key only (webhook
subscriptions, the delivery log, disconnecting an app, and pushing vehicle
data): they answer a token 401, "This endpoint accepts API keys only; app
and agent tokens are not accepted here". The list is in
Keys and tokens.
A token stops working at once when the connection is revoked, when the person it acts for leaves the organization, or when the agent is switched off.
What agents may not do
An agent works under the app's rules with a fleet manager's authority, and
some decisions are never its own: accepting or countering below the floor,
setting the floor, emailing a contact, sending a campaign, approving a
quarter, confirming that money arrived, and switching an automation back on.
Each answers 403 PEOPLE_ONLY (on Cypress Operate, a decision with a link for
a person). An agent never signs a document, accepts terms, changes billing or
manages the team; those endpoints do not accept agent tokens at all. Area by
area, with what the agent is told: What an agent can and cannot do.
A person's confirmation in a Cypress app
Apps Cypress makes itself (first party, such as Grove, the team app) act for a person. For two decisions,
accepting or countering below the floor, the person confirms on the app's screen and the app sends
Cypress-Person-Confirmed: true with that one request. Cypress then treats it as the person's own decision,
still under their role: only the seller's fleet manager or admin decides below the floor, never a Cypress
Manager. The header means nothing on a partner app's request or an agent's: those still answer
403 PEOPLE_ONLY. The activity log names the app and says the person confirmed it in the app.
Cypress staff in a Cypress app
A Cypress Manager can work in an app Cypress makes (such as Grove) for one business at a time, the way Acting for works in Cypress. On the consent screen they choose one of the businesses that connected the app, the ones they manage first, and continue to it; the connection does not change. The token then acts as Cypress staff for that one organization, with the Cypress Manager role: the same rules as a Cypress Manager acting for it in Cypress, the organization's delegation switches included. It never takes a staff-only action, never reaches another organization, and never carries a person's confirmation, so a decision only the organization's own people make stays theirs. It stops the moment its holder is no longer Cypress staff. The activity log records each action as done by the Cypress Manager, through the app, for the organization. A partner app never gets such a token. Since 1.9.0.
Disconnecting
The organization's admin disconnects your app in Settings › Connected apps.
An integration of the organization can do it too, with an API key holding
connections:write: DELETE /api/v1/connections/{id}. Either way every token
ends at once, every member's included, and every agent your app added is
switched off. Its actions stay in the organization's activity log.
Sign in with Cypress
To know who is using your app, call GET /api/oauth/userinfo with the app's
token:
{
"sub": "…",
"kind": "PERSON",
"name": "Ana Ruiz",
"email": "ana@fleet.example",
"role": "ADMIN",
"organization": { "id": "…", "name": "Fox Fleet" },
"organizations": [
{ "id": "…", "name": "Fox Fleet", "role": "ADMIN", "connected": true },
{ "id": "…", "name": "Fox Leasing", "role": "FLEET_MANAGER", "connected": false }
],
"scope": "offers:write webhooks"
}organization and role are where this token acts. organizations lists
every business the person is an active member of, oldest first, with their role
there and whether that organization has connected your app (connected). To
sign the person in for another one, send them through the flow again with its
organization_id; one that is not connected needs its admin first. Since
1.9.0.
With an agent's token it answers kind: AGENT, the agent's name and its
organization, and no address and no organizations.
With the token of a Cypress Manager in a Cypress app it also answers
"acting_for": true and role: CYPRESS_MANAGER. Their organizations are the
businesses that connected your app, each with managed: true when they manage
it, those first. Since 1.9.0.
For a full OpenID Connect sign-in, where your app signs people in with their Cypress login and receives an ID token, ask Cypress to register your app with Cypress's identity provider as well. That registration is separate from the one above.
Sandbox
A sandbox organization to build your integration against, with vehicles, offers, a closing, a CRM book and a buy plan already in it, a key with every scope, and a reset your agent can call.
The app's actions
Every button of the app's screens as a REST operation and a Cypress Operate tool that run the app's own code as you, with the webhook each change sends.