Skip to main content

Endpoint reference

Base URL: https://api.nanako.org. The same paths are served from https://www.nanako.org.


GET /oauth/authorize​

Starts the flow. This is a browser navigation, not a request your server makes.

Authentication: none.

ParameterInRequiredDefaultNotes
client_idqueryYes—
redirect_uriqueryYes—Byte-identical to a registered URI
response_typequeryNocodecode is the only accepted value
scopequeryNoopenid profileSpace-separated; send it explicitly
statequeryNoemptyEchoed to the callback unescaped — keep it URL-safe
code_challengequeryNoemptyPKCE
code_challenge_methodqueryNoplainOnly the exact string S256 selects SHA-256
promptqueryNoemptynone requests a no-UI attempt

Success: 302 to Nanako's sign-in and consent page. No authorization code exists yet at this point — the code reaches your callback after the user approves.

Errors: returned as JSON at this endpoint, not redirected to your callback. See Errors.

prompt=none​

Requests that Nanako complete the flow without showing any interface. Nanako redirects to your callback with an error instead of rendering a page:

SituationCallback receives
Not signed in?error=login_required
Signed in, no matching grant?error=consent_required
The grant check failed?error=interaction_required
Signed in with a matching grant?code=… — no consent screen

state is re-appended in every case. On any of the three errors, retry without prompt=none to fall back to the visible flow.

This is a top-level navigation, not an iframe: Nanako sends X-Frame-Options: DENY and a frame-blocking CSP on every response, so the hidden-iframe pattern used with some providers will not work.


POST /oauth/token​

Exchanges an authorization code for tokens, or refreshes an existing pair.

Authentication: client_secret_post, or PKCE for the authorization code grant. HTTP Basic is not supported.

Content-Type: application/x-www-form-urlencoded. A JSON body is not read.

grant_type=authorization_code​

ParameterRequiredNotes
grant_typeYesauthorization_code
codeYesFrom the callback, within 10 minutes, unused
client_idYes
redirect_uriYesByte-identical to the one used at /oauth/authorize
client_secretConditionalRequired unless the code was issued with a code_challenge
code_verifierConditionalRequired when the code was issued with a code_challenge

Which credential is required is decided by how the code was issued, not by what you send. A code issued without PKCE requires the secret; a code issued with PKCE requires the verifier.

grant_type=refresh_token​

ParameterRequiredNotes
grant_typeYesrefresh_token
refresh_tokenYesThe most recently issued one
client_idYes
client_secretYesAlways — there is no PKCE branch on this grant

Response​

200 for both grants:

{
"access_token": "aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY3zA5bC7dE9=",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "zY9xW7vU5tS3rQ1pN9mL7kJ5hG3fE1dC9bA7zY5xW3=",
"scope": "profile email"
}
FieldTypeNotes
access_tokenstringOpaque, 44 characters. Valid 2 hours.
token_typestringAlways Bearer
expires_innumberAlways 7200
refresh_tokenstringNew on every call. The previous one is now revoked.
scopestringThe granted scope

There is no id_token. See What is not supported.


GET /oauth/userinfo​

Returns the signed-in user's profile.

Authentication: Authorization: Bearer <access_token>. The prefix is case-sensitive.

Response: 200. sub is always present; every other claim depends on the token's scope and on the user having set the value.

{
"sub": "0198c4a1-7f3e-7b21-9d04-1a2b3c4d5e6f",
"name": "hana",
"picture": "https://cdn.nanako.org/avatars/…",
"email": "hana@example.com",
"email_verified": true,
"phone_number": "+8613800138000",
"phone_number_verified": true
}
ClaimScopeNotes
subalwaysStable, permanent user identifier. Key your records on this.
nameprofileDisplay name; the user can change it
pictureprofileAvatar URL; empty string if unset
emailemailOmitted entirely if the account has no email
email_verifiedemail
phone_numberphoneOmitted entirely if the account has no phone
phone_number_verifiedphone
sub has two formats

Accounts created recently have a UUID sub (0198c4a1-7f3e-7b21-9d04-1a2b3c4d5e6f). Accounts that predate a schema migration keep a numeric one (42). Both are stable and permanent for the account they identify.

Treat sub as an opaque string. Do not parse it, and do not assume it is a UUID.


POST /oauth/revoke​

Revokes one token.

Authentication: client_secret_post. Client credentials are mandatory, so a public (PKCE) client cannot call this endpoint.

Content-Type: application/x-www-form-urlencoded.

ParameterRequiredNotes
client_idYes
client_secretYes
tokenYesThe token to revoke
token_type_hintConditionalRequired to revoke a refresh token: refresh_token

Response: 200 {}.

The success response does not confirm that anything was revoked. A token that does not exist, belongs to another application, or was submitted without the matching token_type_hint all produce the same 200 {}. This is deliberate for the first two cases — it avoids disclosing which tokens exist — but it means the hint really matters. Revoke access and refresh tokens in two separate calls, each with its explicit hint.


GET /.well-known/openid-configuration​

Returns provider metadata.

The endpoint URLs in this document are wrong

token_endpoint, userinfo_endpoint and revocation_endpoint are advertised with an /api/v1 prefix that does not exist, and all three return 404. issuer also differs from the host the document is served from, which some strict OIDC libraries reject outright.

Do not point a client library at this URL. Configure the endpoints from this page.

For reference, the document advertises scopes_supported as openid, profile, email, phone; response_types_supported as code; grant_types_supported as authorization_code and refresh_token; token_endpoint_auth_methods_supported as client_secret_post; and code_challenge_methods_supported as plain and S256. Those five are accurate.

It also advertises jwks_uri and id_token_signing_alg_values_supported: ["RS256"]. Ignore both — no id_token is issued, and the key served at that URL belongs to an unrelated internal integration.