descope


Namedescope JSON
Version 1.7.0 PyPI version JSON
download
home_pagehttps://descope.com/
SummaryDescope Python SDK
upload_time2024-11-25 14:11:37
maintainerNone
docs_urlNone
authorDescope
requires_python<4.0,>=3.8.1
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Descope SDK for Python

The Descope SDK for python provides convenient access to the Descope user management and authentication API
for a backend written in python. You can read more on the [Descope Website](https://descope.com).

## Requirements

The SDK supports Python 3.8.1 and above.

## Installing the SDK

Install the package with:

```bash
pip install descope
```

#### If you would like to use the Flask decorators, make sure to install the Flask extras:

```bash
pip install descope[Flask]
```

## Setup

A Descope `Project ID` is required to initialize the SDK. Find it on the
[project page in the Descope Console](https://app.descope.com/settings/project).

```python
from descope import DescopeClient

# Initialized after setting the DESCOPE_PROJECT_ID env var
descope_client = DescopeClient()

# ** Or directly **
descope_client = DescopeClient(project_id="<Project ID>")
```

## Authentication Functions

These sections show how to use the SDK to perform various authentication/authorization functions:

1. [OTP Authentication](#otp-authentication)
2. [Magic Link](#magic-link)
3. [Enchanted Link](#enchanted-link)
4. [OAuth](#oauth)
5. [SSO (SAML / OIDC)](#sso-saml--oidc)
6. [TOTP Authentication](#totp-authentication)
7. [Passwords](#passwords)
8. [Session Validation](#session-validation)
9. [Roles & Permission Validation](#roles--permission-validation)
10. [Tenant selection](#tenant-selection)
11. [Logging Out](#logging-out)
12. [History](#history)
13. [My Tenants](#my-tenants)

## API Managment Function

These sections show how to use the SDK to perform permission and user management functions. You will need to create an instance of `DescopeClient` by following the [Setup](#setup-1) guide, before you can use any of these functions:

1. [Manage Tenants](#manage-tenants)
2. [Manage Users](#manage-users)
3. [Manage Access Keys](#manage-access-keys)
4. [Manage SSO Setting](#manage-sso-setting)
5. [Manage Permissions](#manage-permissions)
6. [Manage Roles](#manage-roles)
7. [Query SSO Groups](#query-sso-groups)
8. [Manage Flows](#manage-flows-and-theme)
9. [Manage JWTs](#manage-jwts)
10. [Impersonate](#impersonate)
11. [Embedded links](#embedded-links)
12. [Audit](#audit)
13. [Manage ReBAC Authz](#manage-rebac-authz)
14. [Manage Project](#manage-project)
15. [Manage SSO Applications](#manage-sso-applications)

If you wish to run any of our code samples and play with them, check out our [Code Examples](#code-examples) section.

If 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.

For rate limiting information, please confer to the [API Rate Limits](#api-rate-limits) section.

### OTP Authentication

Send 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.

The user can either `sign up`, `sign in` or `sign up or in`

```python
from descope import DeliveryMethod

# Every user must have a login ID. All other user information is optional
email = "desmond@descope.com"
user = {"name": "Desmond Copeland", "phone": "212-555-1234", "email": email}
masked_address = descope_client.otp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)
```

The user will receive a code using the selected delivery method. Verify that code using:

```python
jwt_response = descope_client.otp.verify_code(
    method=DeliveryMethod.EMAIL, login_id=email, code=value
)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

### Magic Link

Send a user a Magic Link using your preferred delivery method (_email / SMS / Voice call / WhatsApp_).
The Magic Link will redirect the user to page where the its token needs to be verified.
This redirection can be configured in code, or generally in the [Descope Console](https://app.descope.com/settings/authentication/magiclink)

The user can either `sign up`, `sign in` or `sign up or in`

```python
from descope import DeliveryMethod

masked_address = descope_client.magiclink.sign_up_or_in(
    method=DeliveryMethod.EMAIL,
    login_id="desmond@descope.com",
    uri="http://myapp.com/verify-magic-link", # Set redirect URI here or via console
)
```

To verify a magic link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=<token>`):

```python
jwt_response = descope_client.magiclink.verify(token=token)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

### Enchanted Link

Using the Enchanted Link APIs enables users to sign in by clicking a link
delivered to their email address. The email will include 3 different links,
and the user will have to click the right one, based on the 2-digit number that is
displayed when initiating the authentication process.

This method is similar to [Magic Link](#magic-link) but differs in two major ways:

- The user must choose the correct link out of the three, instead of having just one
  single link.
- This supports cross-device clicking, meaning the user can try to log in on one device,
  like a computer, while clicking the link on another device, for instance a mobile phone.

The Enchanted Link will redirect the user to page where the its token needs to be verified.
This redirection can be configured in code per request, or set globally in the [Descope Console](https://app.descope.com/settings/authentication/enchantedlink).

The user can either `sign up`, `sign in` or `sign up or in`

```python
resp = descope_client.enchantedlink.sign_up_or_in(
    login_id=email,
    uri="http://myapp.com/verify-enchanted-link", # Set redirect URI here or via console
)
link_identifier = resp["linkId"] # Show the user which link they should press in their email
pending_ref = resp["pendingRef"] # Used to poll for a valid session
masked_email = resp["maskedEmail"] # The email that the message was sent to in a masked format
```

After sending the link, you must poll to receive a valid session using the `pending_ref` from
the previous step. A valid session will be returned only after the user clicks the right link.

```python
i = 0
while not done and i < max_tries:
    try:
        i = i + 1
        sleep(4)
        jwt_response = descope_client.enchantedlink.get_session(pending_ref)
        done = True
    except AuthException as e: # Poll while still receiving 401 Unauthorized
        if e.status_code != 401: # Other failures means something's wrong, abort
            logging.info(f"Failed pending session, err: {e}")
            done = True

if jwt_response:
    session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
    refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

To verify an enchanted link, your redirect page must call the validation function on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=<token>`). Once the token is verified, the session polling will receive a valid `jwt_response`.

```python
try:
    descope_client.enchantedlink.verify(token=token)
    # Token is valid
except AuthException as e:
    # Token is invalid
```

The 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)

### OAuth

Users 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:

```python

descope_client.oauth.start(
    provider="google", # Choose an oauth provider out of the supported providers
    return_url="https://my-app.com/handle-oauth", # Can be configured in the console instead of here
)
```

The 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:

```python
jwt_response = descope_client.oauth.exchange_token(code)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

### SSO (SAML / OIDC)

Users can authenticate to a specific tenant using SAML/OIDC based on the tenant settings. Configure your SAML/OIDC tenant settings on the [Descope console](https://app.descope.com/tenants). To start a flow call:

```python
descope_client.sso.start(
    tenant="my-tenant-ID", # Choose which tenant to log into
    return_url="https://my-app.com/handle-sso", # Can be configured in the console instead of here
)
```

The 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:

```python
jwt_response = descope_client.sso.exchange_token(code)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

Note: the descope_client.saml.start(..) and descope_client.saml.exchange_token(..) functions are DEPRECATED, use the above sso functions instead

### TOTP Authentication

The user can authenticate using an authenticator app, such as Google Authenticator.
Sign up like you would using any other authentication method. The sign up response
will then contain a QR code `image` that can be displayed to the user to scan using
their mobile device camera app, or the user can enter the `key` manually or click
on the link provided by the `provisioning_url`.

Existing users can add TOTP using the `update` function.

```python
from descope import DeliveryMethod

# Every user must have a login ID. All other user information is optional
email = "desmond@descope.com"
user = {"name": "Desmond Copeland", "phone": "212-555-1234", "email": email}
totp_response = descope_client.totp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)

# Use one of the provided options to have the user add their credentials to the authenticator
provisioning_url = totp_response["provisioningURL"]
image = totp_response["image"]
key = totp_response["key"]
```

There are 3 different ways to allow the user to save their credentials in
their authenticator app - either by clicking the provisioning URL, scanning the QR
image or inserting the key manually. After that, signing in is done using the code
the app produces.

```python
jwt_response = descope_client.totp.sign_in_code(
    login_id=email,
    code=code, # Code from authenticator app
)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

### Passwords

The user can also authenticate with a password, though it's recommended to
prefer passwordless authentication methods if possible. Sign up requires the
caller to provide a valid password that meets all the requirements configured
for the [password authentication method](https://app.descope.com/settings/authentication/password) in the Descope console.

```python
# Every user must have a login_id and a password. All other user information is optional
login_id = "desmond@descope.com"
password = "qYlvi65KaX"
user = {
    "name": "Desmond Copeland",
    "email": login_id,
}
jwt_response = descope_client.password.sign_up(
    login_id=login_id,
    password=password,
    user=user,
)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The user can later sign in using the same login_id and password.

```python
jwt_response = descope_client.password.sign_in(login_id, password)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

The 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)

In case the user needs to update their password, one of two methods are available: Resetting their password or replacing their password

**Changing Passwords**

_NOTE: send_reset will only work if the user has a validated email address. Otherwise password reset prompts cannot be sent._

In 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.

```python
# Start the reset process by sending a password reset prompt. In this example we'll assume
# that magic link is configured as the reset method. The optional redirect URL is used in the
# same way as in regular magic link authentication.
login_id = "desmond@descope.com"
redirect_url = "https://myapp.com/password-reset"
descope_client.password.send_reset(login_id, redirect_url)
```

The magic link, in this case, must then be verified like any other magic link (see the [magic link section](#magic-link) for more details). However, after verifying the user, it is expected
to allow them to provide a new password instead of the old one. Since the user is now authenticated, this is possible via:

```python
# The refresh token is required to make sure the user is authenticated.
err = descope_client.password.update(login_id, new_password, token)
```

`update` can always be called when the user is authenticated and has a valid session.

Alternatively, it is also possible to replace an existing active password with a new one.

```python
# Replaces the user's current password with a new one
jwt_response = descope_client.password.replace(login_id, old_password, new_password)
session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt")
refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt")
```

### Session Validation

Every secure request performed between your client and server needs to be validated. The client sends
the session and refresh tokens with every request, and they are validated using one of the following:

```python
# Validate the session. Will raise if expired
try:
    jwt_response = descope_client.validate_session(session_token)
except AuthException:
    # Session expired

# If validate_session raises an exception, you will need to refresh the session using
jwt_response = descope_client.refresh_session(refresh_token)

# Alternatively, you could combine the two and
# have the session validated and automatically refreshed when expired
jwt_response = descope_client.validate_and_refresh_session(session_token, refresh_token)
```

Choose the right session validation and refresh combination that suits your needs.

Note: all those validation apis can receive an optional 'audience' parameter that should be provided when using jwt that has the 'aud' claim.

Refreshed sessions return the same response as is returned when users first sign up / log in,
containing the session and refresh tokens, as well as all of the JWT claims.
Make sure to return the tokens from the response to the client, or updated the cookie if you're using it.

Usually, the tokens can be passed in and out via HTTP headers or via a cookie.
The implementation can defer according to your framework of choice. See our [samples](#code-samples) for a few examples.

If Roles & Permissions are used, validate them immediately after validating the session. See the [next section](#roles--permission-validation)
for more information.

### Roles & Permission Validation

When using Roles & Permission, it's important to validate the user has the required
authorization immediately after making sure the session is valid. Taking the `jwt_response`
received by the [session validation](#session-validation), call the following functions:

For multi-tenant uses:

```python
# You can validate specific permissions
valid_permissions = descope_client.validate_tenant_permissions(
    jwt_response, "my-tenant-ID", ["Permission to validate"]
)
if not valid_permissions:
    # Deny access

# Or validate roles directly
valid_roles = descope_client.validate_tenant_roles(
    jwt_response, "my-tenant-ID", ["Role to validate"]
)
if not valid_roles:
    # Deny access

# Or get the matched roles/permissions
matched_tenant_roles = descope_client.get_matched_tenant_roles(
		jwt_response, "my-tenant-ID", ["role-name1", "role-name2"]
)

matched_tenant_permissions = descope_client.get_matched_tenant_permissions(
		jwt_response, "my-tenant-ID", ["permission-name1", "permission-name2"]
)
```

When not using tenants use:

```python
# You can validate specific permissions
valid_permissions = descope_client.validate_permissions(
    jwt_response, ["Permission to validate"]
)
if not valid_permissions:
    # Deny access

# Or validate roles directly
valid_roles = descope_client.validate_roles(
    jwt_response, ["Role to validate"]
)
if not valid_roles:
    # Deny access

# Or get the matched roles/permissions
matched_roles = descope_client.get_matched_roles(
		jwt_response, ["role-name1", "role-name2"]
)

matched_permissions = descope_client.get_matched_permissions(
		jwt_response, ["permission-name1", "permission-name2"]
)
```

### Tenant selection

For a user that has permissions to multiple tenants, you can set a specific tenant as the current selected one
This will add an extra attribute to the refresh JWT and the session JWT with the selected tenant ID

```python
tenant_id_ = "t1"
jwt_response = descope_client.select_tenant(tenant_id, refresh_token)
```

### Logging Out

You can log out a user from an active session by providing their `refresh_token` for that session.
After calling this function, you must invalidate or remove any cookies you have created.

```python
descope_client.logout(refresh_token)
```

It is also possible to sign the user out of all the devices they are currently signed-in with. Calling `logout_all` will
invalidate all user's refresh tokens. After calling this function, you must invalidate or remove any cookies you have created.

```python
descope_client.logout_all(refresh_token)
```

### History

You can get the current session user history.
The request requires a valid refresh token.

```python
users_history_resp = descope_client.history(refresh_token)
for user_history in users_history_resp:
    # Do something
```

### My Tenants

You can get the current session user tenants.
The request requires a valid refresh token.
And either a boolean to receive the current selected tenant
Or a list of tenant IDs that this user is part of

```python
tenants_resp = descope_client.my_tenants(refresh_token, False, ["tenant_id"])
for tenant in tenants_resp.tenants:
    # Do something
```

## Management API

It is very common for some form of management or automation to be required. These can be performed
using the management API. Please note that these actions are more sensitive as they are administrative
in nature. Please use responsibly.

### Setup

To use the management API you'll need a `Management Key` along with your `Project ID`.
Create one in the [Descope Console](https://app.descope.com/settings/company/managementkeys).

```python
from descope import DescopeClient

# Initialized after setting the DESCOPE_PROJECT_ID and the DESCOPE_MANAGEMENT_KEY env vars
descope_client = DescopeClient()

# ** Or directly **
descope_client = DescopeClient(project_id="<Project ID>", management_key="<Management Key>")
```

### Manage Tenants

You can create, update, delete or load tenants:

```Python
# You can optionally set your own ID when creating a tenant
descope_client.mgmt.tenant.create(
    name="My First Tenant",
    id="my-custom-id", # This is optional.
    self_provisioning_domains=["domain.com"],
    custom_attributes={"attribute-name": "value"},
)

# Update will override all fields as is. Use carefully.
descope_client.mgmt.tenant.update(
    id="my-custom-id",
    name="My First Tenant",
    self_provisioning_domains=["domain.com", "another-domain.com"],
    custom_attributes={"attribute-name": "value"},
)

# Tenant deletion cannot be undone. Use carefully.
# Pass true to cascade value, in case you want to delete all users/keys associated only with this tenant
descope_client.mgmt.tenant.delete(id="my-custom-id", cascade=False)

# Load tenant by id
tenant_resp = descope_client.mgmt.tenant.load("my-custom-id")

# Load all tenants
tenants_resp = descope_client.mgmt.tenant.load_all()
tenants = tenants_resp["tenants"]
    for tenant in tenants:
        # Do something

# search all tenants
tenants_resp = descope_client.mgmt.tenant.search_all(ids=["id1"], names=["name1"], custom_attributes={"k1":"v1"}, self_provisioning_domains=["spd1"])
tenants = tenants_resp["tenants"]
    for tenant in tenants:
        # Do something
```

### Manage Users

You can create, update, patch, delete or load users, as well as setting new password, expire password and search according to filters:

```Python
# A user must have a login ID, other fields are optional.
# Roles should be set directly if no tenants exist, otherwise set
# on a per-tenant basis.
descope_client.mgmt.user.create(
    login_id="desmond@descope.com",
    email="desmond@descope.com",
    display_name="Desmond Copeland",
    user_tenants=[
        AssociatedTenant("my-tenant-id", ["role-name1"]),
    ],
	sso_app_ids=["appId1"],
)

# Alternatively, a user can be created and invited via an email message.
# Make sure to configure the invite URL in the Descope console prior to using this function,
# and that an email address is provided in the information.
descope_client.mgmt.user.invite(
    login_id="desmond@descope.com",
    email="desmond@descope.com",
    display_name="Desmond Copeland",
    user_tenants=[
        AssociatedTenant("my-tenant-id", ["role-name1"]),
    ],
	sso_app_ids=["appId1"],
)

# Batch invite
descope_client.mgmt.user.invite_batch(
    users=[
        UserObj(
            login_id="desmond@descope.com",
            email="desmond@descope.com",
            display_name="Desmond Copeland",
            user_tenants=[
                AssociatedTenant("my-tenant-id", ["role-name1"]),
            ],
            custom_attributes={"ak": "av"},
			sso_app_ids=["appId1"],
        )
    ],
    invite_url="invite.me",
    send_mail=True,
    send_sms=True,
)

# Update will override all fields as is. Use carefully.
descope_client.mgmt.user.update(
    login_id="desmond@descope.com",
    email="desmond@descope.com",
    display_name="Desmond Copeland",
    user_tenants=[
        AssociatedTenant("my-tenant-id", ["role-name1", "role-name2"]),
    ],
	sso_app_ids=["appId1"],
)

# Patch will override only the set fields in the user
descope_client.mgmt.user.patch(
    login_id="desmond@descope.com",
    email="desmond@descope.com",
    display_name="Desmond Copeland",
)

# Update explicit data for a user rather than overriding all fields
descope_client.mgmt.user.update_login_id(
    login_id="desmond@descope.com",
    new_login_id="bane@descope.com"
)
descope_client.mgmt.user.update_phone(
    login_id="desmond@descope.com",
    phone="+18005551234",
    verified=True,
)
descope_client.mgmt.user.remove_tenant_roles(
    login_id="desmond@descope.com",
    tenant_id="my-tenant-id",
    role_names=["role-name1"],
)

# Set SSO applications association to a user.
user = descope_client.mgmt.user.set_sso_apps(
	login_id="desmond@descope.com",
	sso_app_ids=["appId1", "appId2"]
)

# Add SSO applications association to a user.
user = descope_client.mgmt.user.add_sso_apps(
	login_id="desmond@descope.com",
	sso_app_ids=["appId1", "appId2"]
)

# Remove SSO applications association from a user.
user = descope_client.mgmt.user.remove_sso_apps(
	login_id="desmond@descope.com",
	sso_app_ids=["appId1", "appId2"]
)

# User deletion cannot be undone. Use carefully.
descope_client.mgmt.user.delete("desmond@descope.com")

# Load specific user
user_resp = descope_client.mgmt.user.load("desmond@descope.com")
user = user_resp["user"]

# If needed, users can be loaded using the user ID as well
user_resp = descope_client.mgmt.user.load_by_user_id("<user-id>")
user = user_resp["user"]

# Logout user from all devices by login ID
descope_client.mgmt.user.logout_user("<login-id>")

# Logout user from all devices by user ID
descope_client.mgmt.user.logout_user_by_user_id("<user-id>")

# Search all users, optionally according to tenant and/or role filter
# results can be paginated using the limit and page parameters
users_resp = descope_client.mgmt.user.search_all(tenant_ids=["my-tenant-id"])
users = users_resp["users"]
    for user in users:
        # Do something

# Get users' authentication history
users_history_resp = descope_client.mgmt.user.history(["user-id-1", "user-id-2"])
    for user_history in users_history_resp:
        # Do something
```

#### Set or Expire User Password

You can set a new active password for a user that they can sign in with.
You can also set a temporary password that the user will be forced to change on the next login.
For a user that already has an active password, you can expire their current password, effectively requiring them to change it on the next login.

```Python

# Set a user's temporary password
descope_client.mgmt.user.set_temporary_password('<login-id>', '<some-password>');

# Set a user's password
descope_client.mgmt.user.set_active_password('<login-id>', '<some-password>');

# Or alternatively, expire a user password
descope_client.mgmt.user.expirePassword('<login-id>');
```

### Manage Access Keys

You can create, update, delete or load access keys, as well as search according to filters:

```Python
# An access key must have a name and expiration, other fields are optional.
# Roles should be set directly if no tenants exist, otherwise set
# on a per-tenant basis.
# If user_id is supplied, then authorization would be ignored, and access key would be bound to the users authorization.
# If description is supplied, then the access key will hold a descriptive text.
# If permitted_ips is supplied, then the access key can only be used from that list of IP addresses or CIDR ranges
create_resp = descope_client.mgmt.access_key.create(
    name="name",
    expire_time=1677844931,
    key_tenants=[
        AssociatedTenant("my-tenant-id", ["role-name1"]),
    ],
    description="this is my access key",
    permitted_ips=['10.0.0.1', '192.168.1.0/24'],
)
key = create_resp["key"]
cleartext = create_resp["cleartext"] # make sure to save the returned cleartext securely. It will not be returned again.

# Load a specific access key
access_key_resp = descope_client.mgmt.access_key.load("key-id")
access_key = access_key_resp["key"]

# Search all access keys, optionally according to a tenant filter
keys_resp = descope_client.mgmt.access_key.search_all_access_keys(tenant_ids=["my-tenant-id"])
keys = keys_resp["keys"]
    for key in keys:
        # Do something

# Update will override all fields as is. Use carefully.
descope_client.mgmt.access_key.update(
    id="key-id",
    name="new name",
)

# Access keys can be deactivated to prevent usage. This can be undone using "activate".
descope_client.mgmt.access_key.deactivate("key-id")

# Disabled access keys can be activated once again.
descope_client.mgmt.access_key.activate("key-id")

# Access key deletion cannot be undone. Use carefully.
descope_client.mgmt.access_key.delete("key-id")
```

Exchange the access key and provide optional access key login options:

```python
loc = AccessKeyLoginOptions(custom_claims={"k1": "v1"})
jwt_response = descope_client.exchange_access_key(
  access_key="accessKey", login_options=loc
)
```

### Manage SSO Setting

You can manage SSO settings and map SSO group roles and user attributes.

```Python
# You can load all tenant SSO settings
sso_settings_res = descope_client.mgmt.sso.load_settings("tenant-id")

# import based on your configuration needs:
from descope import (
    SSOOIDCSettings,
    OIDCAttributeMapping,
    SSOSAMLSettings,
    AttributeMapping,
    RoleMapping,
    SSOSAMLSettingsByMetadata
)

# You can Configure SSO SAML settings for a tenant manually.
settings = SSOSAMLSettings(
	idp_url="https://dummy.com/saml",
	idp_entity_id="entity1234",
	idp_cert="my certificate",
	attribute_mapping=AttributeMapping(
		name="name",
		given_name="givenName",
		middle_name="middleName",
		family_name="familyName",
		picture="picture",
		email="email",
		phone_number="phoneNumber",
		group="groups"
	),
	role_mappings=[RoleMapping(groups=["grp1"], role="rl1")],
)
descope_client.mgmt.sso.configure_saml_settings(
	tenant_id, # Which tenant this configuration is for
	settings, # The SAML settings
	redirect_url="https://your.domain.com", # Global redirection after successful authentication
    domains=["tenant-users.com"] # Users authentication with these domains will be logged in to this tenant
)

# You can Configure SSO SAML settings for a tenant by fetching them from an IDP metadata URL.
settings = SSOSAMLSettingsByMetadata(
	idp_metadata_url="https://dummy.com/metadata",
	attribute_mapping=AttributeMapping(
		name="myName",
		given_name="givenName",
		middle_name="middleName",
		family_name="familyName",
		picture="picture",
		email="email",
		phone_number="phoneNumber",
		group="groups"
	),
	role_mappings=[RoleMapping(groups=["grp1"], role="rl1")],
)
descope_client.mgmt.sso.configure_saml_settings_by_metadata(
	tenant_id, # Which tenant this configuration is for
	settings,  # The SAML settings
	redirect_url="https://your.domain.com", # Global redirection after successful authentication
    domains=["tenant-users.com"] # Users authentication with these domains will be logged in to this tenant
)

# You can Configure SSO OIDC settings for a tenant manually.
settings = SSOOIDCSettings(
	name="myProvider",
	client_id="myId",
	client_secret="secret",
    redirect_url="https://your.domain.com",
	auth_url="https://dummy.com/auth",
	token_url="https://dummy.com/token",
	user_data_url="https://dummy.com/userInfo",
	scope=["openid", "profile", "email"],
	attribute_mapping=OIDCAttributeMapping(
		login_id="subject",
		name="name",
		given_name="givenName",
		middle_name="middleName",
		family_name="familyName",
		email="email",
		verified_email="verifiedEmail",
		username="username",
		phone_number="phoneNumber",
		verified_phone="verifiedPhone",
		picture="picture"
	)
)
descope_client.mgmt.sso.configure_oidc_settings(
	tenant_id, # Which tenant this configuration is for
	settings, # The OIDC provider settings
    domains=["tenant-users.com"] # Users authentication with these domains will be logged in to this tenant
)

# DEPRECATED (use load_settings(..) function instead)
# You can get SSO settings for a tenant
sso_settings_res = descope_client.mgmt.sso.get_settings("tenant-id")

# DEPRECATED (use configure_saml_settings(..) function instead)
# You can configure SSO settings manually by setting the required fields directly
descope_client.mgmt.sso.configure(
    tenant_id, # Which tenant this configuration is for
    idp_url="https://idp.com",
    entity_id="my-idp-entity-id",
    idp_cert="<your-cert-here>",
    redirect_url="https://your.domain.com", # Global redirection after successful authentication
    domains=["tenant-users.com"] # Users authentication with these domains will be logged in to this tenant
)

# DEPRECATED (use configure_saml_settings_by_metadata(..) function instead)
# Alternatively, configure using an SSO metadata URL
descope_client.mgmt.sso.configure_via_metadata(
    tenant_id, # Which tenant this configuration is for
    idp_metadata_url="https://idp.com/my-idp-metadata",
    redirect_url="", # Redirect URL will have to be provided in every authentication call
    domains=None # Remove the current domains configuration if a value was previously set
)

# DEPRECATED (use configure_saml_settings() or configure_saml_settings_by_metadata(..) functions instead)
# Map IDP groups to Descope roles, or map user attributes.
# This function overrides any previous mapping (even when empty). Use carefully.
descope_client.mgmt.sso.mapping(
    tenant_id, # Which tenant this mapping is for
    role_mappings = [RoleMapping(["IDP_ADMIN"], "Tenant Admin")],
    attribute_mapping=AttributeMapping(name="IDP_NAME", phone_number="IDP_PHONE"),
)
```

Note: Certificates should have a similar structure to:

```
-----BEGIN CERTIFICATE-----
Certifcate contents
-----END CERTIFICATE-----
```

### Manage Permissions

You can create, update, delete or load permissions:

```Python
# You can optionally set a description for a permission.
descope_client.mgmt.permission.create(
    name="My Permission",
    description="Optional description to briefly explain what this permission allows."
)

# Update will override all fields as is. Use carefully.
descope_client.mgmt.permission.update(
    name="My Permission",
    new_name="My Updated Permission",
    description="A revised description"
)

# Permission deletion cannot be undone. Use carefully.
descope_client.mgmt.permission.delete("My Updated Permission")

# Load all permissions
permissions_resp = descope_client.mgmt.permission.load_all()
permissions = permissions_resp["permissions"]
    for permission in permissions:
        # Do something
```

### Manage Roles

You can create, update, delete or load roles:

```Python
# You can optionally set a description and associated permission for a roles.
descope_client.mgmt.role.create(
    name="My Role",
    description="Optional description to briefly explain what this role allows.",
    permission_names=["My Updated Permission"],
    tenant_id="Optionally scope this role for this specific tenant. If left empty, the role will be available to all tenants."
)

# Update will override all fields as is. Use carefully.
descope_client.mgmt.role.update(
    name="My Role",
    new_name="My Updated Role",
    description="A revised description",
    permission_names=["My Updated Permission", "Another Permission"]
    tenant_id="The tenant ID to which this role is associated, leave empty, if role is a global one"
)

# Role deletion cannot be undone. Use carefully.
descope_client.mgmt.role.delete("My Updated Role", "<tenant_id>")

# Load all roles
roles_resp = descope_client.mgmt.role.load_all()
roles = roles_resp["roles"]
    for role in roles:
        # Do something

# Search roles
roles_resp = descope_client.mgmt.role.search(["t1", "t2"], ["r1", "r2"])
roles = roles_resp["roles"]
    for role in roles:
        # Do something
```

### Manage Flows and Theme

You can list your flows and also import and export flows and screens, or the project theme:

```Python
# List all project flows
flows_resp = descope_client.mgmt.flow.list_flows()
print(f'Total number of flows: {flows_resp["total"]}')
flows = flows_resp["flows"]
for flow in flows:
    # Do something

# Delete flows by ids
descope_client.mgmt.flow.delete_flows(
    flow_ids=["flow-1", "flow-2"],
)

# Export a selected flow by id for the flow and matching screens.
exported_flow_and_screens = descope_client.mgmt.flow.export_flow(
    flow_id="sign-up-or-in",
)

# Import a given flow and screens to the flow matching the id provided.
imported_flow_and_screens = descope_client.mgmt.flow.import_flow(
    flow_id="sign-up-or-in",
    flow={},
    screens=[]
)

# Export your project theme.
exported_theme = descope_client.mgmt.flow.export_theme()

# Import a theme to your project.
imported_theme = descope_client.mgmt.flow.import_flow(
    theme={}
)
```

### Query SSO Groups

You can query SSO groups:

```Python
# Load all groups for a given tenant id
groups_resp = descope_client.mgmt.group.load_all_groups(
    tenant_id="tenant-id",
)

# Load all groups for the given user IDs (can be found in the user's JWT)
groups_resp = descope_client.mgmt.group.load_all_groups_for_members(
    tenant_id="tenant-id",
    user_ids=["user-id-1", "user-id-2"],
)

# Load all groups for the given user's login IDs (used for sign-in)
groups_resp = descope_client.mgmt.group.load_all_groups_for_members(
    tenant_id="tenant-id",
    login_ids=["login-id-1", "login-id-2"],
)

# Load all group's members by the given group id
groups_resp = descope_client.mgmt.group.load_all_group_members(
    tenant_id="tenant-id",
    group_id="group-id,
)

for group in groups_resp:
    # Do something
```

### Manage JWTs

You can add custom claims to a valid JWT.

```python
updated_jwt = descope_client.mgmt.jwt.update_jwt(
    jwt="original-jwt",
    custom_claims={
        "custom-key1": "custom-value1",
        "custom-key2": "custom-value2"
    },
)
```

### Impersonate

You can impersonate to another user
The impersonator user must have the `impersonation` permission in order for this request to work.
The response would be a refresh JWT of the impersonated user

```python
refresh_jwt = descope_client.mgmt.jwt.impersonate(
    impersonator_id="<Login ID impersonator>",
    login_id="<Login ID of impersonated person>",
    validate_consent=True
)
```

# Note 1: The generate code/link functions, work only for test users, will not work for regular users.

# Note 2: In case of testing sign-in / sign-up operations with test users, need to make sure to generate the code prior calling the sign-in / sign-up operations.

### Embedded links

Embedded links can be created to directly receive a verifiable token without sending it.

This token can then be verified using the magic link 'verify' function, either directly or through a flow.

```python
token = descope_client.mgmt.user.generate_embedded_link("desmond@descope.com", {"key1":"value1"})
```

### Audit

You can perform an audit search for either specific values or full-text across the fields. Audit search is limited to the last 30 days.
Below are some examples. For a full list of available search criteria options, see the function documentation.

```python
# Full text search on last 10 days
audits = descope_client.mgmt.audit.search(
    text="some-text",
    from_ts=datetime.now(timezone.utc)-timedelta(days=10)
)
# Search successful logins in the last 30 days
audits = descope_client.mgmt.audit.search(actions=["LoginSucceed"])
```

You can also create audit event with data

```python
await descopeClient.management.audit.create_event(
    action="pencil.created",
    type="info", # info/warn/error
    actor_id="UXXX",
    tenant_id="tenant-id"
    data={"some": "data"}
)
```

### Manage ReBAC Authz

Descope supports full relation based access control (ReBAC) using a zanzibar like schema and operations.
A schema is comprized of namespaces (entities like documents, folders, orgs, etc.) and each namespace has relation definitions to define relations.
Each relation definition can be simple (either you have it or not) or complex (union of nodes).

A simple example for a file system like schema would be:

```yaml
# Example schema for the authz tests
name: Files
namespaces:
  - name: org
    relationDefinitions:
      - name: parent
      - name: member
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationLeft
                relationDefinition: parent
                relationDefinitionNamespace: org
                targetRelationDefinition: member
                targetRelationDefinitionNamespace: org
  - name: folder
    relationDefinitions:
      - name: parent
      - name: owner
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: folder
                targetRelationDefinition: owner
                targetRelationDefinitionNamespace: folder
      - name: editor
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: folder
                targetRelationDefinition: editor
                targetRelationDefinitionNamespace: folder
            - nType: child
              expression:
                neType: targetSet
                targetRelationDefinition: owner
                targetRelationDefinitionNamespace: folder
      - name: viewer
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: folder
                targetRelationDefinition: viewer
                targetRelationDefinitionNamespace: folder
            - nType: child
              expression:
                neType: targetSet
                targetRelationDefinition: editor
                targetRelationDefinitionNamespace: folder
  - name: doc
    relationDefinitions:
      - name: parent
      - name: owner
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: doc
                targetRelationDefinition: owner
                targetRelationDefinitionNamespace: folder
      - name: editor
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: doc
                targetRelationDefinition: editor
                targetRelationDefinitionNamespace: folder
            - nType: child
              expression:
                neType: targetSet
                targetRelationDefinition: owner
                targetRelationDefinitionNamespace: doc
      - name: viewer
        complexDefinition:
          nType: union
          children:
            - nType: child
              expression:
                neType: self
            - nType: child
              expression:
                neType: relationRight
                relationDefinition: parent
                relationDefinitionNamespace: doc
                targetRelationDefinition: viewer
                targetRelationDefinitionNamespace: folder
            - nType: child
              expression:
                neType: targetSet
                targetRelationDefinition: editor
                targetRelationDefinitionNamespace: doc
```

Descope SDK allows you to fully manage the schema and relations as well as perform simple (and not so simple) checks regarding the existence of relations.

```python
# Load the existing schema
schema = descope_client.mgmt.authz.load_schema()

# Save schema and make sure to remove all namespaces not listed
descope_client.mgmt.authz.save_schema(schema, True)

# Create a relation between a resource and user
descope_client.mgmt.authz.create_relations(
    [
        {
            "resource": "some-doc",
            "relationDefinition": "owner",
            "namespace": "doc",
            "target": "u1",
        }
    ]
)

# Check if target has the relevant relation
# The answer should be true because an owner is also a viewer
relations = descope_client.mgmt.authz.has_relations(
    [
        {
            "resource": "some-doc",
            "relationDefinition": "viewer",
            "namespace": "doc",
            "target": "u1",
        }
    ]
)

# Get list of targets and resources changed since the given date.
res = descope_client.mgmt.authz.get_modified()
```

### Manage Project

You can change the project name, as well as clone the current project to
create a new one.

```python
# Change the project name
descope_client.mgmt.project.change_name("new-project-name")

# Change project's tags
descope_client.mgmt.project.update_tags(["new", "python"])

# Clone the current project, including its settings and configurations.
# Note that this action is supported only with a pro license or above.
# Users, tenants and access keys are not cloned.
clone_resp = descope_client.mgmt.project.clone("new-project-name")
```

You can manage your project's settings and configurations by exporting your
project's environment. You can also import previously exported data into
the same project or a different one.

```python
# Exports the current state of the project
export = descope_client.mgmt.project.export_project()

# Import the previously exported data into the current project
descope_client.mgmt.project.import_project(export)
```

### Manage SSO Applications

You can create, update, delete or load sso applications:

```python
# Create OIDC SSO application
descope_client.mgmt.sso_application.create_oidc_application(
    name="My First sso app",
	login_page_url="http://dummy.com",
	id="my-custom-id", # This is optional.
)

# Create SAML SSO application
descope_client.mgmt.sso_application.create_saml_application(
    name="My First sso app",
	login_page_url="http://dummy.com",
	id="my-custom-id", # This is optional.
	use_metadata_info=True,
	metadata_url="http://dummy.com/metadata,
	default_relay_state="relayState",
	force_authentication=False,
	logout_redirect_url="http://dummy.com/logout",
)

# Update OIDC SSO application
# Update will override all fields as is. Use carefully.
descope_client.mgmt.sso_application.update_oidc_application(
    id="my-custom-id",
    name="My First sso app",
    login_page_url="http://dummy.com",
)

# Update SAML SSO application
# Update will override all fields as is. Use carefully.
descope_client.mgmt.sso_application.update_saml_application(
    id="my-custom-id",
    name="My First sso app",
    login_page_url="http://dummy.com",
	use_metadata_info=False,
	entity_id="ent1234",
	acs_url="http://dummy.com/acs,
	certificate="my cert"
)

# SSO application deletion cannot be undone. Use carefully.
descope_client.mgmt.sso_application.delete("my-custom-id")

# Load SSO application by id
app_resp = descope_client.mgmt.sso_application.load("my-custom-id")

# Load all SSO applications
apps_resp = descope_client.mgmt.sso_application.load_all()
apps = apps_resp["apps"]
    for app in apps:
        # Do something
```

### Utils for your end to end (e2e) tests and integration tests

To ease your e2e tests, we exposed dedicated management methods,
that way, you don't need to use 3rd party messaging services in order to receive sign-in/up Email, SMS, Voice call, WhatsApp, and avoid the need of parsing the code and token from them.

```Python
# User for test can be created, this user will be able to generate code/link without
# the need of 3rd party messaging services.
# Test user must have a loginId, other fields are optional.
# Roles should be set directly if no tenants exist, otherwise set
# on a per-tenant basis.
descope_client.mgmt.user.create_test_user(
    login_id="desmond@descope.com",
    email="desmond@descope.com",
    display_name="Desmond Copeland",
    user_tenants=[
        AssociatedTenant("my-tenant-id", ["role-name1"]),
    ],
)

# Search all test users, optionally according to tenant and/or role filter
# results can be paginated using the limit and page parameters
users_resp = descope_client.mgmt.user.search_all_test_users()
users = users_resp["users"]
    for user in users:
        # Do something

# Now test user got created, and this user will be available until you delete it,
# you can use any management operation for test user CRUD.
# You can also delete all test users.
descope_client.mgmt.user.delete_all_test_users()

# OTP code can be generated for test user, for example:
resp = descope_client.mgmt.user.generate_otp_for_test_user(
    DeliveryMethod.EMAIL, "login-id"
)
code = resp["code"]
# Now you can verify the code is valid (using descope_client.*.verify for example)
# login_options can be provided to set custom claims to the generated jwt.

# Same as OTP, magic link can be generated for test user, for example:
resp = descope_client.mgmt.user.generate_magic_link_for_test_user(
    DeliveryMethod.EMAIL, "login-id", ""
)
link = resp["link"]

# Enchanted link can be generated for test user, for example:
resp = descope_client.mgmt.user.generate_enchanted_link_for_test_user(
    "login-id", ""
)
link = resp["link"]
pending_ref = resp["pendingRef"]
```

## API Rate Limits

Handle API rate limits by comparing the exception to the APIRateLimitExceeded exception, which includes the RateLimitParameters map with the key "Retry-After." This key indicates how many seconds until the next valid API call can take place.

```python
try:
    descope_client.magiclink.sign_up_or_in(
        method=DeliveryMethod.EMAIL,
        login_id="desmond@descope.com",
        uri="http://myapp.com/verify-magic-link",
    )
except RateLimitException as e:
    retry_after_seconds = e.rate_limit_parameters.get(API_RATE_LIMIT_RETRY_AFTER_HEADER)
    # This variable indicates how many seconds until the next valid API call can take place.
```

## Code Samples

You can find various usage samples in the [samples folder](https://github.com/descope/python-sdk/blob/main/samples).

## Run Locally

### Prerequisites

- Python 3.8.1 or higher
- [Poetry](https://python-poetry.org) installed

### Install dependencies

```bash
poetry install
```

### Run tests

Running all tests:

```bash
poetry run pytest tests
```

Running all tests with coverage:

```bash
poetry run pytest --junitxml=/tmp/pytest.xml --cov-report=term-missing:skip-covered --cov=descope tests/ --cov-report=xml:/tmp/cov.xml
```

## Learn More

To learn more please see the [Descope Documentation and API reference page](https://docs.descope.com/).

## Contact Us

If you need help you can email [Descope Support](mailto:support@descope.com)

## License

The Descope SDK for Python is licensed for use under the terms and conditions of the [MIT license Agreement](https://github.com/descope/python-sdk/blob/main/LICENSE).

            

Raw data

            {
    "_id": null,
    "home_page": "https://descope.com/",
    "name": "descope",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8.1",
    "maintainer_email": null,
    "keywords": null,
    "author": "Descope",
    "author_email": "info@descope.com",
    "download_url": "https://files.pythonhosted.org/packages/43/ef/bb915b8e458abccc2176202ab2f18b67b92c672a984a6e307544c6174ea4/descope-1.7.0.tar.gz",
    "platform": null,
    "description": "# Descope SDK for Python\n\nThe Descope SDK for python provides convenient access to the Descope user management and authentication API\nfor a backend written in python. You can read more on the [Descope Website](https://descope.com).\n\n## Requirements\n\nThe SDK supports Python 3.8.1 and above.\n\n## Installing the SDK\n\nInstall the package with:\n\n```bash\npip install descope\n```\n\n#### If you would like to use the Flask decorators, make sure to install the Flask extras:\n\n```bash\npip install descope[Flask]\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```python\nfrom descope import DescopeClient\n\n# Initialized after setting the DESCOPE_PROJECT_ID env var\ndescope_client = DescopeClient()\n\n# ** Or directly **\ndescope_client = DescopeClient(project_id=\"<Project ID>\")\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 / OIDC)](#sso-saml--oidc)\n6. [TOTP Authentication](#totp-authentication)\n7. [Passwords](#passwords)\n8. [Session Validation](#session-validation)\n9. [Roles & Permission Validation](#roles--permission-validation)\n10. [Tenant selection](#tenant-selection)\n11. [Logging Out](#logging-out)\n12. [History](#history)\n13. [My Tenants](#my-tenants)\n\n## API Managment Function\n\nThese sections show how to use the SDK to perform permission and user management functions. You will need to create an instance of `DescopeClient` by following the [Setup](#setup-1) guide, before you can use any of these functions:\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-and-theme)\n9. [Manage JWTs](#manage-jwts)\n10. [Impersonate](#impersonate)\n11. [Embedded links](#embedded-links)\n12. [Audit](#audit)\n13. [Manage ReBAC Authz](#manage-rebac-authz)\n14. [Manage Project](#manage-project)\n15. [Manage SSO Applications](#manage-sso-applications)\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 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### 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```python\nfrom descope import DeliveryMethod\n\n# Every user must have a login ID. All other user information is optional\nemail = \"desmond@descope.com\"\nuser = {\"name\": \"Desmond Copeland\", \"phone\": \"212-555-1234\", \"email\": email}\nmasked_address = descope_client.otp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)\n```\n\nThe user will receive a code using the selected delivery method. Verify that code using:\n\n```python\njwt_response = descope_client.otp.verify_code(\n    method=DeliveryMethod.EMAIL, login_id=email, code=value\n)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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 / Voice call / 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 generally 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```python\nfrom descope import DeliveryMethod\n\nmasked_address = descope_client.magiclink.sign_up_or_in(\n    method=DeliveryMethod.EMAIL,\n    login_id=\"desmond@descope.com\",\n    uri=\"http://myapp.com/verify-magic-link\", # Set redirect URI here or via console\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=<token>`):\n\n```python\njwt_response = descope_client.magiclink.verify(token=token)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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```python\nresp = descope_client.enchantedlink.sign_up_or_in(\n    login_id=email,\n    uri=\"http://myapp.com/verify-enchanted-link\", # Set redirect URI here or via console\n)\nlink_identifier = resp[\"linkId\"] # Show the user which link they should press in their email\npending_ref = resp[\"pendingRef\"] # Used to poll for a valid session\nmasked_email = resp[\"maskedEmail\"] # The email that the message was sent to in a masked format\n```\n\nAfter sending the link, you must poll to receive a valid session using the `pending_ref` from\nthe previous step. A valid session will be returned only after the user clicks the right link.\n\n```python\ni = 0\nwhile not done and i < max_tries:\n    try:\n        i = i + 1\n        sleep(4)\n        jwt_response = descope_client.enchantedlink.get_session(pending_ref)\n        done = True\n    except AuthException as e: # Poll while still receiving 401 Unauthorized\n        if e.status_code != 401: # Other failures means something's wrong, abort\n            logging.info(f\"Failed pending session, err: {e}\")\n            done = True\n\nif jwt_response:\n    session_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\n    refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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=<token>`). Once the token is verified, the session polling will receive a valid `jwt_response`.\n\n```python\ntry:\n    descope_client.enchantedlink.verify(token=token)\n    # Token is valid\nexcept AuthException as e:\n    # Token is invalid\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```python\n\ndescope_client.oauth.start(\n    provider=\"google\", # Choose an oauth provider out of the supported providers\n    return_url=\"https://my-app.com/handle-oauth\", # Can be configured in the console instead of here\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```python\njwt_response = descope_client.oauth.exchange_token(code)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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 / OIDC)\n\nUsers can authenticate to a specific tenant using SAML/OIDC based on the tenant settings. Configure your SAML/OIDC tenant settings on the [Descope console](https://app.descope.com/tenants). To start a flow call:\n\n```python\ndescope_client.sso.start(\n    tenant=\"my-tenant-ID\", # Choose which tenant to log into\n    return_url=\"https://my-app.com/handle-sso\", # Can be configured in the console instead of here\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```python\njwt_response = descope_client.sso.exchange_token(code)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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\nNote: the descope_client.saml.start(..) and descope_client.saml.exchange_token(..) functions are DEPRECATED, use the above sso functions instead\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 `provisioning_url`.\n\nExisting users can add TOTP using the `update` function.\n\n```python\nfrom descope import DeliveryMethod\n\n# Every user must have a login ID. All other user information is optional\nemail = \"desmond@descope.com\"\nuser = {\"name\": \"Desmond Copeland\", \"phone\": \"212-555-1234\", \"email\": email}\ntotp_response = descope_client.totp.sign_up(method=DeliveryMethod.EMAIL, login_id=email, user=user)\n\n# Use one of the provided options to have the user add their credentials to the authenticator\nprovisioning_url = totp_response[\"provisioningURL\"]\nimage = totp_response[\"image\"]\nkey = totp_response[\"key\"]\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```python\njwt_response = descope_client.totp.sign_in_code(\n    login_id=email,\n    code=code, # Code from authenticator app\n)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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```python\n# Every user must have a login_id and a password. All other user information is optional\nlogin_id = \"desmond@descope.com\"\npassword = \"qYlvi65KaX\"\nuser = {\n    \"name\": \"Desmond Copeland\",\n    \"email\": login_id,\n}\njwt_response = descope_client.password.sign_up(\n    login_id=login_id,\n    password=password,\n    user=user,\n)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\n```\n\nThe user can later sign in using the same login_id and password.\n\n```python\njwt_response = descope_client.password.sign_in(login_id, password)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\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: send_reset 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```python\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.\nlogin_id = \"desmond@descope.com\"\nredirect_url = \"https://myapp.com/password-reset\"\ndescope_client.password.send_reset(login_id, redirect_url)\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). However, after verifying the user, it is expected\nto allow them to provide a new password instead of the old one. Since the user is now authenticated, this is possible via:\n\n```python\n# The refresh token is required to make sure the user is authenticated.\nerr = descope_client.password.update(login_id, new_password, token)\n```\n\n`update` 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```python\n# Replaces the user's current password with a new one\njwt_response = descope_client.password.replace(login_id, old_password, new_password)\nsession_token = jwt_response[SESSION_TOKEN_NAME].get(\"jwt\")\nrefresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get(\"jwt\")\n```\n\n### Session Validation\n\nEvery secure request performed between your client and server needs to be validated. The client sends\nthe session and refresh tokens with every request, and they are validated using one of the following:\n\n```python\n# Validate the session. Will raise if expired\ntry:\n    jwt_response = descope_client.validate_session(session_token)\nexcept AuthException:\n    # Session expired\n\n# If validate_session raises an exception, you will need to refresh the session using\njwt_response = descope_client.refresh_session(refresh_token)\n\n# Alternatively, you could combine the two and\n# have the session validated and automatically refreshed when expired\njwt_response = descope_client.validate_and_refresh_session(session_token, refresh_token)\n```\n\nChoose the right session validation and refresh combination that suits your needs.\n\nNote: all those validation apis can receive an optional 'audience' parameter that should be provided when using jwt that has the 'aud' claim.\n\nRefreshed sessions return the same response as is returned when users first sign up / log in,\ncontaining the session and refresh tokens, as well as all of the JWT claims.\nMake sure to return the tokens from the response to the client, or updated the cookie if you're using it.\n\nUsually, the tokens can be passed in and out via HTTP headers or via a cookie.\nThe implementation can defer according to your framework of choice. See our [samples](#code-samples) for a few examples.\n\nIf Roles & Permissions are used, validate them immediately after validating the session. See the [next section](#roles--permission-validation)\nfor more information.\n\n### Roles & Permission Validation\n\nWhen using Roles & Permission, it's important to validate the user has the required\nauthorization immediately after making sure the session is valid. Taking the `jwt_response`\nreceived by the [session validation](#session-validation), call the following functions:\n\nFor multi-tenant uses:\n\n```python\n# You can validate specific permissions\nvalid_permissions = descope_client.validate_tenant_permissions(\n    jwt_response, \"my-tenant-ID\", [\"Permission to validate\"]\n)\nif not valid_permissions:\n    # Deny access\n\n# Or validate roles directly\nvalid_roles = descope_client.validate_tenant_roles(\n    jwt_response, \"my-tenant-ID\", [\"Role to validate\"]\n)\nif not valid_roles:\n    # Deny access\n\n# Or get the matched roles/permissions\nmatched_tenant_roles = descope_client.get_matched_tenant_roles(\n\t\tjwt_response, \"my-tenant-ID\", [\"role-name1\", \"role-name2\"]\n)\n\nmatched_tenant_permissions = descope_client.get_matched_tenant_permissions(\n\t\tjwt_response, \"my-tenant-ID\", [\"permission-name1\", \"permission-name2\"]\n)\n```\n\nWhen not using tenants use:\n\n```python\n# You can validate specific permissions\nvalid_permissions = descope_client.validate_permissions(\n    jwt_response, [\"Permission to validate\"]\n)\nif not valid_permissions:\n    # Deny access\n\n# Or validate roles directly\nvalid_roles = descope_client.validate_roles(\n    jwt_response, [\"Role to validate\"]\n)\nif not valid_roles:\n    # Deny access\n\n# Or get the matched roles/permissions\nmatched_roles = descope_client.get_matched_roles(\n\t\tjwt_response, [\"role-name1\", \"role-name2\"]\n)\n\nmatched_permissions = descope_client.get_matched_permissions(\n\t\tjwt_response, [\"permission-name1\", \"permission-name2\"]\n)\n```\n\n### Tenant selection\n\nFor a user that has permissions to multiple tenants, you can set a specific tenant as the current selected one\nThis will add an extra attribute to the refresh JWT and the session JWT with the selected tenant ID\n\n```python\ntenant_id_ = \"t1\"\njwt_response = descope_client.select_tenant(tenant_id, refresh_token)\n```\n\n### Logging Out\n\nYou can log out a user from an active session by providing their `refresh_token` for that session.\nAfter calling this function, you must invalidate or remove any cookies you have created.\n\n```python\ndescope_client.logout(refresh_token)\n```\n\nIt is also possible to sign the user out of all the devices they are currently signed-in with. Calling `logout_all` will\ninvalidate all user's refresh tokens. After calling this function, you must invalidate or remove any cookies you have created.\n\n```python\ndescope_client.logout_all(refresh_token)\n```\n\n### History\n\nYou can get the current session user history.\nThe request requires a valid refresh token.\n\n```python\nusers_history_resp = descope_client.history(refresh_token)\nfor user_history in users_history_resp:\n    # Do something\n```\n\n### My Tenants\n\nYou can get the current session user tenants.\nThe request requires a valid refresh token.\nAnd either a boolean to receive the current selected tenant\nOr a list of tenant IDs that this user is part of\n\n```python\ntenants_resp = descope_client.my_tenants(refresh_token, False, [\"tenant_id\"])\nfor tenant in tenants_resp.tenants:\n    # Do something\n```\n\n## Management API\n\nIt is very common for some form of management or automation to be required. These can be performed\nusing the management API. 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```python\nfrom descope import DescopeClient\n\n# Initialized after setting the DESCOPE_PROJECT_ID and the DESCOPE_MANAGEMENT_KEY env vars\ndescope_client = DescopeClient()\n\n# ** Or directly **\ndescope_client = DescopeClient(project_id=\"<Project ID>\", management_key=\"<Management Key>\")\n```\n\n### Manage Tenants\n\nYou can create, update, delete or load tenants:\n\n```Python\n# You can optionally set your own ID when creating a tenant\ndescope_client.mgmt.tenant.create(\n    name=\"My First Tenant\",\n    id=\"my-custom-id\", # This is optional.\n    self_provisioning_domains=[\"domain.com\"],\n    custom_attributes={\"attribute-name\": \"value\"},\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.tenant.update(\n    id=\"my-custom-id\",\n    name=\"My First Tenant\",\n    self_provisioning_domains=[\"domain.com\", \"another-domain.com\"],\n    custom_attributes={\"attribute-name\": \"value\"},\n)\n\n# Tenant deletion cannot be undone. Use carefully.\n# Pass true to cascade value, in case you want to delete all users/keys associated only with this tenant\ndescope_client.mgmt.tenant.delete(id=\"my-custom-id\", cascade=False)\n\n# Load tenant by id\ntenant_resp = descope_client.mgmt.tenant.load(\"my-custom-id\")\n\n# Load all tenants\ntenants_resp = descope_client.mgmt.tenant.load_all()\ntenants = tenants_resp[\"tenants\"]\n    for tenant in tenants:\n        # Do something\n\n# search all tenants\ntenants_resp = descope_client.mgmt.tenant.search_all(ids=[\"id1\"], names=[\"name1\"], custom_attributes={\"k1\":\"v1\"}, self_provisioning_domains=[\"spd1\"])\ntenants = tenants_resp[\"tenants\"]\n    for tenant in tenants:\n        # Do something\n```\n\n### Manage Users\n\nYou can create, update, patch, delete or load users, as well as setting new password, expire password and search according to filters:\n\n```Python\n# A user must have a login ID, other fields are optional.\n# Roles should be set directly if no tenants exist, otherwise set\n# on a per-tenant basis.\ndescope_client.mgmt.user.create(\n    login_id=\"desmond@descope.com\",\n    email=\"desmond@descope.com\",\n    display_name=\"Desmond Copeland\",\n    user_tenants=[\n        AssociatedTenant(\"my-tenant-id\", [\"role-name1\"]),\n    ],\n\tsso_app_ids=[\"appId1\"],\n)\n\n# Alternatively, a user can be created and invited via an email message.\n# Make sure to configure the invite URL in the Descope console prior to using this function,\n# and that an email address is provided in the information.\ndescope_client.mgmt.user.invite(\n    login_id=\"desmond@descope.com\",\n    email=\"desmond@descope.com\",\n    display_name=\"Desmond Copeland\",\n    user_tenants=[\n        AssociatedTenant(\"my-tenant-id\", [\"role-name1\"]),\n    ],\n\tsso_app_ids=[\"appId1\"],\n)\n\n# Batch invite\ndescope_client.mgmt.user.invite_batch(\n    users=[\n        UserObj(\n            login_id=\"desmond@descope.com\",\n            email=\"desmond@descope.com\",\n            display_name=\"Desmond Copeland\",\n            user_tenants=[\n                AssociatedTenant(\"my-tenant-id\", [\"role-name1\"]),\n            ],\n            custom_attributes={\"ak\": \"av\"},\n\t\t\tsso_app_ids=[\"appId1\"],\n        )\n    ],\n    invite_url=\"invite.me\",\n    send_mail=True,\n    send_sms=True,\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.user.update(\n    login_id=\"desmond@descope.com\",\n    email=\"desmond@descope.com\",\n    display_name=\"Desmond Copeland\",\n    user_tenants=[\n        AssociatedTenant(\"my-tenant-id\", [\"role-name1\", \"role-name2\"]),\n    ],\n\tsso_app_ids=[\"appId1\"],\n)\n\n# Patch will override only the set fields in the user\ndescope_client.mgmt.user.patch(\n    login_id=\"desmond@descope.com\",\n    email=\"desmond@descope.com\",\n    display_name=\"Desmond Copeland\",\n)\n\n# Update explicit data for a user rather than overriding all fields\ndescope_client.mgmt.user.update_login_id(\n    login_id=\"desmond@descope.com\",\n    new_login_id=\"bane@descope.com\"\n)\ndescope_client.mgmt.user.update_phone(\n    login_id=\"desmond@descope.com\",\n    phone=\"+18005551234\",\n    verified=True,\n)\ndescope_client.mgmt.user.remove_tenant_roles(\n    login_id=\"desmond@descope.com\",\n    tenant_id=\"my-tenant-id\",\n    role_names=[\"role-name1\"],\n)\n\n# Set SSO applications association to a user.\nuser = descope_client.mgmt.user.set_sso_apps(\n\tlogin_id=\"desmond@descope.com\",\n\tsso_app_ids=[\"appId1\", \"appId2\"]\n)\n\n# Add SSO applications association to a user.\nuser = descope_client.mgmt.user.add_sso_apps(\n\tlogin_id=\"desmond@descope.com\",\n\tsso_app_ids=[\"appId1\", \"appId2\"]\n)\n\n# Remove SSO applications association from a user.\nuser = descope_client.mgmt.user.remove_sso_apps(\n\tlogin_id=\"desmond@descope.com\",\n\tsso_app_ids=[\"appId1\", \"appId2\"]\n)\n\n# User deletion cannot be undone. Use carefully.\ndescope_client.mgmt.user.delete(\"desmond@descope.com\")\n\n# Load specific user\nuser_resp = descope_client.mgmt.user.load(\"desmond@descope.com\")\nuser = user_resp[\"user\"]\n\n# If needed, users can be loaded using the user ID as well\nuser_resp = descope_client.mgmt.user.load_by_user_id(\"<user-id>\")\nuser = user_resp[\"user\"]\n\n# Logout user from all devices by login ID\ndescope_client.mgmt.user.logout_user(\"<login-id>\")\n\n# Logout user from all devices by user ID\ndescope_client.mgmt.user.logout_user_by_user_id(\"<user-id>\")\n\n# Search all users, optionally according to tenant and/or role filter\n# results can be paginated using the limit and page parameters\nusers_resp = descope_client.mgmt.user.search_all(tenant_ids=[\"my-tenant-id\"])\nusers = users_resp[\"users\"]\n    for user in users:\n        # Do something\n\n# Get users' authentication history\nusers_history_resp = descope_client.mgmt.user.history([\"user-id-1\", \"user-id-2\"])\n    for user_history in users_history_resp:\n        # Do something\n```\n\n#### Set or Expire User Password\n\nYou can set a new active password for a user that they can sign in with.\nYou can also set a temporary password that the user will be forced to change on the next login.\nFor a user that already has an active password, you can expire their current password, effectively requiring them to change it on the next login.\n\n```Python\n\n# Set a user's temporary password\ndescope_client.mgmt.user.set_temporary_password('<login-id>', '<some-password>');\n\n# Set a user's password\ndescope_client.mgmt.user.set_active_password('<login-id>', '<some-password>');\n\n# Or alternatively, expire a user password\ndescope_client.mgmt.user.expirePassword('<login-id>');\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```Python\n# An access key must have a name and expiration, other fields are optional.\n# Roles should be set directly if no tenants exist, otherwise set\n# on a per-tenant basis.\n# If user_id is supplied, then authorization would be ignored, and access key would be bound to the users authorization.\n# If description is supplied, then the access key will hold a descriptive text.\n# If permitted_ips is supplied, then the access key can only be used from that list of IP addresses or CIDR ranges\ncreate_resp = descope_client.mgmt.access_key.create(\n    name=\"name\",\n    expire_time=1677844931,\n    key_tenants=[\n        AssociatedTenant(\"my-tenant-id\", [\"role-name1\"]),\n    ],\n    description=\"this is my access key\",\n    permitted_ips=['10.0.0.1', '192.168.1.0/24'],\n)\nkey = create_resp[\"key\"]\ncleartext = create_resp[\"cleartext\"] # make sure to save the returned cleartext securely. It will not be returned again.\n\n# Load a specific access key\naccess_key_resp = descope_client.mgmt.access_key.load(\"key-id\")\naccess_key = access_key_resp[\"key\"]\n\n# Search all access keys, optionally according to a tenant filter\nkeys_resp = descope_client.mgmt.access_key.search_all_access_keys(tenant_ids=[\"my-tenant-id\"])\nkeys = keys_resp[\"keys\"]\n    for key in keys:\n        # Do something\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.access_key.update(\n    id=\"key-id\",\n    name=\"new name\",\n)\n\n# Access keys can be deactivated to prevent usage. This can be undone using \"activate\".\ndescope_client.mgmt.access_key.deactivate(\"key-id\")\n\n# Disabled access keys can be activated once again.\ndescope_client.mgmt.access_key.activate(\"key-id\")\n\n# Access key deletion cannot be undone. Use carefully.\ndescope_client.mgmt.access_key.delete(\"key-id\")\n```\n\nExchange the access key and provide optional access key login options:\n\n```python\nloc = AccessKeyLoginOptions(custom_claims={\"k1\": \"v1\"})\njwt_response = descope_client.exchange_access_key(\n  access_key=\"accessKey\", login_options=loc\n)\n```\n\n### Manage SSO Setting\n\nYou can manage SSO settings and map SSO group roles and user attributes.\n\n```Python\n# You can load all tenant SSO settings\nsso_settings_res = descope_client.mgmt.sso.load_settings(\"tenant-id\")\n\n# import based on your configuration needs:\nfrom descope import (\n    SSOOIDCSettings,\n    OIDCAttributeMapping,\n    SSOSAMLSettings,\n    AttributeMapping,\n    RoleMapping,\n    SSOSAMLSettingsByMetadata\n)\n\n# You can Configure SSO SAML settings for a tenant manually.\nsettings = SSOSAMLSettings(\n\tidp_url=\"https://dummy.com/saml\",\n\tidp_entity_id=\"entity1234\",\n\tidp_cert=\"my certificate\",\n\tattribute_mapping=AttributeMapping(\n\t\tname=\"name\",\n\t\tgiven_name=\"givenName\",\n\t\tmiddle_name=\"middleName\",\n\t\tfamily_name=\"familyName\",\n\t\tpicture=\"picture\",\n\t\temail=\"email\",\n\t\tphone_number=\"phoneNumber\",\n\t\tgroup=\"groups\"\n\t),\n\trole_mappings=[RoleMapping(groups=[\"grp1\"], role=\"rl1\")],\n)\ndescope_client.mgmt.sso.configure_saml_settings(\n\ttenant_id, # Which tenant this configuration is for\n\tsettings, # The SAML settings\n\tredirect_url=\"https://your.domain.com\", # Global redirection after successful authentication\n    domains=[\"tenant-users.com\"] # Users authentication with these domains will be logged in to this tenant\n)\n\n# You can Configure SSO SAML settings for a tenant by fetching them from an IDP metadata URL.\nsettings = SSOSAMLSettingsByMetadata(\n\tidp_metadata_url=\"https://dummy.com/metadata\",\n\tattribute_mapping=AttributeMapping(\n\t\tname=\"myName\",\n\t\tgiven_name=\"givenName\",\n\t\tmiddle_name=\"middleName\",\n\t\tfamily_name=\"familyName\",\n\t\tpicture=\"picture\",\n\t\temail=\"email\",\n\t\tphone_number=\"phoneNumber\",\n\t\tgroup=\"groups\"\n\t),\n\trole_mappings=[RoleMapping(groups=[\"grp1\"], role=\"rl1\")],\n)\ndescope_client.mgmt.sso.configure_saml_settings_by_metadata(\n\ttenant_id, # Which tenant this configuration is for\n\tsettings,  # The SAML settings\n\tredirect_url=\"https://your.domain.com\", # Global redirection after successful authentication\n    domains=[\"tenant-users.com\"] # Users authentication with these domains will be logged in to this tenant\n)\n\n# You can Configure SSO OIDC settings for a tenant manually.\nsettings = SSOOIDCSettings(\n\tname=\"myProvider\",\n\tclient_id=\"myId\",\n\tclient_secret=\"secret\",\n    redirect_url=\"https://your.domain.com\",\n\tauth_url=\"https://dummy.com/auth\",\n\ttoken_url=\"https://dummy.com/token\",\n\tuser_data_url=\"https://dummy.com/userInfo\",\n\tscope=[\"openid\", \"profile\", \"email\"],\n\tattribute_mapping=OIDCAttributeMapping(\n\t\tlogin_id=\"subject\",\n\t\tname=\"name\",\n\t\tgiven_name=\"givenName\",\n\t\tmiddle_name=\"middleName\",\n\t\tfamily_name=\"familyName\",\n\t\temail=\"email\",\n\t\tverified_email=\"verifiedEmail\",\n\t\tusername=\"username\",\n\t\tphone_number=\"phoneNumber\",\n\t\tverified_phone=\"verifiedPhone\",\n\t\tpicture=\"picture\"\n\t)\n)\ndescope_client.mgmt.sso.configure_oidc_settings(\n\ttenant_id, # Which tenant this configuration is for\n\tsettings, # The OIDC provider settings\n    domains=[\"tenant-users.com\"] # Users authentication with these domains will be logged in to this tenant\n)\n\n# DEPRECATED (use load_settings(..) function instead)\n# You can get SSO settings for a tenant\nsso_settings_res = descope_client.mgmt.sso.get_settings(\"tenant-id\")\n\n# DEPRECATED (use configure_saml_settings(..) function instead)\n# You can configure SSO settings manually by setting the required fields directly\ndescope_client.mgmt.sso.configure(\n    tenant_id, # Which tenant this configuration is for\n    idp_url=\"https://idp.com\",\n    entity_id=\"my-idp-entity-id\",\n    idp_cert=\"<your-cert-here>\",\n    redirect_url=\"https://your.domain.com\", # Global redirection after successful authentication\n    domains=[\"tenant-users.com\"] # Users authentication with these domains will be logged in to this tenant\n)\n\n# DEPRECATED (use configure_saml_settings_by_metadata(..) function instead)\n# Alternatively, configure using an SSO metadata URL\ndescope_client.mgmt.sso.configure_via_metadata(\n    tenant_id, # Which tenant this configuration is for\n    idp_metadata_url=\"https://idp.com/my-idp-metadata\",\n    redirect_url=\"\", # Redirect URL will have to be provided in every authentication call\n    domains=None # Remove the current domains configuration if a value was previously set\n)\n\n# DEPRECATED (use configure_saml_settings() or configure_saml_settings_by_metadata(..) functions instead)\n# Map IDP groups to Descope roles, or map user attributes.\n# This function overrides any previous mapping (even when empty). Use carefully.\ndescope_client.mgmt.sso.mapping(\n    tenant_id, # Which tenant this mapping is for\n    role_mappings = [RoleMapping([\"IDP_ADMIN\"], \"Tenant Admin\")],\n    attribute_mapping=AttributeMapping(name=\"IDP_NAME\", phone_number=\"IDP_PHONE\"),\n)\n```\n\nNote: Certificates should have a similar structure to:\n\n```\n-----BEGIN CERTIFICATE-----\nCertifcate contents\n-----END CERTIFICATE-----\n```\n\n### Manage Permissions\n\nYou can create, update, delete or load permissions:\n\n```Python\n# You can optionally set a description for a permission.\ndescope_client.mgmt.permission.create(\n    name=\"My Permission\",\n    description=\"Optional description to briefly explain what this permission allows.\"\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.permission.update(\n    name=\"My Permission\",\n    new_name=\"My Updated Permission\",\n    description=\"A revised description\"\n)\n\n# Permission deletion cannot be undone. Use carefully.\ndescope_client.mgmt.permission.delete(\"My Updated Permission\")\n\n# Load all permissions\npermissions_resp = descope_client.mgmt.permission.load_all()\npermissions = permissions_resp[\"permissions\"]\n    for permission in permissions:\n        # Do something\n```\n\n### Manage Roles\n\nYou can create, update, delete or load roles:\n\n```Python\n# You can optionally set a description and associated permission for a roles.\ndescope_client.mgmt.role.create(\n    name=\"My Role\",\n    description=\"Optional description to briefly explain what this role allows.\",\n    permission_names=[\"My Updated Permission\"],\n    tenant_id=\"Optionally scope this role for this specific tenant. If left empty, the role will be available to all tenants.\"\n)\n\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.role.update(\n    name=\"My Role\",\n    new_name=\"My Updated Role\",\n    description=\"A revised description\",\n    permission_names=[\"My Updated Permission\", \"Another Permission\"]\n    tenant_id=\"The tenant ID to which this role is associated, leave empty, if role is a global one\"\n)\n\n# Role deletion cannot be undone. Use carefully.\ndescope_client.mgmt.role.delete(\"My Updated Role\", \"<tenant_id>\")\n\n# Load all roles\nroles_resp = descope_client.mgmt.role.load_all()\nroles = roles_resp[\"roles\"]\n    for role in roles:\n        # Do something\n\n# Search roles\nroles_resp = descope_client.mgmt.role.search([\"t1\", \"t2\"], [\"r1\", \"r2\"])\nroles = roles_resp[\"roles\"]\n    for role in roles:\n        # Do something\n```\n\n### Manage Flows and Theme\n\nYou can list your flows and also import and export flows and screens, or the project theme:\n\n```Python\n# List all project flows\nflows_resp = descope_client.mgmt.flow.list_flows()\nprint(f'Total number of flows: {flows_resp[\"total\"]}')\nflows = flows_resp[\"flows\"]\nfor flow in flows:\n    # Do something\n\n# Delete flows by ids\ndescope_client.mgmt.flow.delete_flows(\n    flow_ids=[\"flow-1\", \"flow-2\"],\n)\n\n# Export a selected flow by id for the flow and matching screens.\nexported_flow_and_screens = descope_client.mgmt.flow.export_flow(\n    flow_id=\"sign-up-or-in\",\n)\n\n# Import a given flow and screens to the flow matching the id provided.\nimported_flow_and_screens = descope_client.mgmt.flow.import_flow(\n    flow_id=\"sign-up-or-in\",\n    flow={},\n    screens=[]\n)\n\n# Export your project theme.\nexported_theme = descope_client.mgmt.flow.export_theme()\n\n# Import a theme to your project.\nimported_theme = descope_client.mgmt.flow.import_flow(\n    theme={}\n)\n```\n\n### Query SSO Groups\n\nYou can query SSO groups:\n\n```Python\n# Load all groups for a given tenant id\ngroups_resp = descope_client.mgmt.group.load_all_groups(\n    tenant_id=\"tenant-id\",\n)\n\n# Load all groups for the given user IDs (can be found in the user's JWT)\ngroups_resp = descope_client.mgmt.group.load_all_groups_for_members(\n    tenant_id=\"tenant-id\",\n    user_ids=[\"user-id-1\", \"user-id-2\"],\n)\n\n# Load all groups for the given user's login IDs (used for sign-in)\ngroups_resp = descope_client.mgmt.group.load_all_groups_for_members(\n    tenant_id=\"tenant-id\",\n    login_ids=[\"login-id-1\", \"login-id-2\"],\n)\n\n# Load all group's members by the given group id\ngroups_resp = descope_client.mgmt.group.load_all_group_members(\n    tenant_id=\"tenant-id\",\n    group_id=\"group-id,\n)\n\nfor group in groups_resp:\n    # Do something\n```\n\n### Manage JWTs\n\nYou can add custom claims to a valid JWT.\n\n```python\nupdated_jwt = descope_client.mgmt.jwt.update_jwt(\n    jwt=\"original-jwt\",\n    custom_claims={\n        \"custom-key1\": \"custom-value1\",\n        \"custom-key2\": \"custom-value2\"\n    },\n)\n```\n\n### Impersonate\n\nYou can impersonate to another user\nThe impersonator user must have the `impersonation` permission in order for this request to work.\nThe response would be a refresh JWT of the impersonated user\n\n```python\nrefresh_jwt = descope_client.mgmt.jwt.impersonate(\n    impersonator_id=\"<Login ID impersonator>\",\n    login_id=\"<Login ID of impersonated person>\",\n    validate_consent=True\n)\n```\n\n# Note 1: The generate code/link functions, work only for test users, will not work for regular users.\n\n# Note 2: In case of testing sign-in / sign-up operations with test users, need to make sure to generate the code prior calling the sign-in / sign-up operations.\n\n### Embedded links\n\nEmbedded links can be created to directly receive a verifiable token without sending it.\n\nThis token can then be verified using the magic link 'verify' function, either directly or through a flow.\n\n```python\ntoken = descope_client.mgmt.user.generate_embedded_link(\"desmond@descope.com\", {\"key1\":\"value1\"})\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.\nBelow are some examples. For a full list of available search criteria options, see the function documentation.\n\n```python\n# Full text search on last 10 days\naudits = descope_client.mgmt.audit.search(\n    text=\"some-text\",\n    from_ts=datetime.now(timezone.utc)-timedelta(days=10)\n)\n# Search successful logins in the last 30 days\naudits = descope_client.mgmt.audit.search(actions=[\"LoginSucceed\"])\n```\n\nYou can also create audit event with data\n\n```python\nawait descopeClient.management.audit.create_event(\n    action=\"pencil.created\",\n    type=\"info\", # info/warn/error\n    actor_id=\"UXXX\",\n    tenant_id=\"tenant-id\"\n    data={\"some\": \"data\"}\n)\n```\n\n### Manage ReBAC Authz\n\nDescope supports full relation based access control (ReBAC) using a zanzibar like schema and operations.\nA schema is comprized of namespaces (entities like documents, folders, orgs, etc.) and each namespace has relation definitions to define relations.\nEach relation definition can be simple (either you have it or not) or complex (union of nodes).\n\nA simple example for a file system like schema would be:\n\n```yaml\n# Example schema for the authz tests\nname: Files\nnamespaces:\n  - name: org\n    relationDefinitions:\n      - name: parent\n      - name: member\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationLeft\n                relationDefinition: parent\n                relationDefinitionNamespace: org\n                targetRelationDefinition: member\n                targetRelationDefinitionNamespace: org\n  - name: folder\n    relationDefinitions:\n      - name: parent\n      - name: owner\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: folder\n                targetRelationDefinition: owner\n                targetRelationDefinitionNamespace: folder\n      - name: editor\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: folder\n                targetRelationDefinition: editor\n                targetRelationDefinitionNamespace: folder\n            - nType: child\n              expression:\n                neType: targetSet\n                targetRelationDefinition: owner\n                targetRelationDefinitionNamespace: folder\n      - name: viewer\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: folder\n                targetRelationDefinition: viewer\n                targetRelationDefinitionNamespace: folder\n            - nType: child\n              expression:\n                neType: targetSet\n                targetRelationDefinition: editor\n                targetRelationDefinitionNamespace: folder\n  - name: doc\n    relationDefinitions:\n      - name: parent\n      - name: owner\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: doc\n                targetRelationDefinition: owner\n                targetRelationDefinitionNamespace: folder\n      - name: editor\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: doc\n                targetRelationDefinition: editor\n                targetRelationDefinitionNamespace: folder\n            - nType: child\n              expression:\n                neType: targetSet\n                targetRelationDefinition: owner\n                targetRelationDefinitionNamespace: doc\n      - name: viewer\n        complexDefinition:\n          nType: union\n          children:\n            - nType: child\n              expression:\n                neType: self\n            - nType: child\n              expression:\n                neType: relationRight\n                relationDefinition: parent\n                relationDefinitionNamespace: doc\n                targetRelationDefinition: viewer\n                targetRelationDefinitionNamespace: folder\n            - nType: child\n              expression:\n                neType: targetSet\n                targetRelationDefinition: editor\n                targetRelationDefinitionNamespace: doc\n```\n\nDescope SDK allows you to fully manage the schema and relations as well as perform simple (and not so simple) checks regarding the existence of relations.\n\n```python\n# Load the existing schema\nschema = descope_client.mgmt.authz.load_schema()\n\n# Save schema and make sure to remove all namespaces not listed\ndescope_client.mgmt.authz.save_schema(schema, True)\n\n# Create a relation between a resource and user\ndescope_client.mgmt.authz.create_relations(\n    [\n        {\n            \"resource\": \"some-doc\",\n            \"relationDefinition\": \"owner\",\n            \"namespace\": \"doc\",\n            \"target\": \"u1\",\n        }\n    ]\n)\n\n# Check if target has the relevant relation\n# The answer should be true because an owner is also a viewer\nrelations = descope_client.mgmt.authz.has_relations(\n    [\n        {\n            \"resource\": \"some-doc\",\n            \"relationDefinition\": \"viewer\",\n            \"namespace\": \"doc\",\n            \"target\": \"u1\",\n        }\n    ]\n)\n\n# Get list of targets and resources changed since the given date.\nres = descope_client.mgmt.authz.get_modified()\n```\n\n### Manage Project\n\nYou can change the project name, as well as clone the current project to\ncreate a new one.\n\n```python\n# Change the project name\ndescope_client.mgmt.project.change_name(\"new-project-name\")\n\n# Change project's tags\ndescope_client.mgmt.project.update_tags([\"new\", \"python\"])\n\n# Clone the current project, including its settings and configurations.\n# Note that this action is supported only with a pro license or above.\n# Users, tenants and access keys are not cloned.\nclone_resp = descope_client.mgmt.project.clone(\"new-project-name\")\n```\n\nYou can manage your project's settings and configurations by exporting your\nproject's environment. You can also import previously exported data into\nthe same project or a different one.\n\n```python\n# Exports the current state of the project\nexport = descope_client.mgmt.project.export_project()\n\n# Import the previously exported data into the current project\ndescope_client.mgmt.project.import_project(export)\n```\n\n### Manage SSO Applications\n\nYou can create, update, delete or load sso applications:\n\n```python\n# Create OIDC SSO application\ndescope_client.mgmt.sso_application.create_oidc_application(\n    name=\"My First sso app\",\n\tlogin_page_url=\"http://dummy.com\",\n\tid=\"my-custom-id\", # This is optional.\n)\n\n# Create SAML SSO application\ndescope_client.mgmt.sso_application.create_saml_application(\n    name=\"My First sso app\",\n\tlogin_page_url=\"http://dummy.com\",\n\tid=\"my-custom-id\", # This is optional.\n\tuse_metadata_info=True,\n\tmetadata_url=\"http://dummy.com/metadata,\n\tdefault_relay_state=\"relayState\",\n\tforce_authentication=False,\n\tlogout_redirect_url=\"http://dummy.com/logout\",\n)\n\n# Update OIDC SSO application\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.sso_application.update_oidc_application(\n    id=\"my-custom-id\",\n    name=\"My First sso app\",\n    login_page_url=\"http://dummy.com\",\n)\n\n# Update SAML SSO application\n# Update will override all fields as is. Use carefully.\ndescope_client.mgmt.sso_application.update_saml_application(\n    id=\"my-custom-id\",\n    name=\"My First sso app\",\n    login_page_url=\"http://dummy.com\",\n\tuse_metadata_info=False,\n\tentity_id=\"ent1234\",\n\tacs_url=\"http://dummy.com/acs,\n\tcertificate=\"my cert\"\n)\n\n# SSO application deletion cannot be undone. Use carefully.\ndescope_client.mgmt.sso_application.delete(\"my-custom-id\")\n\n# Load SSO application by id\napp_resp = descope_client.mgmt.sso_application.load(\"my-custom-id\")\n\n# Load all SSO applications\napps_resp = descope_client.mgmt.sso_application.load_all()\napps = apps_resp[\"apps\"]\n    for app in apps:\n        # Do something\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, WhatsApp, and avoid the need of parsing the code and token from them.\n\n```Python\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.\ndescope_client.mgmt.user.create_test_user(\n    login_id=\"desmond@descope.com\",\n    email=\"desmond@descope.com\",\n    display_name=\"Desmond Copeland\",\n    user_tenants=[\n        AssociatedTenant(\"my-tenant-id\", [\"role-name1\"]),\n    ],\n)\n\n# Search all test users, optionally according to tenant and/or role filter\n# results can be paginated using the limit and page parameters\nusers_resp = descope_client.mgmt.user.search_all_test_users()\nusers = users_resp[\"users\"]\n    for user in users:\n        # Do something\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.\ndescope_client.mgmt.user.delete_all_test_users()\n\n# OTP code can be generated for test user, for example:\nresp = descope_client.mgmt.user.generate_otp_for_test_user(\n    DeliveryMethod.EMAIL, \"login-id\"\n)\ncode = resp[\"code\"]\n# Now you can verify the code is valid (using descope_client.*.verify for example)\n# login_options can be provided to set custom claims to the generated jwt.\n\n# Same as OTP, magic link can be generated for test user, for example:\nresp = descope_client.mgmt.user.generate_magic_link_for_test_user(\n    DeliveryMethod.EMAIL, \"login-id\", \"\"\n)\nlink = resp[\"link\"]\n\n# Enchanted link can be generated for test user, for example:\nresp = descope_client.mgmt.user.generate_enchanted_link_for_test_user(\n    \"login-id\", \"\"\n)\nlink = resp[\"link\"]\npending_ref = resp[\"pendingRef\"]\n```\n\n## API Rate Limits\n\nHandle API rate limits by comparing the exception to the APIRateLimitExceeded exception, which includes the RateLimitParameters map with the key \"Retry-After.\" This key indicates how many seconds until the next valid API call can take place.\n\n```python\ntry:\n    descope_client.magiclink.sign_up_or_in(\n        method=DeliveryMethod.EMAIL,\n        login_id=\"desmond@descope.com\",\n        uri=\"http://myapp.com/verify-magic-link\",\n    )\nexcept RateLimitException as e:\n    retry_after_seconds = e.rate_limit_parameters.get(API_RATE_LIMIT_RETRY_AFTER_HEADER)\n    # This variable indicates how many seconds until the next valid API call can take place.\n```\n\n## Code Samples\n\nYou can find various usage samples in the [samples folder](https://github.com/descope/python-sdk/blob/main/samples).\n\n## Run Locally\n\n### Prerequisites\n\n- Python 3.8.1 or higher\n- [Poetry](https://python-poetry.org) installed\n\n### Install dependencies\n\n```bash\npoetry install\n```\n\n### Run tests\n\nRunning all tests:\n\n```bash\npoetry run pytest tests\n```\n\nRunning all tests with coverage:\n\n```bash\npoetry run pytest --junitxml=/tmp/pytest.xml --cov-report=term-missing:skip-covered --cov=descope tests/ --cov-report=xml:/tmp/cov.xml\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 Python is licensed for use under the terms and conditions of the [MIT license Agreement](https://github.com/descope/python-sdk/blob/main/LICENSE).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Descope Python SDK",
    "version": "1.7.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/descope/python-sdk/issues",
        "Documentation": "https://docs.descope.com",
        "Homepage": "https://descope.com/",
        "Repository": "https://github.com/descope/python-sdk"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b7e816cfd8b27a7947d9511bd926e035f9486123025f5e6a0ed3d400d85f25ae",
                "md5": "f049a50ab08f66adbbb8a9ffe751888f",
                "sha256": "64f213f35af4e977237a7447def65e29f9c20bb5b0a57ba377206f9deac11b97"
            },
            "downloads": -1,
            "filename": "descope-1.7.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f049a50ab08f66adbbb8a9ffe751888f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8.1",
            "size": 73891,
            "upload_time": "2024-11-25T14:11:35",
            "upload_time_iso_8601": "2024-11-25T14:11:35.959098Z",
            "url": "https://files.pythonhosted.org/packages/b7/e8/16cfd8b27a7947d9511bd926e035f9486123025f5e6a0ed3d400d85f25ae/descope-1.7.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "43efbb915b8e458abccc2176202ab2f18b67b92c672a984a6e307544c6174ea4",
                "md5": "866b218ccfb1021c677fd33f5944d9b9",
                "sha256": "24841d01003577c1120df4e3211a6205e4e2f131994150c619db3746c375e2ae"
            },
            "downloads": -1,
            "filename": "descope-1.7.0.tar.gz",
            "has_sig": false,
            "md5_digest": "866b218ccfb1021c677fd33f5944d9b9",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8.1",
            "size": 68520,
            "upload_time": "2024-11-25T14:11:37",
            "upload_time_iso_8601": "2024-11-25T14:11:37.151974Z",
            "url": "https://files.pythonhosted.org/packages/43/ef/bb915b8e458abccc2176202ab2f18b67b92c672a984a6e307544c6174ea4/descope-1.7.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-25 14:11:37",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "descope",
    "github_project": "python-sdk",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "tox": true,
    "lcname": "descope"
}
        
Elapsed time: 0.46896s