cURL quickstart
🔑 Auth:
client_id+client_secret(server-side) · 👤 This IS the login — standard OpenID Connect · 💻 Raw HTTP, no code · ⏱ ~5 minutes
What you’ll build: a complete login walked by hand — browser for the consent screen, cURL for everything else — so you can see exactly what your server will do.
Your real values are pre-filled on your app’s Quick setup tab in the Developer Portal .
Discovery lives at https://idp.valyd.work/api/.well-known/openid-configuration — everything
below is listed there.
1. Create the app
In the Developer Portal create an application, enable the profile
scope, and register a redirect URI. For this walkthrough any URL you can read
from the address bar works, e.g.:
http://localhost:8080/callbackCopy the client_id and the one-time client_secret.
2. Build the authorize URL and open it
Generate a random state (and nonce), then open this URL in a browser:
STATE=$(openssl rand -hex 16) # store it — you compare it on the callback
NONCE=$(openssl rand -hex 16)
echo "https://idp.valyd.work/api/auth/oidc/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&response_type=code&scope=openid%20profile&state=$STATE&nonce=$NONCE"Anatomy: response_type=code always; scope is space-separated (URL-encoded) and must include
openid; state is required and echoed back unchanged (your CSRF check); nonce is
recommended and bound into the id_token.
3. Grab the code from the callback
After you approve the consent screen, the browser lands on your redirect URI:
http://localhost:8080/callback?code=AUTH_CODE_HERE&state=...Check that state in the URL equals the $STATE you generated — then copy the code. It is
single-use and expires in seconds, so exchange it immediately.
4. Exchange the code for tokens
Form-encoded POST to the token endpoint (this is the step that must always run server-side —
it carries your client_secret):
curl -X POST https://idp.valyd.work/api/auth/oidc/token \
-d grant_type=authorization_code \
-d code=AUTH_CODE_HERE \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d redirect_uri=http://localhost:8080/callbackThe response is top-level token JSON — no data wrapper:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "rfrsh_abc123...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "openid profile"
}The id_token is an RS256 JWT — in production verify it against the JWKS at
https://idp.valyd.work/api/auth/oidc/jwks.json and check its nonce claim equals your
$NONCE.
5. Read the user
curl -H "Authorization: Bearer ACCESS_TOKEN_HERE" \
https://idp.valyd.work/api/auth/oidc/userinfoReturns the profile with valyd_id (the stable pseudonymous ID — use it as your user key) and
id_verified. The same Bearer token also reads /api/auth/oidc/licenses and
/api/auth/oidc/verifications, gated by the scopes the user approved.
Checkpoint: the callback’s state matched yours; the token exchange returned HTTP 200 with
top-level access_token, refresh_token, and id_token; and the userinfo call returned
valyd_id and id_verified. Replaying step 4 with the same code fails with invalid_grant —
codes are single-use.
Access tokens last ~15 minutes. Renew with grant_type=refresh_token at the same endpoint —
rotation is on, so always persist the new refresh_token you get back. Details in
Authentication.
Troubleshooting
redirect_urimismatch — the URI at/authorizeand/tokenmust be identical and exactly match a registered redirect URI. See Errors & troubleshooting.invalid_grant— the code expired or was already exchanged; go back to step 2 for a fresh one. See Errors & troubleshooting.invalid_scope/ missingopenid—scopemust includeopenid, and every requested scope must be enabled on the app in the portal. See Errors & troubleshooting.
Next steps
- Scopes — request
verifications,email, or license scopes. - Account API — every endpoint the Bearer token can read.
- Attach a verification — run a new KYC or license check with the user’s token so the proof saves to their Valyd account.
- Ready to code it? The official SDK (
npm install @valyd/sdk) does steps 2–5, plus PKCE and id_token verification, in two calls — see the Node.js quickstart.