Appearance
Sign in with OAuth
An MCP client can sign in to JustDocs instead of being given a key. The client opens a JustDocs page, where you choose a workspace (your personal one, or a team you own or administer) and what the app may do, and the client gets tokens. Claude, ChatGPT, Muse and Claude Code connect this way. Agent keys still work for every client.
| MCP server | https://tryjustdocs.com/mcp |
| Resource metadata | https://tryjustdocs.com/.well-known/oauth-protected-resource/mcp |
| Authorization server metadata | https://tryjustdocs.com/.well-known/oauth-authorization-server |
| Flow | Authorization code with PKCE (S256), and refresh tokens that rotate |
| Clients | Public clients, identified by a Client ID Metadata Document or by Dynamic Client Registration |
| Permissions | read, documents and send |
| Plan | Every plan. Below Teams & Platform, a connected app is one of the workspace's 3 active agent keys |
How it works
You give your client the server's URL,
https://tryjustdocs.com/mcp. Its first request has no token, so the server answers401with a header that says where to sign in:httpWWW-Authenticate: Bearer realm="JustDocs", resource_metadata="https://tryjustdocs.com/.well-known/oauth-protected-resource/mcp", scope="read documents send"The client reads that document, which names JustDocs as the authorization server, then reads the authorization server's metadata.
The client identifies itself, with its own Client ID Metadata Document or by registering.
It opens a JustDocs page in your browser. You sign in if you aren't already, then choose a workspace and what the app may do.
JustDocs sends you back to the app with a one-time code. The app exchanges it for an access token and a refresh token.
The app calls
/mcpwithAuthorization: Bearer <access token>. When the token expires, it refreshes it without asking you again.
Give the URL exactly as https://tryjustdocs.com/mcp, with no trailing slash and without www.. The resource metadata names that URL, and clients refuse to sign in when it differs from the one you gave.
What the person approving sees
The page shows:
- The app's name. Beside it is Verified and a domain, but only when the app is described by a document at its own address and every web address it returns people to is on that same domain. An app that registered itself is shown as Unverified app, because anyone can register any name.
- Where the sign-in returns you, exactly. This is the host that will receive the app's code.
- Warnings, when they apply:
- the app is described on one domain but returns you to another;
- the sign-in returns to a program on your computer. Any program on the computer can use that address, so continue only if you just started the sign-in from a tool you trust.
- The account you're signed in as, with Not you? Sign out.
- The workspace to connect: your personal workspace, or a team where you're an owner or admin, once your email address is confirmed. Below Teams & Platform, a workspace that already has 3 active agent keys can't take another, unless you're reconnecting this app. Others are listed with the reason.
- The same limits as a key: the permissions, sends a day, recipient domains and the days it works. Only the permissions the app asked for are offered. You can untick any of them, but you can't add one.
Connect returns you to the app with its code. Cancel returns you to the app with access_denied. Connect works once the page has been in front of you for a moment, focused or with the pointer over it, so a click meant for something else can't approve it. A click sooner asks you to press it again.
If the app's request had a mistake, such as a missing PKCE challenge, the page says so and offers only to return to the app with the error. If JustDocs doesn't recognise the app, or the address it wants to return you to, a page explains that instead. It never sends you anywhere.
What connecting makes
Connecting makes an agent key for the app, in your name:
- Every send waits for approval. Anything the app asks to send waits for an owner or admin, as with any agent key. It can never sign. See the safety model.
- It counts toward the workspace's active keys: 3 agent keys below Teams & Platform, and 25 keys of either kind on every plan. If none are left, the page says so. Revoke a key first.
- It is yours alone. There is one key per person, per app, per workspace. Connecting the same app to the same workspace again updates your key's limits instead of making another. Another admin who connects the same app gets their own key, which acts in their name.
- It is listed on the workspace's API & agents page as a Connected app, with its domain when the app is verified.
- It stops as any key does: when it expires, when it's disconnected, or when you stop being an owner or admin.
A key that was disconnected or has expired is never used again. Connecting again makes a new one.
Tokens
A token response looks like this:
json
{
"access_token": "jd_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "jd_rt_…",
"scope": "read documents send"
}| Lifetime | |
|---|---|
| Approving the sign-in | 10 minutes, in the browser that started it |
| Authorization code | 1 minute, used once |
| Access token | 1 hour, or until the key expires if that's sooner |
| Refresh token | 30 days from when it's issued, never past the key's expiry |
| Sign-ins per connected app | The 10 most recent. A new sign-in ends the oldest. |
These are also on Plans and limits.
- Refresh tokens rotate. Each refresh returns a new refresh token, and the old one stops working. An app that refreshes twice at once, within 2 minutes, gets a second pair. Whichever pair it uses first continues, and the other stops.
- A reused refresh token ends the sign-in. Any other use of a refresh token that was already replaced means it leaked, so both tokens of that sign-in stop working. The same happens when a code is exchanged twice.
scopeis what the token can do: the key's permissions, within what was approved. A refresh can sendscopeto narrow it, never to widen it.- No
offline_accessis needed. Every sign-in gets a refresh token.
Permissions
The app asks for permissions with scope, separated by spaces:
| Permission | Allows |
|---|---|
read | Templates, documents, signing requests, intake links and approvals |
documents | Creating templates, and starting documents from them |
send | Asking to send signing requests and intake links, which waits for approval, and voiding requests |
- A request with no
scopeasks for all three. - Values JustDocs doesn't know, such as
offline_accessoropenid, are ignored. If none is left, the app getsinvalid_scope. - The person approving can narrow what was asked, never widen it.
- The app sees only the tools its token's permissions allow. See Keys and permissions for which tools each permission covers.
Ending a sign-in
- From the app: the app sends its access or refresh token to the revocation endpoint. That ends that one sign-in, both of its tokens. The key stays, with any other sign-ins.
- From JustDocs: Disconnect the app on the workspace's API & agents page. Every sign-in for that key stops at once. To use the app again, connect it again from the app.
Errors an app can get
From the token endpoint, as 400 JSON with error and error_description:
| Error | When | What to do |
|---|---|---|
invalid_grant | The code is unknown, expired or used. The code_verifier or redirect_uri doesn't match. The refresh token is unknown, expired or replaced. The sign-in ended, or the key was disconnected or expired. | Start a new sign-in. |
invalid_target | resource isn't https://tryjustdocs.com/mcp, or was sent twice. | Send that URL once, or leave resource out. |
invalid_scope | A refresh asked for a permission the sign-in doesn't have. | Ask for the same permissions or fewer. |
invalid_client | The request authenticated as a client, with a secret, an assertion or Authorization: Basic. Or the client_id is unknown. | Send client_id only: every client here is public. Basic authentication gets 401. |
invalid_request | A required parameter is missing, or the body isn't form encoded. | Check the request. |
unsupported_grant_type | Any grant other than these two. | Use authorization_code or refresh_token. |
A dead token gets invalid_grant, never a 429 of its own, so an app knows to sign in again rather than wait.
On /mcp, an access token that's expired, revoked or for another server gets 401 with error="invalid_token" in WWW-Authenticate. Refresh, and sign in again if the refresh fails.
When a person returns to the app from the JustDocs page, the redirect can carry access_denied (they cancelled), or invalid_request, unsupported_response_type, invalid_scope or invalid_target (the request had that mistake).
For client developers
| Endpoint | URL | |
|---|---|---|
| Authorization | https://tryjustdocs.com/oauth/authorize | GET, in the person's browser. Answers by query string. |
| Token | https://tryjustdocs.com/oauth/token | POST, application/x-www-form-urlencoded |
| Registration | https://tryjustdocs.com/oauth/register | POST, JSON (RFC 7591) |
| Revocation | https://tryjustdocs.com/oauth/revoke | POST, form encoded (RFC 7009) |
The authorization server's issuer is https://tryjustdocs.com. The metadata documents and the token, registration and revocation endpoints answer any origin, without credentials, so a client in a browser can call them.
Identify the app
Every client is public: it proves itself with PKCE and its registered addresses, never a secret.
With a Client ID Metadata Document, which JustDocs prefers, the client_id is the https address of a JSON document about the app:
- The address has a path, no query or fragment, and the default port. It must be written the one normal way, with a lowercase host.
- The document's
client_idequals that address exactly. client_nameis required. Names longer than 80 characters are cut, and names that contain "JustDocs" are refused.redirect_urislists the addresses the app returns people to. Addresses JustDocs can't use are left out, and at least one must remain.- The app must be able to sign in without a secret. Either
token_endpoint_auth_methods_supportedincludesnone, ortoken_endpoint_auth_methodisnoneor absent. A document with a client secret is refused. - Other fields, such as
logo_uriandjwks_uri, are ignored. JustDocs never fetches them. - JustDocs fetches the document as JSON, without following redirects. It must be at most 5 KB and arrive within 5 seconds. It's kept for its
Cache-Controlmax-age, from 5 minutes to a day. If it later names anotherclient_id, the app's sign-ins end.
With Dynamic Client Registration, the app posts its metadata to the registration endpoint:
redirect_urismust list 1 to 10 addresses, or the app getsinvalid_redirect_uri.client_nameis optional, and 1 to 80 characters. A longer name, or one that contains "JustDocs", getsinvalid_client_metadata. Without a name, the app is named after its first address's host.- Whatever authentication method, grant types or response types are asked for, the app is registered as public (
none), with codes and refresh tokens. Other fields are ignored. - The reply is
201with aclient_idthat startsjdc_, and no secret. - Registering the same name and addresses again returns the same
client_id. Loopback addresses are compared without their port. - One network address can register 60 new apps an hour. Registering the same app again doesn't count. Past the limit, the app gets
429withRetry-After. - A registered app that nobody connects is removed after 30 days unused. Register again if the authorization page says the app isn't recognised.
Redirect addresses must be https, or http to a loopback address on the person's own computer. A loopback address matches on any port. Custom schemes (myapp://) and fragments are refused.
The authorization request
| Parameter | |
|---|---|
response_type | code |
client_id | The document's address, or the registered client_id |
redirect_uri | One of the app's addresses. It can be left out only when the app has exactly one, on the web. |
code_challenge, code_challenge_method | Required. The method is S256. |
state | Recommended, up to 1,024 characters. It comes back only if sent. |
scope | Optional. See permissions. |
resource | Optional. If sent, it must be https://tryjustdocs.com/mcp. |
Other parameters, such as prompt and nonce, are ignored. Every answer to the app carries iss=https://tryjustdocs.com (RFC 9207), errors included. Check it.
JustDocs never redirects to an app by itself. An unknown client_id, or an address the app didn't register, gets an error page. A mistake in the request reaches the app only after a signed-in person chooses to return to it.
The token request
| Grant | Parameters |
|---|---|
authorization_code | code, code_verifier, client_id. redirect_uri is optional, and must match exactly if sent. resource is optional. |
refresh_token | refresh_token, client_id. scope is optional, to narrow. resource is optional. |
The token and revocation endpoints share the per-address ceiling on calls listed in Plans and limits.
Revocation
Post token and client_id, form encoded. Either token of a sign-in ends both. The answer is 200, including for a token that's unknown, expired or already revoked. A token issued to another client gets invalid_client, and nothing is revoked.