Skip to content

Developers

Sign in with JSON Resume

Let people bring their resume to your HR tool, job board or applicant tracker. They sign in with jsonresume.org and choose what you may see; you get their resume.json, kept up to date by them, with no forms to fill in again.

// auth.ts: Auth.js (NextAuth) with JSON Resume as a provider
providers: [
  {
    id: 'jsonresume',
    name: 'JSON Resume',
    type: 'oauth',
    authorization: {
      url: 'https://jsonresume.org/oauth/authorize',
      params: { scope: 'profile email resume:read' },
    },
    token: 'https://jsonresume.org/api/oauth/token',
    userinfo: 'https://jsonresume.org/api/oauth/userinfo',
    clientId: process.env.JSONRESUME_CLIENT_ID,
    clientSecret: process.env.JSONRESUME_CLIENT_SECRET,
    checks: ['pkce', 'state'],
    profile: (p) => ({
      id: p.sub,
      name: p.name ?? p.preferred_username,
      email: p.email,
      image: p.picture,
    }),
  },
],

OAuth 2.1

How it works

  1. Register your app

    Under Settings → Your OAuth apps, give its name and the URLs people come back to. You get a client ID and a secret, shown once. Keep the secret on your server.
  2. Send people to jsonresume.org

    Link to the authorization page with the scopes you need and a random state. They sign in (or sign up), see exactly what you asked for, and choose Allow.
    https://jsonresume.org/oauth/authorize?response_type=code
      &client_id=$CLIENT_ID
      &redirect_uri=https%3A%2F%2Facme.example%2Fauth%2Fjsonresume%2Fcallback
      &scope=profile%20email%20resume:read
      &state=$STATE
  3. Swap the code for tokens

    They come back to your redirect URI with code and your state. From your server, exchange the code within 10 minutes. Access tokens last an hour; refresh tokens 90 days, and each is used once.
    curl https://jsonresume.org/api/oauth/token -u "$CLIENT_ID:$CLIENT_SECRET" \
      -d grant_type=authorization_code \
      -d code=$CODE \
      -d redirect_uri=https://acme.example/auth/jsonresume/callback
  4. Read their resume

    Send the access token as a Bearer token. Who they are is at /api/oauth/userinfo; their resume, as JSON Resume, at /api/oauth/resume.
    curl https://jsonresume.org/api/oauth/resume -H "Authorization: Bearer $ACCESS_TOKEN"

Ask for what you need

Scopes

Scopes, space-separated in scope=
ScopeLets your app
profileTheir username, name, photo and page, from /api/oauth/userinfo. What you get when you ask for nothing.
emailTheir email address, and whether it is verified.
resume:readTheir resume as JSON Resume, from GET /api/oauth/resume.
resume:writeReplace their resume with PUT /api/oauth/resume. Saved to their gist when their page reads from it, and checked against the schema.
mcpEverything, through the MCP server. For agents, not HR tools.

Reference

Endpoints

Endpoints
Authorizationhttps://jsonresume.org/oauth/authorize
TokenPOST https://jsonresume.org/api/oauth/token
User infoGET https://jsonresume.org/api/oauth/userinfo
ResumeGET, PUT https://jsonresume.org/api/oauth/resume
RevocationPOST https://jsonresume.org/api/oauth/revoke
Metadata (RFC 8414)https://jsonresume.org/.well-known/oauth-authorization-server

Details

Good to know

  • Use sub as the user’s ID: usernames can change.
  • PKCE (S256) is optional for apps with a secret and required for apps without one. Browser and mobile apps without a server can register themselves (RFC 7591, POST /api/oauth/register) and must use PKCE.
  • People see every app they’ve connected under Settings → Connected apps, and can disconnect it there. Revoke a token yourself when someone disconnects on your side.
  • Errors follow RFC 6749 and 6750: invalid_grant, invalid_client, invalid_token (401) and insufficient_scope (403).

Building for agents instead?