Identifying your app
Before your app can ask a user for access, ShiftSync needs to know who it is. There are two ways to arrange that, and you pick one.
Option 1 — a client ID metadata document (recommended)
Section titled “Option 1 — a client ID metadata document (recommended)”Host a JSON document at an HTTPS URL you control, and use that URL as your client_id. There
is no registration step and nothing to store: ShiftSync fetches the URL when your app first sends
a user to authorise, and reads your app’s details from it.
This is the mechanism the MCP specification now
prefers,
defined by
draft-ietf-oauth-client-id-metadata-document.
Because your identity is a URL you host, the same client_id works against any authorization
server that supports it — there is nothing to re-register when you add one.
Check that ShiftSync supports it before you rely on it:
GET https://api.shiftsync.app/.well-known/oauth-authorization-server{ "client_id_metadata_document_supported": true}The document
Section titled “The document”{ "client_id": "https://mcp.example.com/oauth/client.json", "client_name": "Example MCP Client", "client_uri": "https://mcp.example.com", "logo_uri": "https://mcp.example.com/logo.png", "redirect_uris": ["http://127.0.0.1:3000/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none"}client_id, client_name and redirect_uris are required. client_id must be exactly the
URL the document is served from — byte for byte, same case, same spelling. Anything else is
refused.
Rules for the URL
Section titled “Rules for the URL”- HTTPS, with a path (
https://example.com/client.json, nothttps://example.com). - No fragment, no query string, no
.or..path segments, no username or password. - Already in canonical form.
https://Example.com/c.jsonandhttps://example.com:443/c.jsonare refused — write them the way a URL parser would. - At most 512 characters.
Two identifiers that differ in any way are two different apps, with separate user grants. Pick one spelling and keep it.
How ShiftSync fetches it
Section titled “How ShiftSync fetches it”- Exactly one request, with
Accept: application/json. Redirects are not followed — serve the document at the URL itself, with a200and a JSON content type. - At most 5 kB is read.
- Private, loopback and link-local addresses are refused.
The result is cached, honouring your Cache-Control (s-maxage first, then max-age) or
Expires, clamped to between 5 minutes and 24 hours. A static file on a CDN is exactly right. If
you change the document, allow up to your cache lifetime for it to take effect — and note that
changing your redirect_uris while a user is midway through authorising will not affect that
in-flight authorisation.
Limits
Section titled “Limits”| Field | Limit |
|---|---|
| Whole document | 5 kB |
redirect_uris |
5 entries, 512 characters each |
client_name |
255 characters |
logo_uri, client_uri |
HTTPS only |
scope |
Every value must be a scope ShiftSync issues |
scope is optional. Leave it out and your app may request any scope — which is usually what you
want for a document used against several authorization servers.
Only public clients, for now
Section titled “Only public clients, for now”The document is public, so it can never carry a secret. client_secret,
client_secret_expires_at and any client_secret_* value for token_endpoint_auth_method are
refused outright.
private_key_jwt with jwks/jwks_uri is the specification’s answer for a confidential client.
ShiftSync does not support it yet and refuses a document that declares it, with
"private_key_jwt is not supported yet". Use "token_endpoint_auth_method": "none" and PKCE,
which is mandatory regardless.
Option 2 — register a client
Section titled “Option 2 — register a client”Use the App console to create an app and get a client_id (and, for a confidential
app, a client_secret). This is the right choice when you want an app you manage from a UI, with
an icon and a fixed set of scopes.
Dynamic Client Registration (POST /oauth/register, RFC 7591) also still works and is still
advertised, for clients that cannot host a document. The MCP specification deprecates it in favour
of the option above.
Redirect URIs
Section titled “Redirect URIs”Whichever option you pick, the redirect_uri on an authorization request must match one you
declared, by exact string comparison — except for a loopback URI over plain HTTP
(http://127.0.0.1…, http://[::1]…, http://localhost…), where any port is accepted, as
RFC 8252 §7.3 requires. A native app
can therefore declare http://127.0.0.1:3000/callback and bind whatever port is free at runtime.
Scheme, host, path and query must still match exactly.
Errors
Section titled “Errors”A rejected authorization request returns 400 with an OAuth error body, and — for a metadata
document problem — an error_description naming the cause:
{ "error": "invalid_request", "error_description": "client_id in the metadata document does not match the requested client_id"}Others you may see: client_id is not a valid client identifier URL, the client_id metadata document could not be retrieved, the client_id metadata document is not valid JSON, the client_id metadata document is missing required fields, the client_id metadata document contains invalid client metadata, client_secret must not appear in a client_id metadata document, and
temporarily unable to retrieve the client_id metadata document (back off and retry).