Overview
A partner is a platform that offers Latitude to its own users: an agent platform, an IDE, a hosting provider, a marketplace. Your users click “install Latitude” inside your product, and a few seconds later their agents are sending traces. Whichever way a user installs, you end up in the same place: holding an OAuth access and refresh token for their Latitude organization, which you use against the regular Latitude API. Your users see you listed under Settings → Keys → OAuth Keys and can revoke you at any time. Partners are vetted and registered by hand, so this is not a self-serve program.Interested? Email hello@latitude.so with your product, the flow you have in mind, and roughly how many users you expect to onboard. We’ll set you up and send your credentials.
What you get
When we register you, we ask for your product name, an icon, your OAuth redirect URLs (the exacthttps:// callback addresses on your side users return to after authorizing — see Your client_id), and optionally the egress IPs to pin your account to.
Once registered you receive a Partner ID and an HMAC secret. The secret is shown to us once and to you once, so store it somewhere safe.
Those credentials unlock the private partner API, which is scoped per partner: you only get the endpoints you were granted.
The two install paths
Your install button has to handle two kinds of user.They already have Latitude
Send them through the standard OAuth flow. They sign in, pick which organization to connect, and approve your access. No partner credentials involved.
They have no account yet
Call the provisioning endpoint. Latitude creates the user, their organization, and your OAuth grant in one signed request, and returns the tokens.
Path A — existing account
This is the plain OAuth 2.1 authorization code flow with PKCE, the same one the MCP server uses. You need no partner credentials for it.1
Register a client for this account
POST https://app.latitude.so/api/auth/mcp/register with your client_name and redirect_uris. Use "token_endpoint_auth_method": "none" for a public client. Store the client_id you get back with this account — registration is unauthenticated and instant, and each connected account needs its own client (see Your client_id).2
Send the user to authorize
Open
https://app.latitude.so/api/auth/mcp/authorize with response_type=code, your client_id, redirect_uri, scope=openid offline_access, a state, and a PKCE code_challenge (S256).3
They sign in and consent
Latitude asks them to pick which organization to connect and to approve your access.
4
Exchange the code
They come back to your
redirect_uri with ?code. Exchange it at POST https://app.latitude.so/api/auth/mcp/token with grant_type=authorization_code, the code, your client_id, and the code_verifier.GET /v1/account with the access token to find out which user and organization you are now connected to.
Path B — no account yet
There is nobody to consent yet, so instead of an interactive flow you make one signed request. Latitude creates everything the consent flow would have created, minus the interaction.Your client_id
Aclient_id at Latitude identifies one connection between your product and one account — not your platform as a whole. Each path hands you one:
- Path A — the one you registered before sending the user to authorize.
- Path B — the
client_idfield of the provisioning response.
client_id + refresh_token as that customer’s connection record, and reuse the same client_id for everything you do on their behalf:
- Refreshing tokens — the refresh call requires it.
- Reconnecting — if the tokens expire or the user revokes and wants to reconnect, run the path-A authorize flow with the same
client_id. Latitude remembers the consent granted to that client, so the user isn’t asked to approve you again, and the connection stays a single entry in their Settings → Keys page instead of piling up duplicates.
Account provisioning
Request
user.email is required. Everything else is optional, and anything you omit is derived.
Response
201 Created, shaped like an OAuth token response so you can hand it to the same code that handles path A:
client_id alongside the tokens — it is this account’s connection identity, and refreshing needs it. The grant carries your registered redirect URLs, so a later interactive re-authorize with this client_id works too.
Errors
The
409 is the important one to handle. Provisioning never touches an existing account, by design, so an install button that only implements path B will fail for any user who already knows Latitude. Show them the “connect your existing account” option instead.Signing a request
Every request to the partner API carries three headers. All three are required.
The signed string is:
timestamp— the same value you put inX-Partner-Timestamp.METHOD— uppercase HTTP method, e.g.POST.pathname— the request path including/v1, without the query string, e.g./v1/private/partners/abc123/accounts.nonce— the same value you put inX-Partner-Nonce.sha256hex(body)— SHA-256 of the exact raw request body bytes you send, hex-encoded.
The nonce is part of the signature, so generate it before you sign and send that same value. It is what makes a captured request unreplayable: swap it and the signature no longer matches, reuse it and we reject the repeat.
v1=.
Using the tokens
The access token is a normal Latitude bearer token. Everything in the API reference works with it, scoped to the organization it belongs to:Refreshing
Access tokens last 1 hour, refresh tokens 7 days. Refresh at the same token endpoint both paths use. Your client is public, so no secret is involved:What your users experience
- They can sign in immediately. Latitude uses email magic links, so there is no password to set. They go to app.latitude.so, enter the email you provisioned, and they are in as the owner of their new organization.
- They skip our onboarding questionnaire. Latitude records your platform as where the account came from, so we don’t ask them again. This is why the profile fields above are worth sending.
- They see you. Your name and icon appear under Settings → Keys → OAuth Keys, with the date they connected.
- They can revoke you. One click, and your tokens stop working within seconds. Handle a sudden
401on the public API as “this user disconnected us” and stop retrying.
Restricting access by IP
If you have stable egress IPs, tell us and we’ll pin your partner account to them. Any signed request from anywhere else is rejected, so a leaked secret is useless on its own. We accept single addresses and CIDR blocks, IPv4 and IPv6 —203.0.113.7, 203.0.113.0/24, 2001:db8::/32. This is optional and off by default; if your infrastructure moves around, skip it rather than fight it.
Limits
Provisioning is rate limited per partner, at 100 requests per minute by default. That comfortably covers organic signup traffic, since one user installing your integration is one call. Rejected requests count too, so a bug that sends a bad signature in a loop will exhaust your quota. Back off on a401 instead of retrying immediately.
Planning a migration or a bulk onboarding? Email us first and we’ll raise your limit, rather than have you find the ceiling in production.