{"id":18896484,"url":"https://github.com/descope/descope-java","last_synced_at":"2025-04-15T01:51:31.875Z","repository":{"id":182425328,"uuid":"626049704","full_name":"descope/descope-java","owner":"descope","description":"Java library used to integrate with Descope","archived":false,"fork":false,"pushed_at":"2025-04-11T17:19:33.000Z","size":657,"stargazers_count":25,"open_issues_count":12,"forks_count":2,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-11T17:47:15.904Z","etag":null,"topics":["authentication","descope","java","java-sdk","sdk","spring","spring-boot","spring-security"],"latest_commit_sha":null,"homepage":"https://docs.descope.com","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/descope.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-04-10T17:32:59.000Z","updated_at":"2025-04-09T08:26:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"99185540-4caf-4996-b6b7-c06f11ba7f8b","html_url":"https://github.com/descope/descope-java","commit_stats":null,"previous_names":["descope/descope-java"],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/descope","download_url":"https://codeload.github.com/descope/descope-java/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248991538,"owners_count":21194894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","descope","java","java-sdk","sdk","spring","spring-boot","spring-security"],"created_at":"2024-11-08T08:34:09.244Z","updated_at":"2025-04-15T01:51:31.866Z","avatar_url":"https://github.com/descope.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/descope/descope-java/actions/workflows/ci.yaml/badge.svg)](https://github.com/descope/descope-java/actions/workflows/ci.yaml)\n\n# Descope SDK for Java\n\nThe Descope SDK for Java provides convenient access to the Descope user management and authentication API\nfor a backend written in Java. You can read more on the [Descope Website](https://descope.com).\n\n## Requirements\n\nThe SDK supports Java version 8 and above.\n\n## Installing the SDK\n\nUsing [maven](https://maven.apache.org) add the following dependency to your pom.xml:\n\n```xml\n\u003cdependencies\u003e\n    ...\n    \u003cdependency\u003e\n        \u003cartifactId\u003ejava-sdk\u003c/artifactId\u003e\n        \u003cgroupId\u003ecom.descope\u003c/groupId\u003e\n        \u003cversion\u003e[1.0.0,)\u003c/version\u003e\n    \u003c/dependency\u003e\n    ...\n\u003c/dependencies\u003e\n```\n\n## Setup\n\nA Descope `Project ID` is required to initialize the SDK. Find it on the\n[project page in the Descope Console](https://app.descope.com/settings/project).\n\n```java\nimport com.descope.client;\n\n// Initialized after setting the DESCOPE_PROJECT_ID env var (and optionally DESCOPE_MANAGEMENT_KEY)\n// We can also take these variables from .env file in the running directory\nvar descopeClient = new DescopeClient();\n\n// ** Or directly **\nvar descopeClient = new DescopeClient(Config.builder().projectId(\"Your-project\").build());\n```\n\n## Authentication Functions\n\nThese sections show how to use the SDK to perform various authentication/authorization functions:\n\n1. [OTP Authentication](#otp-authentication)\n2. [Magic Link](#magic-link)\n3. [Enchanted Link](#enchanted-link)\n4. [OAuth](#oauth)\n5. [SSO/SAML](#ssosaml)\n6. [TOTP Authentication](#totp-authentication)\n7. [Passwords](#passwords)\n8. [Session Validation](#session-validation)\n9. [Roles \u0026 Permission Validation](#roles--permission-validation)\n10. [Logging Out](#logging-out)\n\n## Management Functions\n\nThese sections show how to use the SDK to perform API management functions. Before using any of them, you will need to create a Management Key. The instructions for this can be found under [Setup](#setup-1).\n\n1. [Manage Tenants](#manage-tenants)\n2. [Manage Users](#manage-users)\n3. [Manage Access Keys](#manage-access-keys)\n4. [Manage SSO Setting](#manage-sso-setting)\n5. [Manage Permissions](#manage-permissions)\n6. [Manage Roles](#manage-roles)\n7. [Query SSO Groups](#query-sso-groups)\n8. [Manage Flows](#manage-flows)\n9. [Manage JWTs](#manage-jwts)\n10. [Audit](#audit)\n11. [Manage Project](#manage-project)\n\nIf you wish to run any of our code samples and play with them, check out our [Code Examples](#code-examples) section.\n\nIf you're developing unit tests, see how you can use our mocks package underneath the [Unit Testing and Data Mocks](#unit-testing-and-data-mocks) section.\n\nIf you're performing end-to-end testing, check out the [Utils for your end to end (e2e) tests and integration tests](#utils-for-your-end-to-end-e2e-tests-and-integration-tests) section. You will need to use the `descopeClient` object created under [Setup](#setup-1) guide.\n\nFor rate limiting information, please confer to the [API Rate Limits](#api-rate-limits) section.\n\n---\n\n### OTP Authentication\n\nSend a user a one-time password (OTP) using your preferred delivery method (_email / SMS / Voice call / WhatsApp_). An email address or phone number must be provided accordingly.\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```java\n// Every user must have a loginID. All other user information is optional\nString loginId = \"desmond@descope.com\"\nUser user = User.builder()\n    .name(\"Desmond Copeland\")\n    .phone(\"212-555-1234\")\n    .email(loginId)\n    .build();\nOTPService otps = descopeClient.getAuthenticationServices().getOTPService();\ntry {\n  String maskedAddress = otps.signUp(DeliveryMethod.EMAIL, loginId, user);\n} catch (DescopeException de) {\n  // Handle the error\n}\n```\n\nThe user will receive a code using the selected delivery method. Verify that code using:\n\n```java\n// Will throw DescopeException if there is an error with update\ntry {\n  AuthenticationInfo info = otps.verifyCode(DeliveryMethod.EMAIL, loginId, code);\n} catch (DescopeException de) {\n  // Handle the error\n}\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Magic Link\n\nSend a user a Magic Link using your preferred delivery method (_email / SMS / WhatsApp_).\nThe Magic Link will redirect the user to page where the its token needs to be verified.\nThis redirection can be configured in code, or globally in the [Descope Console](https://app.descope.com/settings/authentication/magiclink)\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```java\n// If configured globally, the redirect URI is optional. If provided however, it will be used\n// instead of any global configuration\n\n// Every user must have a loginID. All other user information is optional\nString loginId = \"desmond@descope.com\"\nUser user = User.builder()\n    .name(\"Desmond Copeland\")\n    .phone(\"212-555-1234\")\n    .email(loginId)\n    .build();\n\nMagicLinkService mls = descopeClient.getAuthenticationServices().getMagicLinkService();\n\ntry {\n    String uri = \"http://myapp.com/verify-magic-link\";\n    String maskedAddress = mls.signUp(DeliveryMethod.EMAIL, loginId, uri, user);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nTo verify a magic link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=\u003ctoken\u003e`):\n\n```java\ntry {\n    AuthenticationInfo info = mls.verify(token);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Enchanted Link\n\nUsing the Enchanted Link APIs enables users to sign in by clicking a link\ndelivered to their email address. The email will include 3 different links,\nand the user will have to click the right one, based on the 2-digit number that is\ndisplayed when initiating the authentication process.\n\nThis method is similar to [Magic Link](#magic-link) but differs in two major ways:\n\n- The user must choose the correct link out of the three, instead of having just one\n  single link.\n- This supports cross-device clicking, meaning the user can try to log in on one device,\n  like a computer, while clicking the link on another device, for instance a mobile phone.\n\nThe Enchanted Link will redirect the user to page where the its token needs to be verified.\nThis redirection can be configured in code per request, or set globally in the [Descope Console](https://app.descope.com/settings/authentication/enchantedlink).\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```java\n// If configured globally, the redirect URI is optional. If provided however, it will be used\n// instead of any global configuration.\n\nEnchantedLinkService els = descopeClient.getAuthenticationServices().getEnchantedLinkService();\nEnchantedLinkResponse res = null;\ntry {\n    String uri = \"http://myapp.com/verify-enchanted-link\";\n    res = els.signUp(loginId, uri, user);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nAfter sending the link, you must poll to receive a valid session using the `PendingRef` from\nthe previous step. A valid session will be returned only after the user clicks the right link.\n\n```java\n// Poll for a certain number of tries / time frame\nfor (int i = retriesCount; i \u003e 0; i--) {\n    try {\n        AuthenticationInfo info = els.getSession(res.getPendingRef());\n    } catch (DescopeException de) {\n        if (i \u003e 1) {\n            // Poll again after X seconds\n            TimeUnit.SECONDS.sleep(retryInterval);\n            continue;\n        }\n        else {\n            // Handle the error\n            break;\n        }\n    }\n}\n\n```\n\nTo verify an enchanted link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=\u003ctoken\u003e`). Once the token is verified, the session polling will receive a valid response.\n\n```java\n\ntry {\n    els.verify(token);\n} catch (DescopeException de) {\n    // Token is invalid, handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### OAuth\n\nUsers can authenticate using their social logins, using the OAuth protocol. Configure your OAuth settings on the [Descope console](https://app.descope.com/settings/authentication/social). To start a flow call:\n\n```java\n// Choose an oauth provider out of the supported providers\n// If configured globally, the return URL is optional. If provided however, it will be used\n// instead of any global configuration.\n// Redirect the user to the returned URL to start the OAuth redirect chain\nOAuthService oas = descopeClient.getAuthenticationServices().getOAuthService();\n\ntry {\n    String returnUrl = \"https://my-app.com/handle-oauth\";\n    oas.start(\"google\", returnUrl, loginOptions);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe user will authenticate with the authentication provider, and will be redirected back to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it to validate the user:\n\n```java\n\ntry {\n    AuthenticationInfo info = oas.exchangeToken(code);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### SSO/SAML\n\nUsers can authenticate to a specific tenant using SAML or Single Sign On. Configure your SSO/SAML settings on the [Descope console](https://app.descope.com/settings/authentication/sso). To start a flow call:\n\n```java\n// Choose which tenant to log into\n// Redirect the user to the returned URL to start the SSO/SAML redirect chain\nSAMLService ss = descopeClient.getAuthenticationServices().getSAMLService();\n\ntry {\n    String returnURL = \"https://my-app.com/handle-saml\";\n    String url = ss.start(\"my-tenant-ID\", returnURL, loginOptions);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe user will authenticate with the authentication provider configured for that tenant, and will be redirected back to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it to validate the user:\n\n```java\n// The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically.\n// Otherwise they're available via authInfo\ntry {\n    String url = ss.exchangeToken(code);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### TOTP Authentication\n\nThe user can authenticate using an authenticator app, such as Google Authenticator.\nSign up like you would using any other authentication method. The sign up response\nwill then contain a QR code `Image` that can be displayed to the user to scan using\ntheir mobile device camera app, or the user can enter the `Key` manually or click\non the link provided by the `ProvisioningURL`.\n\nExisting users can add TOTP using the `update` function.\n\n```java\n// Every user must have a loginID. All other user information is optional\nString loginId = \"desmond@descope.com\"\nUser user = User.builder()\n    .name(\"Desmond Copeland\")\n    .phone(\"212-555-1234\")\n    .email(loginId)\n    .build();\n\nTOTPService ts = descopeClient.getAuthenticationServices().getTOTPService();\n\ntry {\n    TOTPResponse resp = ts.signUp(loginId, user);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Use one of the provided options to have the user add their credentials to the authenticator\n// resp.getProvisioningURL()\n// resp.getImage()\n// resp.getKey()\n```\n\nThere are 3 different ways to allow the user to save their credentials in\ntheir authenticator app - either by clicking the provisioning URL, scanning the QR\nimage or inserting the key manually. After that, signing in is done using the code\nthe app produces.\n\n```java\n// The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically.\n// Otherwise they're available via authInfo\ntry {\n    AuthenticationInfo info = ts.signInCode(loginId, code, loginOptions);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\n### Passwords\n\nThe user can also authenticate with a password, though it's recommended to\nprefer passwordless authentication methods if possible. Sign up requires the\ncaller to provide a valid password that meets all the requirements configured\nfor the [password authentication method](https://app.descope.com/settings/authentication/password) in the Descope console.\n\n```java\n// Every user must have a loginID. All other user information is optional\nString loginId = \"desmond@descope.com\";\nUser user = User.builder()\n    .name(\"Desmond Copeland\")\n    .phone(\"212-555-1234\")\n    .email(loginId)\n    .build();\nString password = \"qYlvi65KaX\";\n\nPasswordService ps = descopeClient.getAuthenticationServices().getPasswordService();\n\ntry {\n    AuthenticationInfo info = ps.signUp(loginId, user, password);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe user can later sign in using the same loginID and password.\n\n```java\ntry {\n    AuthenticationInfo info = ps.signIn(loginId, password);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation)\n\nIn case the user needs to update their password, one of two methods are available: Resetting their password or replacing their password\n\n**Changing Passwords**\n\n_NOTE: SendPasswordReset will only work if the user has a validated email address. Otherwise password reset prompts cannot be sent._\n\nIn the [password authentication method](https://app.descope.com/settings/authentication/password) in the Descope console, it is possible to define which alternative authentication method can be used in order to authenticate the user, in order to reset and update their password.\n\n```java\n// Start the reset process by sending a password reset prompt. In this example we'll assume\n// that magic link is configured as the reset method. The optional redirect URL is used in the\n// same way as in regular magic link authentication.\nPasswordService ps = descopeClient.getAuthenticationServices().getPasswordService();\nString loginId = \"desmond@descope.com\";\nString redirectUrl = \"https://myapp.com/password-reset\";\ntry {\n    ps.sendPasswordReset(loginId, redirectUrl);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nThe magic link, in this case, must then be verified like any other magic link (see the [magic link section](#magic-link) for more details).\nHowever, after verifying the user, it is expected to allow them to provide a new password instead of the old one.\nSince the user is now authenticated, this is possible with the refresh token received from the verify:\n\n```java\ntry {\n    ps.updateUserPassword(loginId, newPassword, refreshToken);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n`UpdateUserPassword` can always be called when the user is authenticated and has a valid session.\n\nAlternatively, it is also possible to replace an existing active password with a new one.\n\n```java\n// Replaces the user's current password with a new one\ntry {\n    AuthenticationInfo info = ps.replaceUserPassword(loginId, oldPassword, newPassword);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Session Validation\n\nEvery secure request performed between your client and server needs to be validated.\n\nTokens can be validated directly:\n\n```java\n// Validate the session. Will return an error if expired\nAuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();\ntry {\n    Token t = as.validateSessionWithToken(sessionToken);\n} catch (DescopeException de) {\n    // Handle the unauthorized error\n}\n\n// If ValidateSessionWithRequest raises an exception, you will need to refresh the session using\ntry {\n    Token t = as.refreshSessionWithToken(refreshToken);\n} catch (DescopeException de) {\n    // Handle the unauthorized error\n}\n\n// Alternatively, you could combine the two and\n// have the session validated and automatically refreshed when expired\ntry {\n    Token t = as.validateAndRefreshSessionWithTokens(sessionToken, refreshToken);\n} catch (DescopeException de) {\n    // unauthorized error\n}\n\n```\n\nChoose the right session validation and refresh combination that suits your needs.\n\nRefreshed sessions return the same response as is returned when users first sign up / log in,\nMake sure to return the session token from the response to the client if tokens are validated directly.\n\nUsually, the tokens can be passed in and out via HTTP headers or via a cookie.\nThe implementation can defer according to your implementation. See our [examples](#code-examples) for a few examples.\n\nIf Roles \u0026 Permissions are used, validate them immediately after validating the session. See the [next section](#roles--permission-validation)\nfor more information.\n\n#### Session Validation Using Middleware\n\nAlternatively, you can validate the session using Spring Framework middleware. See example using [java-spring](https://github.com/descope/java-spring).\n\n### Roles \u0026 Permission Validation\n\nWhen using Roles \u0026 Permission, it's important to validate the user has the required\nauthorization immediately after making sure the session is valid. Taking the `sessionToken`\nreceived by the [session validation](#session-validation), call the following functions:\n\nFor multi-tenant uses:\n\n```java\n// You can validate specific permissions\nAuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();\ntry {\n    if (!as.validatePermissions(sessionToken, \"my-tenant-ID\", Arrays.asList(\"Permission to validate\"))) {\n        // Deny access\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Or validate roles directly\ntry {\n    if (!as.validateRoles(sessionToken, \"my-tenant-ID\", Arrays.asList(\"Role to validate\"))) {\n        // Deny access\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Or get the matched roles/permissions\nList\u003cString\u003e matchedPermissions = as.getMatchedPermissions(sessionToken, \"my-tenant-ID\", Arrays.asList(\"Permission1\", \"Permission2\"));\n\nList\u003cString\u003e matchedRoles = as.getMatchedRoles(sessionToken, \"my-tenant-ID\", Arrays.asList(\"Role1\", \"Role2\"));\n```\n\nWhen not using tenants use:\n\n```java\n// You can validate specific permissions\nAuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();\ntry {\n    if (!as.validatePermissions(sessionToken, Arrays.asList(\"Permission to validate\"))) {\n        // Deny access\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Or validate roles directly\ntry {\n    if (!as.validateRoles(sessionToken, Arrays.asList(\"Role to validate\"))) {\n        // Deny access\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Or get the matched roles/permissions\nList\u003cString\u003e matchedPermissions = as.getMatchedPermissions(sessionToken, Arrays.asList(\"Permission1\", \"Permission2\"));\n\nList\u003cString\u003e matchedRoles = as.getMatchedRoles(sessionToken, Arrays.asList(\"Role1\", \"Role2\"));\n```\n\n### Logging Out\n\nYou can log out a user from an active session by providing their `refreshToken` for that session.\nAfter calling this function, you must invalidate or remove any cookies you have created.\n\n```java\nAuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();\ntry {\n    as.logout(refreshToken);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nIt is also possible to sign the user out of all the devices they are currently signed-in with. Calling `logoutAll` will\ninvalidate all user's refresh tokens. After calling this function, you must invalidate or remove any cookies you have created.\n\n```java\ntry {\n    as.logoutAll(refreshToken);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n## Management Functions\n\nIt is very common for some form of management or automation to be required. These can be performed\nusing the management functions. Please note that these actions are more sensitive as they are administrative\nin nature. Please use responsibly.\n\n### Setup\n\nTo use the management API you'll need a `Management Key` along with your `Project ID`.\nCreate one in the [Descope Console](https://app.descope.com/settings/company/managementkeys).\n\n```java\nimport com.descope.client;\n\n// Initialized after setting the DESCOPE_PROJECT_ID env var (and optionally DESCOPE_MANAGEMENT_KEY)\nvar descopeClient = new DescopeClient();\n\n// ** Or directly **\nvar descopeClient = new DescopeClient(Config.builder()\n        .projectId(\"Your-project\")\n        .managementKey(\"management-key\")\n        .build());\n\n```\n\n### Manage Tenants\n\nYou can create, update, delete or load tenants:\n\n```java\nTenantService ts = descopeClient.getManagementServices().getTenantService();\n// The self provisioning domains or optional. If given they'll be used to associate\n// Users logging in to this tenant\ntry {\n    ts.create(\"My Tenant\", Arrays.asList(\"domain.com\"), new HashMap\u003cString, Object\u003e() {{\n                put(\"custom-attribute-1\", \"custom-value1\");\n                put(\"custom-attribute-2\", \"custom-value2\");\n            }});\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// You can optionally set your own ID when creating a tenant\ntry {\n    ts.createWithId(\"my-custom-id\", \"My Tenant\", Arrays.asList(\"domain.com\"), new HashMap\u003cString, Object\u003e() {{\n                put(\"custom-attribute-1\", \"custom-value1\");\n                put(\"custom-attribute-2\", \"custom-value2\");\n            }});\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Update will override all fields as is. Use carefully.\ntry {\n    ts.update(\"my-custom-id\", \"My Tenant\", Arrays.asList(\"domain.com\", \"another-domain.com\"), new HashMap\u003cString, Object\u003e() {{\n                put(\"custom-attribute-1\", \"custom-value1\");\n                put(\"custom-attribute-2\", \"custom-value2\");\n            }});\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Tenant deletion cannot be undone. Use carefully.\ntry {\n    ts.delete(\"my-custom-id\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load all tenants\ntry {\n    List\u003cTenant\u003e tenants = ts.loadAll();\n    for (Tenant t : tenants) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Search tenants\ntry {\n    List\u003cTenant\u003e tenants = ts.searchAll(TenantSearchRequest.builder()\n            .ids(Arrays.asList(\"my-custom-id\"))\n            .names(Arrays.asList(\"My Tenant\"))\n            .customAttributes(Map.of(\"custom-attribute-1\", \"custom-value1\"))\n            .selfProvisioningDomains(Arrays.asList(\"domain.com\", \"another-domain.com\")));\n    for (Tenant t : tenants) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n```\n\n### Manage Users\n\nYou can create, update, delete or load users, as well as search according to filters:\n\n```java\n// A user must have a loginID, other fields are optional.\n// Roles should be set directly if no tenants exist, otherwise set on a per-tenant basis.\nUserService us = descopeClient.getManagementServices().getUserService();\ntry {\n    us.create(\"desmond@descope.com\", UserRequest.builder()\n            .email(\"desmond@descope.com\")\n            .displayName(\"Desmond Copeland\")\n            .tenants(Arrays.asList(\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID1\")\n                    .roleNames(Arrays.asList(\"role-name1\"),\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID2\")))));\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Alternatively, a user can be created and invited via an email message.\n// You can configure the invite URL in the Descope console prior to using this function, or pass inviteUrl in the options parameter.\n// and that an email address is provided in the information.\ntry {\n    us.invite(\"desmond@descope.com\",\n\t\t\t\t\t\tUserRequest.builder()\n            .email(\"desmond@descope.com\")\n            .displayName(\"Desmond Copeland\")\n            .tenants(Arrays.asList(\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID1\")\n                    .roleNames(Arrays.asList(\"role-name1\"),\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID2\")))),\n\t\t\t\t\t\tInviteOptions.builder()\n\t\t\t\t\t\t.inviteUrl(\"https://my-app.com/invite\")\n\t\t\t\t\t);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Update will override all fields as is. Use carefully. Use patch instead if providing select fields.\ntry {\n    us.update(\"desmond@descope.com\", UserRequest.builder()\n            .email(\"desmond@descope.com\")\n            .displayName(\"Desmond Copeland\")\n            .tenants(Arrays.asList(\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID1\")\n                    .roleNames(Arrays.asList(\"role-name1\"),\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID2\"))))\n            .build());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Patch will override provided fields but will leave other fields untouched.\ntry {\n    us.patch(\"desmond@descope.com\", PatchUserRequest.builder()\n            .name(\"Desmond Copeland\")\n            .build());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// User deletion cannot be undone. Use carefully.\ntry {\n    us.delete(\"desmond@descope.com\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load specific user\ntry {\n    us.load(\"desmond@descope.com\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// If needed, users can be loaded using their ID as well\ntry {\n    us.loadByUserId(\"\u003cuser-id\u003e\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Search all users, optionally according to tenant and/or role filter\n// Results can be paginated using the limit and page parameters\ntry {\n    List\u003cAllUsersResponsibleDetails\u003e users = us.searchAll(UserRequest.builder()\n            .tenants(Arrays.asList(\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID1\"),\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID2\"))));\n    for (AllUsersResponsibleDetails u : users) {\n        // Do something\n    }\n}\n\n```\n\n#### Set or Expire User Password\n\nYou can set a new active password for a user, which they can then use to sign in. You can also set a temporary\npassword that the user will be forced to change on the next login.\n\n```java\nUserService us = descopeClient.getManagementServices().getUserService();\n\n// Set a temporary user's password\ntry {\n    us.setTemporaryPassword(\"my-custom-id\", \"some-password\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Set a user's password\ntry {\n    us.setActivePassword(\"my-custom-id\", \"some-password\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Or alternatively, expire a user password\ntry {\n    us.expirePassword(\"my-custom-id\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Later, if the user is signing in with an expired password, the returned error will be ErrPasswordExpired\nPasswordService ps = descopeClient.getAuthenticationServices().getPasswordService();\ntry {\n    AuthenticationInfo info = ps.signIn(\"my-custom-id\", \"some-password\");\n} catch (DescopeException de) {\n    // Handle the error\n    // Handle a case when the error is expired, the user should replace/reset the password\n}\n\n```\n\n### Manage Access Keys\n\nYou can create, update, delete or load access keys, as well as search according to filters:\n\n```java\n// Roles should be set directly if no tenants exist, otherwise set\n// on a per-tenant basis.\nAccessKeyService aks = descopeClient.getManagementServices().getAccessKeyService();\ntry {\n    // Create a new access key with a name, delay time, and tenant\n    AccessKeyResponse resp = aks.create(\"access-key-1\", 0,\n            Arrays.asList(\"Role names\"),\n            Arrays.asList(\n                new Tenant(\"tenant-ID1\",\n                    \"Key Tenant\",\n                    Arrays.asList(new AssociatedTenant(\"tenant-ID2\", Arrays.asList(\"Role names\"))))));\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load specific user\ntry {\n    AccessKeyResponse resp = aks.load(\"access-key-1\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Search all users, optionally according to tenant and/or role filter\ntry {\n    AccessKeyResponseList resp = aks.searchAll(Arrays.asList(\"Tenant IDs\"));\n    for (AccessKeyResponse r : aks.getKeys()) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Update will override all fields as is. Use carefully.\ntry {\n    AccessKeyResponse resp = aks.update(\"access-key-1\", \"updated-name\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Access keys can be deactivated to prevent usage. This can be undone using \"activate\".\ntry {\n    AccessKeyResponse resp = aks.deactivate(\"access-key-1\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Disabled access keys can be activated once again.\ntry {\n    AccessKeyResponse resp = aks.activate(\"access-key-1\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Access key deletion cannot be undone. Use carefully.\ntry {\n    aks.delete(\"access-key-1\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Manage SSO Setting\n\nYou can manage SSO settings and map SSO group roles and user attributes.\n\n```java\nSsoService ss = descopeClient.getManagementServices().getSsoService();\n// You can get SSO settings for a specific tenant ID\ntry {\n    SSOSettingsResponse resp = ss.loadSettings(\"tenant-id\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Configure SSO - SAML\nString tenantId = \"tenant-id\"; // Which tenant this configuration is for\nString idpUrl = \"https://idp.com\";\nString entityId = \"my-idp-entity-id\";\nString idpCert = \"\u003cyour-cert-here\u003e\";\nString idpMetadataUrl = \"https://idp.com/metadata\";\nString redirectUrl = \"https://my-app.com/handle-saml\"; // Global redirect URL for SSO/SAML\nList\u003cString\u003e domains = Arrays.asList(\"domain.com\"); // Users logging in from this domain will be logged in to this tenant\n\n// Map IDP groups to Descope roles, or map user attributes.\n// This function overrides any previous mapping (even when empty). Use carefully.\nList\u003cRoleMapping\u003e rm = Arrays.asList(new RoleMapping(Arrays.asList(\"Groups\"), \"Tenant Role\"));\nAttributeMapping am = new AttributeMapping(\"Tenant Name\", \"Tenant Email\", \"Tenant Phone Num\", \"Tenant Group\");\n\n\n// Using Manual Configuration\nSSOSAMLSettings manualSettings = new SSOSAMLSettings(idpUrl, entityId, idpCert, am, rm);\n\ntry {\n    ss.configureSAMLSettings(tenantId, manualSettings, domains);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Using metadata URL\nSSOSAMLSettingsByMetadata metadataSettings = new SSOSAMLSettingsByMetadata(idpMetadataUrl ,am, rm);\n\ntry {\n    ss.configureSAMLSettingsByMetadata(tenantId, metadataSettings, domains);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Configure SSO - OIDC\nString name = \"Provider\"; // Name of the provider\nString clientId = \"\u003coidc-client-id\u003e\"; // The client id set on the IdP\nString clientSecret = \"\u003coidc-client-secret\u003e\"; // The client secret on the IdP\nString redirectUrl = \"https://my-app.com/redirect\"; // Optional - a custom redirect url\nString authUrl = \"https://idp.com/auth\"; // The IdP's authentication endpoint\nString tokenUrl = \"https://idp.com/token\"; // The IdP's token endpoint\nString userDataUrl = \"https://idp.com/user\"; // The IdP's user endpoint\nList\u003cString\u003e scope = Arrays.asList(\"openid\", \"profile\"); // The scopes\nString grantType = \"implicit\"; // The grant type\nList\u003cString\u003e domains = Arrays.asList(\"domain.com\"); // Users logging in from this domain will be logged in to this tenant\n\n\nSSOOIDCSettings oidcSettings = new SSOOIDCSettings(name, clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userDataUrl, scope, grantType);\n\ntry {\n    ss.configureOIDCSettings(tenantId, oidcSettings, domains);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nNote: Certificates should have a similar structure to:\n\n```\n-----BEGIN CERTIFICATE-----\nCertifcate contents\n-----END CERTIFICATE-----\n```\n\n```java\n// To delete SSO settings, call the following method\ntry {\n    ss.deleteSettings(tenantId);\n} catch (DescopeException de) {\n    // Handle the error\n}\n```\n\n### Manage Permissions\n\nYou can create, update, delete or load permissions:\n\n```java\n// You can optionally set a description for a permission.\nPermissionService ps = descopeClient.getManagementServices().getPermissionService();\n\nString name = \"My Permission\";\nString description = \"Optional description to briefly explain what this permission allows.\";\n\ntry {\n    ps.create(name, description);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Update will override all fields as is. Use carefully.\nString newName = \"My Updated Permission\";\ndescription = \"A revised description\";\n\ntry {\n    ps.update(name, newName, description);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Permission deletion cannot be undone. Use carefully.\ntry {\n    ps.delete(newName);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load all permissions\ntry {\n    PermissionResponse resp = ps.loadAll();\n    for (Permission p : resp.getPermissions()) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Manage Roles\n\nYou can create, update, delete or load roles:\n\n```java\n// You can optionally set a description and associated permission for a roles.\nRolesService rs = descopeClient.getManagementServices().getRolesService();\n\nString name = \"My Role\";\nString description = \"Optional description to briefly explain what this role allows.\";\nList\u003cString\u003e permissionNames = Arrays.asList(\"My Updated Permission\");\n\n// In case roles are on tenant scope, use the overloaded functions that has the tenantId parameter\n\ntry {\n    rs.create(name, description, permissionNames);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Update will override all fields as is. Use carefully.\nString newName = \"My Updated Role\";\ndescription = \"A revised description\";\npermissionNames.add(\"Another Permission\");\n\ntry {\n    rs.update(name, newName, description, permissionNames);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Role deletion cannot be undone. Use carefully.\ntry {\n    rs.delete(newName);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load all roles\ntry {\n    RoleResponse resp = rs.loadAll();\n    for (Role r : resp.getRoles()) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Search roles\ntry {\n    RoleResponse resp = rs.search(RoleSearchOptions.builder().tenantIds(Arrays.asList(tid)).build());\n    for (Role r : resp.getRoles()) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Query SSO Groups\n\nYou can query SSO groups:\n\n```java\n// Load all groups for a given tenant id\nGroupService gs = descopeClient.getManagementServices().getGroupService();\ntry {\n    List\u003cGroup\u003e groups = gs.loadAllGroups(\"tenant-id\");\n    for (Group g : groups) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load all groups for the given user/login IDs (can be found in the user's JWT, used for sign-in)\ntry {\n    List\u003cGroup\u003e groups = gs.loadAllGroupsForMembers(\"tenant-id\",\n            Arrays.asList(\"user-id-1\", \"user-id-2\"),\n            Arrays.asList(\"login-id-1\", \"login-id-2\"));\n    for (Group g : groups) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Load all group's members by the given group id\ntry {\n    List\u003cGroup\u003e groups = gs.loadAllGroupMembers(\"tenant-id\", \"group-id\");\n    for (Group g : groups) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Manage Flows\n\nYou can list, import and export flows and screens, or the project theme:\n\n```java\nFlowService fs = descopeClient.getManagementServices().getFlowService();\n\n// List all your flows\ntry {\n    List\u003cFlow\u003e flows = fs.listFlows();\n    for (Flow f : flows) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Export the flow and it's matching screens based on the given id\ntry {\n    FlowResponse resp = fs.exportFlow(\"sign-up\");\n    Flow flow = resp.getFlow();\n    List\u003cScreen\u003e screens = resp.getScreens();\n    for (Screen s : screens) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Import the given flow and screens as the given id\ntry {\n    FlowResponse resp = fs.importFlow(\"sign-up\", flow, screens);\n    Flow flow = resp.getFlow();\n    List\u003cScreen\u003e screens = resp.getScreens();\n    for (Screen s : screens) {\n        // Do something\n    }\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Export the current theme of the project\ntry {\n    Theme t = fs.exportTheme();\n    System.out.println(t.getId());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Import the given theme to the project\ntry {\n    Theme theme = fs.importTheme(t);\n    System.out.println(theme.getId());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Manage JWTs\n\nYou can add custom claims to a valid JWT.\n\n```java\nJwtService jwts = descopeClient.getManagementServices().getJwtService();\ntry {\n    String res = jwts.updateJWTWithCustomClaims(\"original-jwt\",\n            new HashMap\u003cString, Object\u003e() {{\n                put(\"custom-key1\", \"custom-value1\");\n                put(\"custom-key2\", \"custom-value2\");\n            }}).getJwt();\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n### Audit\n\nYou can perform an audit search for either specific values or full-text across the fields. Audit search is limited to the last 30 days.\n\n```java\nAuditService as = descopeClient.getManagementServices().getAuditService();\n// Full text search on the last 10 days\ntry {\n    AuditSearchResponse resp = as.search(AuditSearchRequest.builder()\n            .from(Instant.now().minus(Duration.ofDays(10))));\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Search successful logins in the last 30 days\ntry {\n    AuditSearchResponse resp = as.search(AuditSearchRequest.builder()\n            .from(Instant.now().minus(Duration.ofDays(30)))\n            .actions(Arrays.asList(\"LoginSucceed\"))\n            .build());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\nYou can also create audit event with data\n\n```java\ntry {\n    as.createEvent(AuditCreateRequest.builder()\n            .userId(\"some-id\")\n            .actorId(\"some-actor-id\")\n            .type(AuditType.INFO)\n            .action(\"some-action-name\")\n            .build());\n} catch (DescopeException de) {\n    // Handle the error\n}\n```\n### Manage Project\n\nYou can change the project name, as well as to clone the current project to a new one.\n\n```java\nProjectService ps = descopeClient.getManagementServices().getProjectService();\n// Change the project name\ntry {\n    ps.updateName(\"New Project Name\");\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Clone the current project to a new one\n// Note that this action is supported only with a pro license or above.\ntry {\n    NewProjectResponse resp = ps.cloneProject(\"New Project Name\", ProjectTag.None);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nYou can manage your project's settings and configurations by exporting your project's environment.\n\n```java\n// Exports the current state of the project\ntry {\n    ExportProjectResponse resp = ps.exportProject();\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\nYou can also import previously exported data into the same project or a different one.\n\n```java\ntry {\n    // Load data from a previous export of this project or some other one\n    Map\u003cString, Object\u003e files = ...\n\n    // Update the current project's settings to mirror those in the exported data\n    ps.importProject(files);\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n## Code Examples\n\nYou can find various usage examples in the [examples folder](https://github.com/descope/java-sdk/blob/main/examples).\n\n### Setup\n\nTo run the examples, set your `Project ID` and `Management Key` by setting the `DESCOPE_PROJECT_ID` and `DESCOPE_MANAGEMENT_KEY` env vars or directly in the sample code.\nFind your Project ID in the [Descope console](https://app.descope.com/settings/project).\nFind your management key in the [Descope console](https://app.descope.com/settings/company/managementkeys).\n\n```bash\nexport DESCOPE_PROJECT_ID=\u003cProjectID\u003e\nexport DESCOPE_MANAGEMENT_KEY=\u003cManagementKey\u003e\n```\n\nAlternatively, you can create a `.env` file in the working folder with your project ID and management key.\n\n```\nDESCOPE_PROJECT_ID=\u003cProjectID\u003e\nDESCOPE_MANAGEMENT_KEY=\u003cManagementKey\u003e\n```\n\n### Run an example\n\n1. Make sure that the main Descope java-sdk is installed in the local Maven. Run this in the main folder.\n   ```bash\n   mvn install\n   ```\n2. Run this command in your project to build the example in the `examples/management-cli` folder.\n\n   ```bash\n   mvn package\n   ```\n\n3. Run a specific example\n\n   ```bash\n   # CLI example\n   java -jar target/management-cli-1.0.jar command-name -option1 -option2\n   # For example to display 10 users:\n   java -jar target/management-cli-1.0.jar user-search-all -l 10\n   # Help is available on all commands and within the command itself:\n   java -jar target/management-cli-1.0.jar -h\n   java -jar target/management-cli-1.0.jar user-search-all -h\n   ```\n\n### Using Visual Studio Code\n\nTo run Run and Debug using Visual Studio Code open the examples folder and run the ManagementCLI class\n\n## Unit Testing and Data Mocks\n\nJava provides a very simple way to mock services and objects using the Mockito package.\nHere is a simple example of how you can mock a magic link verify response.\n\n```java\nUser user = new User(\"someUserName\", MOCK_EMAIL, \"+1-555-555-5555\");\nApiProxy apiProxy = mock(ApiProxy.class); // Mock the proxy that actually sends the HTTP requests\nvar maskedEmailRes = new MaskedEmailRes(MOCK_MASKED_EMAIL); // Define expected response\ndoReturn(maskedEmailRes).when(apiProxy).post(any(), any(), any()); // When post is called, return mock response\ntry (MockedStatic\u003cApiProxyBuilder\u003e mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { // Mock proxy builder\n    mockedApiProxyBuilder.when(() -\u003e ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); // to return our mock proxy\n    String signUp = magicLinkService.signUp(DeliveryMethod.EMAIL, MOCK_EMAIL, MOCK_DOMAIN, user); // call the service as you usually would\n}\n\n// Now mock the verify\nApiProxy apiProxy = mock(ApiProxy.class);\ndoReturn(MOCK_JWT_RESPONSE).when(apiProxy).post(any(), any(), any()); // Return the mock JWT response\ndoReturn(new SigningKeysResponse(Arrays.asList(MOCK_SIGNING_KEY))).when(apiProxy).get(any(), eq(SigningKeysResponse.class)); // Return mock key\nvar provider = mock(Provider.class);\nwhen(provider.getProvidedKey()).thenReturn(mock(Key.class));\n\nAuthenticationInfo authenticationInfo;\ntry (MockedStatic\u003cApiProxyBuilder\u003e mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {\n    mockedApiProxyBuilder.when(() -\u003e ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); // Return proxy when building\n    try (MockedStatic\u003cJwtUtils\u003e mockedJwtUtils = mockStatic(JwtUtils.class)) {\n        mockedJwtUtils.when(() -\u003e JwtUtils.getToken(anyString(), any())).thenReturn(MOCK_TOKEN); // Return mock token instead of parsing\n        authenticationInfo = magicLinkService.verify(\"SomeToken\");\n    }\n}\n\n```\n\n### Utils for your end to end (e2e) tests and integration tests\n\nTo ease your e2e tests, we exposed dedicated management methods.\nThat way, you don't need to use 3rd party messaging services in order to receive sign-in/up Email, SMS, Voice call or WhatsApp,\nand avoid the need of parsing the code and token from them.\n\n```java\n// User for test can be created, this user will be able to generate code/link without\n// the need of 3rd party messaging services.\n// Test user must have a loginID, other fields are optional.\n// Roles should be set directly if no tenants exist, otherwise set\n// on a per-tenant basis.\nUserService us = descopeClient.getManagementServices().getUserService();\ntry {\n    UserResponseDetails resp = us.createTestUser(\"desmond@descope.com\", UserRequest.builder()\n            .email(\"desmond@descope.com\")\n            .displayName(\"Desmond Copeland\")\n            .tenants(Arrays.asList(\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID1\")\n                    .roleNames(Arrays.asList(\"role-name1\"),\n                AssociatedTenant.builder()\n                    .tenantId(\"tenant-ID2\")))));\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Now test user got created, and this user will be available until you delete it,\n// you can use any management operation for test user CRUD.\n// You can also delete all test users.\ntry {\n    us.deleteAllTestUsers();\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// OTP code can be generated for test user, for example:\ntry {\n    OTPTestUserResponse res = us.generateOtpForTestUser(\"desmond@descope.com\", DeliveryMethod.EMAIL);\n    // Use res.getCode() for verify and establishing a session\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Same as OTP, magic link can be generated for test user, for example:\ntry {\n    MagicLinkTestUserResponse res = us.generateMagicLinkForTestUser(\"desmond@descope.com\", \"\", DeliveryMethod.EMAIL);\n    // Use res.getLink() to get the generated link. To get the actual token, use:\n    // var params = UriUtils.splitQuery(\"https://example.com\" + res.getLink());\n    // var authInfo = magicLinkService.verify(params.get(\"t\").get(0));\n\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Enchanted link can be generated for test user, for example:\ntry {\n    EnchantedLinkTestUserResponse res = us.generateEnchantedLinkForTestUser(\"desmond@descope.com\", \"\");\n    // Use res.getLink() to get the generated link. To get the actual token, use:\n    // var params = UriUtils.splitQuery(\"https://example.com\" + res.getLink());\n    // enchantedLinkService.verify(params.get(\"t\").get(0));\n    // var authInfo = enchantedLinkService.getSession(res.getPendingRef());\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n// Note 1: The generate code/link methods, work only for test users, will not work for regular users.\n// Note 2: In case of testing sign-in / sign-up methods with test users, need to make sure to generate the code prior calling the sign-in / sign-up methods\n\n// Embedded links can be created to directly receive a verifiable token without sending it.\n// This token can then be verified using the magic link 'verify' function, either directly or through a flow.\nUserService us = descopeClient.getManagementServices().getUserService();\nMagicLinkService mls = descopeClient.getAuthenticationServices().getMagicLinkService();\ntry {\n    String token - us.generateEmbeddedLink(\"desmond@descope.com\", null /*custom claims if any */);\n    var authInfo = mls.verify(token);\n} catch (DescopeException de) {\n    // Handle the error\n}\ntoken, err := descopeClient.Management.User().GenerateEmbeddedLink(\"desmond@descope.com\", map[string]any{\"key1\":\"value1\"})\n```\n\n# API Rate Limits\n\nHandle API rate limits by catching the RateLimitExceededException.\nThe exception includes the number of seconds until the next valid API call can take place.\n\n```java\nMagicLinkService mls = descopeClient.getAuthenticationServices().getMagicLinkService();\ntry {\n    mls.signUpOrIn(DeliveryMethod.EMAIL, \"desmond@descope.com\", \"http://myapp.com/verify-magic-link\");\n} catch (RateLimitExceededException re) {\n    // Use re.getRetryAfterSeconds() to determine time until retry\n} catch (DescopeException de) {\n    // Handle the error\n}\n\n```\n\n## Learn More\n\nTo learn more please see the [Descope Documentation and API reference page](https://docs.descope.com/).\n\n## Contact Us\n\nIf you need help you can email [Descope Support](mailto:support@descope.com)\n\n## License\n\nThe Descope SDK for Java is licensed for use under the terms and conditions of the [MIT license Agreement](https://github.com/descope/descope-java/blob/main/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdescope%2Fdescope-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdescope%2Fdescope-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdescope%2Fdescope-java/lists"}