{
  "info": {
    "_postman_id": "42b80e82-f923-4c04-ad62-0c4537cce5c3",
    "name": "Valyd OIDC (Partner-Facing)",
    "description": "## Valyd OpenID Connect (Partner-Facing)\n\nThis collection begins **after Valyd redirects your app with an authorization `code`** on your registered `redirect_uri`.\n\n**Standard flow**:\n1) You receive `code` on your redirect URI (e.g. `https://yourapp.com/callback?code=...&state=...`).\n2) Compare the callback `state` to the value you sent (CSRF check), then call **/token** to exchange the `code` for `access_token` and `refresh_token`.\n3) Use `access_token` to call **/userinfo**, **/licenses**, and **/verifications**.\n4) When `access_token` expires, call **/token** with `grant_type=refresh_token` and persist the rotated refresh token.\n\n**Security notes**:\n- Keep your `client_secret` server-side only.\n- `access_token` is short-lived; `refresh_token` is longer-lived.\n- Always use HTTPS.\n",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_exporter_id": "38659724",
    "_collection_link": "https://grey-space-997213.postman.co/workspace/My-Workspace~a44fc959-adf9-4ea8-87f1-78afa8fdb8ad/collection/17885942-42b80e82-f923-4c04-ad62-0c4537cce5c3?action=share&source=collection_link&creator=38659724"
  },
  "item": [
    {
      "name": "1) Token — Exchange Code",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "if (pm.response.code === 200) {",
              "  const json = pm.response.json();",
              "  pm.collectionVariables.set('access_token', json.access_token || '');",
              "  pm.collectionVariables.set('refresh_token', json.refresh_token || '');",
              "  pm.collectionVariables.set('expires_in', String(json.expires_in || ''));",
              "}",
              ""
            ],
            "type": "text/javascript"
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"grant_type\": \"authorization_code\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\",\n  \"code\": \"{{code}}\",\n  \"redirect_uri\": \"{{redirect_uri}}\"\n}"
        },
        "url": {
          "raw": "{{oauth_base_url}}/token",
          "host": [
            "{{oauth_base_url}}"
          ],
          "path": [
            "token"
          ]
        },
        "description": "Exchange the one-time `code` you received on your `redirect_uri` for tokens at the standard OIDC token endpoint (`POST /api/auth/oidc/token`).\n\n**Body fields**:\n- `grant_type`: must be `authorization_code`\n- `client_id`: your assigned client ID\n- `client_secret`: your client secret (server-side only)\n- `code`: the authorization code received from Valyd (single-use)\n- `redirect_uri`: the exact value used at `/authorize`\n\n**Successful response** is a standard top-level token JSON: `access_token`, `refresh_token`, `id_token` (RS256 — verify the `nonce` claim), `token_type`, `expires_in`, `scope`.\n"
      },
      "response": [
        {
          "name": "200 OK",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"access_token\": \"eyJhbGciOi...\",\n  \"refresh_token\": \"rfrsh_abc123...\",\n  \"id_token\": \"eyJhbGciOiJSUzI1NiIs...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 900,\n  \"scope\": \"openid profile verifications\"\n}"
        },
        {
          "name": "401 invalid_client",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "Unauthorized",
          "code": 401,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": { \"code\": \"invalid_client\", \"message\": \"client_id/client_secret invalid\" }\n}"
        }
      ]
    },
    {
      "name": "2a) Userinfo — Profile",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{base_url}}/userinfo",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "userinfo"
          ]
        },
        "description": "Get user profile and identity fields for the bearer `access_token`.\n\nTypical fields: `sub` (subject ID), `email`, `first_name`, `last_name`, `full_name`, `valyd_id`, `id_verified`, `created_at`.\n"
      },
      "response": [
        {
          "name": "200 OK",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": true,\n  \"data\": {\n    \"sub\": \"valyd_225c7f2ac450496f97bbbc57354a5898\",\n    \"email\": \"candidate@example.com\",\n    \"first_name\": \"Candidate\",\n    \"last_name\": \"Example\",\n    \"full_name\": \"Candidate Example\",\n    \"valyd_id\": \"valyd_225c7f2ac450496f97bbbc57354a5898\",\n    \"id_verified\": true,\n    \"created_at\": \"2025-09-10T12:00:00Z\"\n  }\n}"
        },
        {
          "name": "401 invalid_token",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "Unauthorized",
          "code": 401,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": { \"code\": \"invalid_token\", \"message\": \"token invalid/expired\" }\n}"
        }
      ]
    },
    {
      "name": "2b) Licenses — Verification Snapshot",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{base_url}}/licenses",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "licenses"
          ]
        },
        "description": "Returns a snapshot of the user's professional licenses as verified by Valyd.\n\nExample items include: nursing licenses, CDL endorsements, CPR/BLS, Food Handler, etc. Each record typically carries at least a type, number, status, expiry date, and issuer.\n"
      },
      "response": [
        {
          "name": "200 OK",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": true,\n  \"data\": {\n    \"licenses\": [\n      {\n        \"type\": \"nurse_licenses\",\n        \"number\": \"RN-123456\",\n        \"status\": \"Active\",\n        \"expires_on\": \"2027-06-30\",\n        \"issuer\": \"CA Board of Nursing\"\n      },\n      {\n        \"type\": \"cpr_certification\",\n        \"number\": \"CPR-998877\",\n        \"status\": \"Active\",\n        \"expires_on\": \"2026-05-15\",\n        \"issuer\": \"American Heart Association\"\n      }\n    ]\n  }\n}"
        }
      ]
    },
    {
      "name": "2c) Verifications — Identity Payload",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Accept",
            "value": "application/json"
          },
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{base_url}}/verifications",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "verifications"
          ]
        },
        "description": "Returns identity/verification results for the bearer user (e.g., ID verification flag, face match confidence, last checked timestamp). Use alongside **/userinfo** for a full picture.\n"
      },
      "response": [
        {
          "name": "200 OK",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": true,\n  \"data\": {\n    \"verifications\": {\n      \"id_verified\": true,\n      \"face_match\": 0.98,\n      \"last_checked\": \"2025-09-11T12:00:00Z\"\n    }\n  }\n}"
        }
      ]
    },
    {
      "name": "3) Refresh — Rotate Tokens (OIDC)",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "if (pm.response.code === 200) {",
              "  const json = pm.response.json();",
              "  pm.collectionVariables.set('access_token', json.access_token || '');",
              "  pm.collectionVariables.set('refresh_token', json.refresh_token || '');",
              "  pm.collectionVariables.set('expires_in', String(json.expires_in || ''));",
              "}",
              ""
            ],
            "type": "text/javascript"
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Accept",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"grant_type\": \"refresh_token\",\n  \"refresh_token\": \"{{refresh_token}}\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}"
        },
        "url": {
          "raw": "{{oauth_base_url}}/token",
          "host": [
            "{{oauth_base_url}}"
          ],
          "path": [
            "token"
          ]
        },
        "description": "Use your `refresh_token` to obtain a new `access_token` at the standard OIDC token endpoint (`POST /api/auth/oidc/token`, `grant_type: refresh_token`). Requires client authentication (client_id + client_secret) and the token must belong to that client.\n\nRotation is on for every refresh: the token you send is revoked and a new top-level `refresh_token` returned — store it. Replaying a rotated-away token revokes every refresh token for that user and client.\n"
      },
      "response": [
        {
          "name": "200 OK",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "OK",
          "code": 200,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"access_token\": \"eyJhbGciOi...\",\n  \"refresh_token\": \"rfrsh_new456...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 900,\n  \"scope\": \"openid profile verifications\"\n}"
        },
        {
          "name": "400 invalid_grant",
          "originalRequest": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": ""
            }
          },
          "status": "Bad Request",
          "code": 400,
          "_postman_previewlanguage": "json",
          "header": [],
          "cookie": [],
          "body": "{\n  \"success\": false,\n  \"error\": { \"code\": \"invalid_grant\", \"message\": \"refresh_token is invalid or expired\" }\n}"
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "packages": {},
        "exec": [
          ""
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "packages": {},
        "exec": [
          ""
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "https://idp.valyd.work/api/auth/oidc"
    },
    {
      "key": "oauth_base_url",
      "value": "https://idp.valyd.work/api/auth/oidc"
    },
    {
      "key": "client_id",
      "value": "YOUR_CLIENT_ID"
    },
    {
      "key": "client_secret",
      "value": "YOUR_CLIENT_SECRET"
    },
    {
      "key": "code",
      "value": ""
    },
    {
      "key": "refresh_token",
      "value": ""
    },
    {
      "key": "expires_in",
      "value": ""
    },
    {
      "key": "access_token",
      "value": ""
    },
    {
      "key": "redirect_uri",
      "value": "https://yourapp.com/callback"
    }
  ]
}
