Sign in with Seoclerks
Let a member sign in to your app and act on their own account with an OAuth access token instead of an API key.
When to use it
Use OAuth when your app acts for other members. The member signs in on Seoclerks, sees what the app asks for, and allows it. Your app then sends that member's own access token with each request.
Use an API key instead when the calls are for your own account only. An app never receives a member's password, and the member can disconnect it at any time. Until an admin reviews an app, the Allow screen shows "Not reviewed by Seoclerks".
Discovery
The server publishes its OAuth settings at:
https://www.seoclerks.com/.well-known/oauth-authorization-server
Read it first. It lists the issuer, the authorization, token and revocation endpoints, the registration endpoint while dynamic registration is on, the supported scopes, and that PKCE S256 is the only challenge method. It also says that Client ID Metadata Documents are accepted. Nothing OpenID Connect is offered: there is no user info endpoint and no key set.
Getting a client id
There are three ways to get one.
A member's own app
A member registers an app under "Your apps" on /settings/api. An app is public, which uses PKCE and no secret, or confidential, which also gets a secret. The secret is shown once. A confidential app sends it with HTTP Basic (client_secret_basic) or in the token request body (client_secret_post). The member can rotate the secret later.
A Client ID Metadata Document
Your client_id can be an https URL that points at a JSON document you host. Seoclerks fetches it with a 5 second timeout and a 64 KB limit, follows no redirects, and caches it for up to 24 hours. The document must contain client_id equal to the URL, client_name, and redirect_uris. Set token_endpoint_auth_method to none. It may also contain client_uri, logo_uri and policy_uri.
Dynamic Client Registration
A client can register itself at:
POST https://www.seoclerks.com/oauth/register
Only public clients may register: token_endpoint_auth_method must be none. A registration is limited to 20 per address per hour.
curl -X POST "https://www.seoclerks.com/oauth/register" \
-H "Content-Type: application/json" \
-d '{"client_name":"Example CLI","redirect_uris":["http://localhost:7777/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"scope":"profile orders:read"}'
The answer gives a client_id beginning scd_. No secret is issued.
Redirect URIs
A redirect URI must match one registered for the app, exactly. Use https. A public app may also use a loopback address: http://localhost, http://127.0.0.1 or http://[::1], on any port, with the registered path. Wildcards, fragments and partial paths are refused.
The authorization request
Send the member to the authorization endpoint:
https://www.seoclerks.com/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback&scope=profile%20orders%3Aread&state=RANDOM&code_challenge=CHALLENGE&code_challenge_method=S256&resource=https%3A%2F%2Fwww.seoclerks.com%2Fapi%2Fv1
Send response_type=code, your client_id, the exact redirect_uri, a space-separated scope, an unguessable state, your PKCE code_challenge, code_challenge_method=S256, and resource=. The resource is https://www.seoclerks.com/api/v1 by default, or https://www.seoclerks.com/mcp with the mcp scope.
The member signs in, confirms with a second factor, and allows the app. Seoclerks then redirects to the registered URI with code, your state, and iss. Check that iss is https://www.seoclerks.com. An error comes back the same way with error, and never as a redirect to an address the app did not register.
Exchanging the code
Exchange the code once, within 60 seconds:
curl -X POST "https://www.seoclerks.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=CODE&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback&code_verifier=VERIFIER&client_id=YOUR_CLIENT_ID"
A confidential app adds its secret, with client_secret_basic or client_secret_post. The JSON answer holds access_token (beginning sca1_), token_type Bearer, expires_in 3600, refresh_token (beginning scr1_) and scope.
Call the API with the access token:
curl "https://www.seoclerks.com/api/v1/me" \
-H "Authorization: Bearer sca1_..."
Refreshing
When the access token expires, send grant_type=refresh_token:
curl -X POST "https://www.seoclerks.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=YOUR_CLIENT_ID"
Every refresh returns a new refresh token, and the old one stops working. Reusing an old refresh token revokes the whole connection and answers invalid_grant. The offline_access scope is accepted and is not needed. By default an access token lives one hour, a refresh token 90 days unused, and a connection must be allowed again after a year.
Revoking
Revoke a token at /oauth/revoke (RFC 7009):
curl -X POST "https://www.seoclerks.com/oauth/revoke" \
-d "token=TOKEN&client_id=YOUR_CLIENT_ID"
A member can also disconnect the app under "Connected apps" on /settings/api.
Scopes
An app may ask for profile, email, read, orders:read, listings:read, messages:read, balance:read, notifications:write, messages:write, orders:write, listings:write, requests:write, account:write, subscriptions:write, bumps:write, mcp and checkout:write.
write, deposits:write and payouts:write are never given to an app. checkout:write spends the member's balance only within the spending limit the member sets, 1 to 5000 USD per 7 days, and only while the site allows buying.
Errors
The token endpoint answers invalid_request, invalid_client, invalid_grant, invalid_scope, unsupported_grant_type or temporarily_unavailable. A malformed or missing request is invalid_request; a bad client secret is invalid_client; a used, expired or revoked code or refresh token is invalid_grant; a scope the app may not ask for is invalid_scope; an unknown grant_type is unsupported_grant_type; and OAuth switched off is temporarily_unavailable.
The API itself answers 401 when an access token has expired or has been revoked.