{
  "openapi": "3.1.0",
  "info": {
    "title": "Valyd ID API",
    "version": "1.10.1",
    "summary": "Standards-based OpenID Connect for verified profiles, licenses, and identity claims.",
    "description": "Valyd ID implements one standards-based OIDC namespace at /api/auth/oidc/*, plus discovery. UserInfo returns top-level OIDC claims. Credentials are created in the Developer Portal; keep client_secret and token exchange server-side.",
    "contact": {
      "name": "Valyd Developer Docs",
      "url": "https://docs.valyd.vip/docs/overview.md"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://docs.valyd.vip"
    }
  },
  "servers": [
    {
      "url": "https://idp.valyd.vip",
      "description": "Valyd ID host"
    }
  ],
  "tags": [
    {
      "name": "OIDC Resources",
      "description": "Bearer-token profile, license, and verification resources under the canonical OIDC namespace."
    },
    {
      "name": "OIDC",
      "description": "Standards-based OpenID Connect endpoints."
    },
    {
      "name": "Consent",
      "description": "Consent-gated release of RAW account attributes (Valyd 1.0 §2). Every other Account API returns proofs only (pseudonym, id_verified, badges, age bands) and never raw KYC. This is the only path by which raw attributes leave Valyd, and only after the subject approves in their Valyd app. The payload is sealed to YOUR X25519 public key, so Valyd cannot read it unless you opt into managed custody."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/.well-known/openid-configuration": {
      "get": {
        "tags": [
          "OIDC"
        ],
        "summary": "OIDC discovery document",
        "description": "OpenID Connect discovery document for auto-configuration. Public; returns bare JSON.",
        "operationId": "oidcDiscovery",
        "security": [],
        "responses": {
          "200": {
            "description": "Discovery document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OidcDiscovery"
                },
                "example": {
                  "issuer": "https://idp.valyd.vip",
                  "authorization_endpoint": "https://idp.valyd.vip/api/auth/oidc/authorize",
                  "token_endpoint": "https://idp.valyd.vip/api/auth/oidc/token",
                  "userinfo_endpoint": "https://idp.valyd.vip/api/auth/oidc/userinfo",
                  "jwks_uri": "https://idp.valyd.vip/api/auth/oidc/jwks.json",
                  "response_types_supported": [
                    "code",
                    "id_token",
                    "token id_token"
                  ],
                  "grant_types_supported": [
                    "authorization_code",
                    "refresh_token"
                  ],
                  "subject_types_supported": [
                    "public"
                  ],
                  "id_token_signing_alg_values_supported": [
                    "RS256"
                  ],
                  "scopes_supported": [
                    "openid",
                    "profile",
                    "email",
                    "verifications",
                    "zkp"
                  ],
                  "token_endpoint_auth_methods_supported": [
                    "client_secret_post",
                    "client_secret_basic"
                  ],
                  "claims_supported": [
                    "sub",
                    "iss",
                    "aud",
                    "exp",
                    "iat",
                    "name",
                    "email",
                    "preferred_username",
                    "first_name",
                    "last_name"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/oidc/authorize": {
      "get": {
        "tags": [
          "OIDC"
        ],
        "summary": "OIDC authorization (browser redirect)",
        "description": "Standard OpenID Connect authorization-code entry point. This is a browser redirect to the Valyd login/consent screen, not a JSON API call. Parameters follow OIDC Core; the docs do not enumerate them, so the standard set is shown for guidance.",
        "operationId": "oidcAuthorize",
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "examples": [
                "code"
              ]
            },
            "description": "Use `code` for the authorization-code flow."
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "Must exactly match a registered redirect URI."
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "examples": [
                "openid profile email"
              ]
            },
            "description": "Space-separated scopes; must include `openid`."
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the Valyd login/consent screen; on completion, redirects back to `redirect_uri` with `code` and `state`."
          }
        }
      }
    },
    "/api/auth/oidc/token": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "OIDC token exchange",
        "description": "Exchange the OIDC authorization code for `access_token`, `id_token`, and `refresh_token`. Client authentication is `client_secret_post` (shown) or `client_secret_basic`. The docs do not print the exact request/response bodies; the OIDC Core standard shape is shown for guidance.",
        "operationId": "oidcToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "code",
                  "redirect_uri",
                  "client_id"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "examples": [
                      "authorization_code"
                    ]
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Required for client_secret_post."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens issued (bare OIDC token response — not enveloped).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OidcTokenResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/oidc/userinfo": {
      "get": {
        "tags": [
          "OIDC Resources"
        ],
        "summary": "Get user profile",
        "description": "Retrieve the authenticated user's profile. Requires the `profile` scope.",
        "operationId": "oidcUserinfo",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "User profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserInfoResponse"
                },
                "example": {
                  "sub": "valyd_225c7f2ac450496f97bbbc57354a5898",
                  "valyd_id": "valyd_225c7f2ac450496f97bbbc57354a5898",
                  "preferred_username": "johndoe",
                  "email": "user@example.com",
                  "name": "John Doe",
                  "id_verified": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/auth/oidc/jwks.json": {
      "get": {
        "tags": [
          "OIDC"
        ],
        "summary": "JSON Web Key Set",
        "description": "Public keys (RS256) for validating ID-token (JWT) signatures.",
        "operationId": "oidcJwks",
        "security": [],
        "responses": {
          "200": {
            "description": "JWKS document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/attribute-request": {
      "post": {
        "tags": [
          "Consent"
        ],
        "operationId": "createAttributeRequest",
        "summary": "Request raw attributes (starts consent)",
        "description": "Ask the subject to release raw attributes. Creates a PENDING request and pushes a consent prompt to their Valyd app. You then poll the result endpoint. The user must pass a face check before they can approve. Expires in 10 minutes.\n\nSDK: `valyd.attributes.request({ attributes, valydId, purpose })` — it generates the keypair and decrypts the result for you.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client_id",
                  "attributes",
                  "requester_public_key",
                  "valyd_id"
                ],
                "properties": {
                  "client_id": {
                    "type": "string",
                    "description": "Your application client_id."
                  },
                  "attributes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Requested attributes. Raw identity: `full_name` (alias `legal_name`), `first_name`, `last_name`, `email`, `phone`, `dob`, `country`. Proofs (non-PII, preferred over raw `dob`): `id_verified`, `is_16_plus`, `is_18_plus`, `is_21_plus`, `is_30_plus`, `is_65_plus`. Unknown values are dropped; if nothing releasable remains you get 400."
                  },
                  "requester_public_key": {
                    "type": "string",
                    "description": "base64 of your 32-byte X25519 PUBLIC key. The approved payload is sealed to it. SDK: `generateRequesterKeypair()`."
                  },
                  "valyd_id": {
                    "type": "string",
                    "description": "The subject: the valyd_id you received at login (e.g. valyd_ab12…). Legacy alias: anon_id."
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Human-readable reason, shown to the user on the consent screen. Be specific — vague purposes get denied."
                  },
                  "redirect_uri": {
                    "type": "string",
                    "description": "Optional. Where to send the user after they decide."
                  },
                  "state": {
                    "type": "string",
                    "description": "Optional. Echoed back on redirect (CSRF)."
                  },
                  "managed_private_key": {
                    "type": "string",
                    "description": "Optional. base64 of your 32-byte X25519 SECRET key, handing custody to Valyd so we open the payload for you (stored encrypted) and `result` returns plaintext. OMIT for true end-to-end — then only you can decrypt."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent requested.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "request_id": {
                          "type": "string",
                          "description": "Poll the result endpoint with this."
                        },
                        "status": {
                          "type": "string",
                          "example": "pending"
                        },
                        "attributes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "The attributes actually accepted."
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No releasable attributes, or requester_public_key is not base64 of a 32-byte X25519 key."
          },
          "401": {
            "description": "Unknown or missing client_id."
          }
        }
      }
    },
    "/api/auth/attribute-request/{requestId}/result": {
      "parameters": [
        {
          "name": "requestId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "request_id from the create call."
        }
      ],
      "get": {
        "tags": [
          "Consent"
        ],
        "operationId": "getAttributeRequestResult",
        "summary": "Fetch the sealed payload",
        "description": "Poll until the user decides. `pending` = not yet. Once approved you get the sealed payload ONCE (single-use, short-lived) — store it immediately.\n\nSelf-custody: decrypt `sealed_payload` with your X25519 secret key (SDK does this automatically). Managed custody: `custody: \"valyd\"` and `attributes` come back as plaintext.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your client_id — must match the one that created the request."
          }
        ],
        "responses": {
          "200": {
            "description": "pending, or released.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "released"
                          ]
                        },
                        "custody": {
                          "type": "string",
                          "enum": [
                            "self",
                            "valyd"
                          ],
                          "description": "`valyd` only when you supplied managed_private_key."
                        },
                        "attributes": {
                          "type": "object",
                          "description": "Plaintext — managed custody only."
                        },
                        "sealed_payload": {
                          "type": "string",
                          "description": "Self-custody: base64 sealed box, decrypt with your secret key."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "User denied the request."
          },
          "404": {
            "description": "Request not found (or not yours)."
          },
          "410": {
            "description": "Expired: the one-time code lapsed or the payload was purged."
          }
        }
      }
    },
    "/api/auth/oidc/licenses": {
      "get": {
        "tags": [
          "OIDC Resources"
        ],
        "summary": "Get professional licenses",
        "description": "Returns a snapshot of the user's professional licenses verified by Valyd (e.g. nursing, CDL, CPR/BLS, Food Handler). The required scope is not declared in the source docs; `doctor_license` is the likely scope but is unconfirmed.",
        "operationId": "oidcLicenses",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "License snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicensesResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "licenses": [
                      {
                        "type": "nurse_licenses",
                        "number": "RN-123456",
                        "status": "Active",
                        "expires_on": "2027-06-30",
                        "issuer": "CA Board of Nursing"
                      },
                      {
                        "type": "cpr_certification",
                        "number": "CPR-998877",
                        "status": "Active",
                        "expires_on": "2026-05-15",
                        "issuer": "American Heart Association"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/auth/oidc/verifications": {
      "get": {
        "tags": [
          "OIDC Resources"
        ],
        "summary": "Get identity verifications",
        "description": "Returns ID-verification status and face-match confidence. Requires the `verifications` scope.",
        "operationId": "oidcVerifications",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verification snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationsResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "verifications": {
                      "id_verified": true,
                      "face_match": 0.98,
                      "last_checked": "2025-09-11T12:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "OIDC access token from /api/auth/oidc/token. Send as Authorization: Bearer <access_token>."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "OAuth error envelope.",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_client",
                  "invalid_token",
                  "insufficient_scope",
                  "invalid_grant",
                  "invalid_request",
                  "access_denied"
                ],
                "description": "Machine-readable error code."
              },
              "message": {
                "type": "string",
                "description": "Human-readable message."
              }
            }
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret",
          "code"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "authorization_code"
          },
          "client_id": {
            "type": "string",
            "description": "Your Client ID from the Developer Portal."
          },
          "client_secret": {
            "type": "string",
            "description": "Your Client Secret (server-side only)."
          },
          "code": {
            "type": "string",
            "description": "One-time authorization code from the callback (valid 5 minutes)."
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "access_token": {
                "type": "string"
              },
              "token_type": {
                "type": "string",
                "examples": [
                  "Bearer"
                ]
              },
              "expires_in": {
                "type": "integer",
                "description": "Access-token lifetime in seconds.",
                "examples": [
                  900
                ]
              },
              "refresh_token": {
                "type": "string"
              },
              "user": {
                "$ref": "#/components/schemas/TokenUser"
              }
            }
          }
        }
      },
      "TokenUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "username": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "valyd_id": {
            "type": "string"
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RefreshRequest": {
        "type": "object",
        "required": [
          "client_id",
          "client_secret",
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string"
          },
          "rotate_refresh": {
            "type": "boolean",
            "description": "Defaults to true. Set false only if you cannot store the replacement token."
          },
          "client_id": {
            "type": "string",
            "description": "Your application's client ID."
          },
          "client_secret": {
            "type": "string",
            "description": "Your client secret (server-side only)."
          }
        }
      },
      "RefreshResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "tokens": {
                "type": "object",
                "properties": {
                  "access_token": {
                    "type": "string"
                  },
                  "token_type": {
                    "type": "string",
                    "examples": [
                      "Bearer"
                    ]
                  },
                  "expires_in": {
                    "type": "integer",
                    "examples": [
                      900
                    ]
                  },
                  "refresh_token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "UserInfoResponse": {
        "type": "object",
        "required": [
          "sub"
        ],
        "properties": {
          "sub": {
            "type": "string"
          },
          "valyd_id": {
            "type": "string"
          },
          "preferred_username": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "id_verified": {
            "type": "boolean"
          }
        }
      },
      "LicensesResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "licenses": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "number": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "expires_on": {
                      "type": "string",
                      "format": "date"
                    },
                    "issuer": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "VerificationsResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "verifications": {
                "type": "object",
                "properties": {
                  "id_verified": {
                    "type": "boolean"
                  },
                  "face_match": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Face-match confidence (0–1)."
                  },
                  "last_checked": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        }
      },
      "OidcDiscovery": {
        "type": "object",
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "userinfo_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "jwks_uri": {
            "type": "string",
            "format": "uri"
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "id_token_signing_alg_values_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "claims_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "OidcTokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "id_token": {
            "type": "string",
            "description": "Signed JWT (RS256)."
          },
          "refresh_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "examples": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "examples": [
              3600
            ]
          }
        }
      },
      "OidcUserInfo": {
        "type": "object",
        "properties": {
          "sub": {
            "type": "string"
          },
          "preferred_username": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "email_verified": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "picture": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request — e.g. `invalid_grant` (bad/expired/used code or refresh token) or `invalid_request` (missing parameter).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "code": "invalid_grant",
                "message": "refresh_token is invalid or expired"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication failed — `invalid_client` (bad client credentials) or `invalid_token` (token invalid/expired).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "code": "invalid_token",
                "message": "token invalid/expired"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The access token lacks the scope required by this endpoint.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "code": "insufficient_scope",
                "message": "The request requires the profile scope"
              }
            }
          }
        }
      }
    }
  }
}
