cypress.DOCS
Log inRegister
Cypress Docs

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.

Dive deeper:

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:

FieldWhat it is
Name and descriptionWhat the admin reads on the consent screen
Redirect addressesWhere 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
ScopesThe most your app will ever ask for, from the key scopes
AgentsOptional. 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's client_id is 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_method is none or 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, or http on the person's own computer (127.0.0.1, localhost, [::1]). An address on the computer matches on any port, so a desktop client lists http://127.0.0.1/callback and 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 201 with the client_id (cyp_dyn_…), and for an app that asks for client_secret_basic or client_secret_post (the default) a client_secret (cyp_cs_…), shown once and never expiring.
  • What it may ask for: the authorization_code grant, with refresh_token; response_types code; any of the scopes (all of them when scope is left out), of which an admin approves a part. It never adds agents.
  • Redirect addresses follow the same rule as above: https, or http on the person's own computer, without a fragment.
  • A refusal answers 400 with invalid_redirect_uri or invalid_client_metadata and 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). agents is 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.