<jupyter_start><jupyter_text>Set up OAuth APIsRunning this script will create the APIs so we can sign JWTs and use the private_key_jwt authorization methods Requirements1. Create an APImetrics Project1. Get an API key with EDITOR permissions for the project, enter it below1. Run this workbook to create all Auth Settings, APIs and Workflows for Sign-in flow1. Set environment variables:    1. kid_cert_version - this is the numeric ID internal to APImetrics for your signing key    1. kid - this is your signing certs real key ID (as per JWKS)    1. client_id - this is your client_id     1. issuer - this is the issuer for your JWTs    1. redirect_uri - this is your redirect_uri for OAuth    NB: Until we publish the API call conditions API, those need to be added manually from the conditions tab<jupyter_code># Enter intended project's APImetrics API key here:
API_KEY = input("Enter your APImetrics API key for the intended project: ")
# Enter the .well-known URL for the auth service you want to use
WELL_KNOWN_URL = input('Enter .well-known/openid-configuration URL: ') or 'https://accounts.google.com/.well-known/openid-configuration'
import json
import requests
from urllib.parse import urlparse

def get_json(url):
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except Exception as ex:
        print(resp.content)
        raise ex
    return resp.json()

url_info = urlparse(WELL_KNOWN_URL)
DOMAIN, _, _ = url_info.netloc.partition(':') # removes the port 
print(f"Looking up details for {DOMAIN}")

try:
    WELL_KNOWN = get_json(WELL_KNOWN_URL)
except:
    WELL_KNOWN = json.loads(input("Enter your .well-known JSON here: "))

print(json.dumps(WELL_KNOWN, indent=2))

SSL_KEY_PATH = ""#"/Users/nick/Devel/cloud_storage/BankingCerts/Fractal/obwac.nosign.key" #"./local/TPP_OB_Transport.key"
SSL_CERT_PATH = ""#"/Users/nick/Devel/cloud_storage/BankingCerts/Fractal/obwac.pem" #"./local/TTP_OB_Transport.pem"
# Enter your intended scope for your tokens
scopes_in = input("Enter the scopes you wish to use (except openid, we'll add that automatically): ") or "accounts"
scopes = ['openid'] + [scope.strip() for scope in scopes_in.split(' ')]
SCOPES = " ".join(scopes)
for scope in scopes:
    if not scope in WELL_KNOWN['scopes_supported']:
        print(f"Scope {scope} is not supported according to .well-known")
print(f"Using scopes {SCOPES}")

# We now extract the values we need (or you can override it)
AUTHORIZATION_URL = WELL_KNOWN['authorization_endpoint']
ACCESS_TOKEN_URL = WELL_KNOWN['token_endpoint']

print("Auth Methods options:")
for auth_method in WELL_KNOWN['token_endpoint_auth_methods_supported']:
    print(auth_method)

AUTH_METHOD = input('Which auth method do you want? (tls_client_auth) ') or 'tls_client_auth'
assert AUTH_METHOD in WELL_KNOWN['token_endpoint_auth_methods_supported'], 'Your well-known does not claim that it is supported'

# Helper functions
import requests
import json
import urllib
from apimetrics_api import APImetricsAPI 
    
# An instance of the class that calls the APImetrics API
CLIENT = APImetricsAPI(API_KEY)

# [CLIENT.delete_token(o['id']) for o in CLIENT.tokens.values()]
# [CLIENT.delete_auth(o['id']) for o in CLIENT.auths.values()]
# [CLIENT.delete_call(o['id']) for o in CLIENT.calls.values()]
# [CLIENT.delete_workflow(o['id']) for o in CLIENT.workflows.values()]<jupyter_output><empty_output><jupyter_text>From here on out, you can pick and choose what you want to run. Create Auth Setting for APImetrics API<jupyter_code># First, create APImetrics API Auth Setting
tag = 'auth:apimetrics_api'
if tag not in CLIENT.auths_by_tag:
    setup = {
        "meta": {
            "domain": "client.apimetrics.io",
            "documentation": {
                "keys": "https://client.apimetrics.io/settings/api-key",
                "docs": "https://apimetrics.readme.io/v2/reference",
                "apps": "",
                "provider": "https://client.apimetrics.io/",
            },
            "name": "APImetrics API",
            "tags": [tag],
            "description": "API that allows you to call APImetrics' API.",
        },
        "settings": {"auth_type": "MANUAL"}
    }
    auth = CLIENT.create_auth(setup)
    print(f"Created Auth Setting {auth['meta']['name']} with id {auth['id']}")
else:
    print(f"Found Auth Setting {CLIENT.auths_by_tag[tag]}")

# Next, Create Token
if CLIENT.auths_by_tag[tag] not in CLIENT.tokens_by_auth:
    setup = {
        'meta': {
            'name': 'Project Access Token',
            'domain': 'client.apimetrics.io',
            'auth_id': CLIENT.auths_by_tag[tag]
        },
        'token': {
            'headers': [
              {
                'p_key': 'Authorization',
                'p_val': f'Bearer {API_KEY}',
              },
            ],
        }
    }
    token = CLIENT.create_token(setup)
    print(f"Created Auth Token {token['meta']['name']} with id {token['id']}")<jupyter_output><empty_output><jupyter_text>Create Auth for calling Bank APIs<jupyter_code># First create Auth Setting
tag = 'auth:bank_matls'
if tag not in CLIENT.auths_by_tag:

    ssl_key = None
    ssl_cert = None

    if SSL_KEY_PATH:
        with open(SSL_KEY_PATH) as stream:
            ssl_key = stream.read()

    if SSL_CERT_PATH:
        with open(SSL_CERT_PATH) as stream:
            ssl_cert = stream.read()
    
    setup = {
        "access": {
            "keys": False,
            "org_keys": False,
            "org_settings": True,
            "settings": False,
        },
        "keys": {},
        "meta": {
            "domain": DOMAIN,
            "documentation": {"keys": "", "docs": "", "apps": "", "provider": ""},
            "name": "Transport MATLS",
            "tags": [tag],
            "description": "Mutual Authenticated TLS for calls to bank APIs",
        },
        "settings": {
            "auth_type": "MANUAL",
            "ssl_key": ssl_key,
            "ssl_cert": ssl_cert,
        },
    }
    
    auth = CLIENT.create_auth(setup)
    print(f"Created Auth Setting {auth['meta']['name']} with id {auth['id']}")
    
# Second Create Token
if CLIENT.auths_by_tag['auth:bank_matls'] not in CLIENT.tokens_by_auth:
    setup = {
        'meta': {
            'name': 'Authenticated User Access Token',
            'domain': DOMAIN,
            'auth_id': CLIENT.auths_by_tag['auth:bank_matls']
        },
        'token': {}
    }
    token = CLIENT.create_token(setup)
    print(f"Created Auth Token {token['meta']['name']} with id {token['id']}")<jupyter_output><empty_output><jupyter_text>OAuth Assertions - Build JWT and Sign it<jupyter_code>def sign_empty_jwt(tag, name):
    auth_tag = 'auth:apimetrics_api'
    auth_id = CLIENT.auths_by_tag[auth_tag]
    token_id = CLIENT.tokens_by_auth[auth_id]
    
    body = {
        "header": {},
        "payload": {},
        "private_key": {
            "versionId": '{{kid_cert_version}}'
        }
    }
    body_str = json.dumps(body, indent=2)
    
    setup = {
        "meta": {
            "tags": ["api_type:create", "sector:devtools", tag],
            "name": f"Sign {name} JWT with KMS Cert",
            "workspace": "global",
        },
        "request": {
            "body": body_str,
            "parameters": [
                {"key": "kid", "value": "{{kid}}"},
                {"key": "alg", "value": "PS256"},
                {"key": "sub", "value": "{{client_id}}"},
                {"key": "aud", "value": "{{issuer}}"},
                {"key": "iss", "value": "{{client_id}}"},
                {"key": "jti", "value": "__GUID__"},
                {"key": "expiresIn", "value": "5m"},
            ],
            "url": "https://us-central1-viatests.cloudfunctions.net/jwt-kms-signer",
            "auth_id": auth_id,
            "headers": [
                {"key": "Accept", "value": "application/json"},
                {"key": "Content-Type", "value": "application/json"},
            ],
            "token_id": token_id,
            "method": "POST",
        },
    }
    return setup

if AUTH_METHOD == "private_key_jwt":
    # For client credentials flow
    tag = 'jwt:sign:client_credentials'
    if tag not in CLIENT.calls_by_tag:
        setup = sign_empty_jwt(tag, 'Client Credentials')
        call = CLIENT.create_call(setup)
        print(f"Created Call {call['meta']['name']} with id {call['id']}")

    # For authorization code flow
    tag = 'jwt:sign:code'
    if tag not in CLIENT.calls_by_tag:
        setup = sign_empty_jwt(tag, 'Auth Code')
        call = CLIENT.create_call(setup)
        print(f"Created Call {call['meta']['name']} with id {call['id']}")

    # For refresh token flow
    tag = 'jwt:sign:refresh_token'
    if tag not in CLIENT.calls_by_tag:
        setup = sign_empty_jwt(tag, 'Refresh Token')
        call = CLIENT.create_call(setup)
        print(f"Created Call {call['meta']['name']} with id {call['id']}")<jupyter_output><empty_output><jupyter_text>OAuth Authorize Claims - build JWT and sign it<jupyter_code># For user authentication
tag = 'jwt:sign:authorize'
if tag not in CLIENT.calls_by_tag:
    auth_tag = 'auth:apimetrics_api'
    auth_id = CLIENT.auths_by_tag[auth_tag]
    token_id = CLIENT.tokens_by_auth[auth_id]
    
    body = {
        "header": {},
        "payload": {
            "scope": SCOPES,
            "claims": {
                "id_token": {
                    "acr": {"value": "urn:openbanking:psd2:sca", "essential": True},
                    "openbanking_intent_id": {
                        "value": "%%CONSENT_ID%%",
                        "essential": True,
                    },
                },
                "userinfo": {
                    "openbanking_intent_id": {
                        "value": "%%CONSENT_ID%%",
                        "essential": True,
                    }
                },
            },
            "response_type": "code id_token",
            "redirect_uri": "{{redirect_uri}}",
            "state": "__TEST_RUN_RESULT_ID__",
            "nonce": "__TEST_RUN_RESULT_ID__",
            "client_id": "{{client_id}}",
        },
        "private_key": {
            "versionId": '{{kid_cert_version}}'
        }
    }
    
    body_str = json.dumps(body, indent=2)
    
    setup = {
        "meta": {
            "tags": ["api_type:create", "sector:devtools", tag],
            "name": "Sign User Auth JWT with KMS Cert",
            "workspace": "global",
        },
        "request": {
            "body": body_str,
            "parameters": [
                {"value": "{{kid}}", "key": "kid"},
                {"value": "PS256", "key": "alg"},
                {"value": "{{issuer}}", "key": "aud"},
                {"value": "{{client_id}}", "key": "iss"},
                {"value": "10m", "key": "expiresIn"},
                {"value": "%%GUID%%", "key": "jti"},
            ],
            "url": "https://us-central1-viatests.cloudfunctions.net/jwt-kms-signer",
            "auth_id": auth_id,
            "headers": [
                {"key": "Accept", "value": "application/json"},
                {"key": "Content-Type", "value": "application/json"},
            ],
            "token_id": token_id,
            "method": "POST",
        },
    }
    call = CLIENT.create_call(setup)
    print(f"Created Call {call['meta']['name']} with id {call['id']}")
    
    conditions = {
        "conditions": [
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "token", 
              "variable_name": "JWT_TOKEN", 
              "test_result_on_false": "CONTENT_ERROR", 
              "condition": "EXISTS"
            },
          ]
    }
    CLIENT.set_call_conditions(call['id'], conditions)<jupyter_output><empty_output><jupyter_text>OAuth - API calls Client Credentials flow<jupyter_code>tag = 'banks:oauth:client_credentials'
if tag not in CLIENT.calls_by_tag:
    
    assert 'auth:bank_matls' in CLIENT.auths_by_tag, 'Did not find a maTLS Auth Setting to use'
    
    headers = [{"key": "Content-Type", "value": "application/x-www-form-urlencoded"}]
    
    if AUTH_METHOD == 'client_secret_basic':
        headers.append({"key": "Authorization", "value": "Basic {{basic_auth}}"})
    
    if AUTH_METHOD == 'private_key_jwt':
        params = {
            "grant_type": "client_credentials",
            "scope": SCOPES,
            "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "client_assertion": "__JWT_TOKEN__",
        }
    else:
        params = {
            "grant_type": "client_credentials",
            "scope": SCOPES,
            "client_id": "{{client_id}}",
        }
    
        if AUTH_METHOD == 'client_secret_post':
            params["client_secret"] = "{{client_secret}}"
    
    
    params_str = urllib.parse.urlencode(params)
    
    setup = {
        "meta": {
            "description": "",
            "tags": ["api_type:create", "sector:financial", "ob_id:0", tag],
            "name": "OAuth: Access Token: client_credentials",
            "workspace": "global",
        },
        "request": {
            "body": params_str,
            "parameters": [],
            "url": ACCESS_TOKEN_URL,
            "auth_id": CLIENT.auths_by_tag['auth:bank_matls'],
            "headers": headers,
            "token_id": None,
            "method": "POST",
        },
    }
    call = CLIENT.create_call(setup)
    print(f"Created Call {call['meta']['name']} with id {call['id']}")
    
    conditions = {
        "conditions": [
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "access_token", 
              "variable_name": "ACCESS_TOKEN", 
              "test_result_on_false": "CONTENT_ERROR", 
              "condition": "EXISTS"
            }, 
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "expires_in", 
              "variable_name": "EXPIRES_IN", 
              "test_result_on_false": None, 
              "condition": "EXISTS"
            }
          ]
    }
    CLIENT.set_call_conditions(call['id'], conditions)<jupyter_output><empty_output><jupyter_text>Auth Code flow - go to user authorization page<jupyter_code>tag = 'banks:oauth:authorize'
if tag not in CLIENT.calls_by_tag:
    
    headers = [
        {"value": "*/*", "key": "Accept"},
        {"value": "text/plain", "key": "Content-Type"},
    ]
    
    params = {
        "response_type": "code id_token",
        "scope": SCOPES,
        "client_id": "{{client_id}}",
        "state": "__TEST_RUN_RESULT_ID__",
        "nonce": "__TEST_RUN_RESULT_ID__",
        "redirect_uri": "{{redirect_uri}}",
        "request": "__JWT_TOKEN__",
    }
        
    setup = {
        "meta": {
            "description": "",
            "tags": ["api_type:create", "sector:financial", "ob_id:0", tag, "apimetrics:browser"],
            "name": "OAuth: Authorize",
            "workspace": "global",
        },
        "request": {
            "body": None,
            "url": AUTHORIZATION_URL,
            "parameters": params,
            "auth_id": CLIENT.auths_by_tag['auth:bank_matls'],
            "headers": headers,
            "token_id": None,
            "method": "POST",
        },
    }
    call = CLIENT.create_call(setup)
    print(f"Created Call {call['meta']['name']} with id {call['id']}")<jupyter_output><empty_output><jupyter_text>Auth Code flow - get access tokenThis is the call that swaps the temporary code from the user sign in redirect URL for an access token<jupyter_code># For auth_code flow
tag = 'banks:oauth:code'
if tag not in CLIENT.calls_by_tag:
    assert 'auth:bank_matls' in CLIENT.auths_by_tag, 'Did not find a maTLS Auth Setting to use'
    
    headers = [{"key": "Content-Type", "value": "application/x-www-form-urlencoded"}]

    if AUTH_METHOD == 'private_key_jwt':
        params = {
            "grant_type": "authorization_code",
            "code": "__CODE__",
            "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "client_assertion": "__JWT_TOKEN__",
        }
    else: 
        params = {
            "grant_type": "authorization_code",
            "code": "__CODE__",
            "client_id": "{{ client_id }}",
            "redirect_uri": "{{ redirect_uri }}",
        }
        if AUTH_METHOD == 'client_secret_post':
            params['client_secret'] = "{{ client_secret }}"

    params_str = urllib.parse.urlencode(params)

    setup = {
        "meta": {
            "description": "",
            "tags": ["api_type:create", "sector:financial", "ob_id:0", tag],
            "name": "OAuth: Access Token: authorization_code",
            "workspace": "global",
        },
        "request": {
            "body": params_str,
            "parameters": [],
            "url": ACCESS_TOKEN_URL,
            "auth_id": CLIENT.auths_by_tag['auth:bank_matls'],
            "headers": headers,
            "token_id": None,
            "method": "POST",
        },
    }
    call = CLIENT.create_call(setup)
    print(f"Created Call {call['meta']['name']} with id {call['id']}")
    
    conditions = {
        "conditions": [
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "access_token", 
              "variable_name": "ACCESS_TOKEN", 
              "test_result_on_false": "CONTENT_ERROR", 
              "condition": "EXISTS"
            }, 
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "expires_in", 
              "variable_name": "EXPIRES_IN", 
              "test_result_on_false": None, 
              "condition": "EXISTS"
            },
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "refresh_token", 
              "variable_name": "REFRESH_TOKEN", 
              "test_result_on_false": None, 
              "condition": "EXISTS"
            }, 
          ]
    }
    CLIENT.set_call_conditions(call['id'], conditions)<jupyter_output><empty_output><jupyter_text>Refresh token flow<jupyter_code># For auth_code flow
tag = 'banks:oauth:refresh_token'
if tag not in CLIENT.calls_by_tag:
    assert 'auth:bank_matls' in CLIENT.auths_by_tag, 'Did not find a maTLS Auth Setting to use'
    
    headers = [{"key": "Content-Type", "value": "application/x-www-form-urlencoded"}]

    if AUTH_METHOD == 'private_key_jwt':
        params = {
            "grant_type": "refresh_token",
            "refresh_token": "__REFRESH_TOKEN__",
            "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "client_assertion": "__JWT_TOKEN__",
        }
    else: 
        params = {
            "grant_type": "refresh_token",
            "refresh_token": "__REFRESH_TOKEN__",
            "client_id": "{{ client_id }}",
        }
        if AUTH_METHOD == 'client_secret_post':
            params['client_secret'] = "{{ client_secret }}"

    params_str = urllib.parse.urlencode(params)

    setup = {
        "meta": {
            "description": "",
            "tags": ["api_type:create", "sector:financial", "ob_id:0", tag],
            "name": "OAuth: Access Token: refresh_token",
            "workspace": "global",
        },
        "request": {
            "body": params_str,
            "parameters": [],
            "url": ACCESS_TOKEN_URL,
            "auth_id": CLIENT.auths_by_tag['auth:bank_matls'],
            "headers": headers,
            "token_id": None,
            "method": "POST",
        },
    }
    call = CLIENT.create_call(setup)
    print(f"Created Call {call['meta']['name']} with id {call['id']}")
    
    conditions = {
        "conditions": [
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "access_token", 
              "variable_name": "ACCESS_TOKEN", 
              "test_result_on_false": "CONTENT_ERROR", 
              "condition": "EXISTS"
            }, 
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "expires_in", 
              "variable_name": "EXPIRES_IN", 
              "test_result_on_false": None, 
              "condition": "EXISTS"
            },
            {
              "source": "RESPONSE_BODY", 
              "test_result_on_true": None, 
              "val": "", 
              "variable_path": "refresh_token", 
              "variable_name": "REFRESH_TOKEN", 
              "test_result_on_false": None, 
              "condition": "EXISTS"
            }, 
          ]
    }
    CLIENT.set_call_conditions(call['id'], conditions)
CLIENT.set_env_variable("global", "issuer", ACCESS_TOKEN_URL)
client_id = input("Enter your client_id: ")
CLIENT.set_env_variable("global", "client_id", client_id)
if AUTH_METHOD in ['client_secret_post', 'client_secret_basic']:
    client_secret = input("Enter your client_secret: ")
    CLIENT.set_env_variable("global", "client_secret", client_secret)
    
    if AUTH_METHOD == "client_secret_basic":
        import base64
        user_pass = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
        CLIENT.set_env_variable("global", "basic_auth", user_pass)<jupyter_output><empty_output><jupyter_text>Create Consent APIs to useGo to [OBIE - Account Consent APIs.ipynb](./OBIE%20-%20Account%20Consent%20APIs.ipynb) to create the consent APIs<jupyter_code># An instance of the class that calls the APImetrics API
CLIENT = APImetricsAPI(API_KEY)<jupyter_output><empty_output><jupyter_text>Create Workflows to for User sign-in (authorize_code flow)<jupyter_code># The user sign-in flow step 1
tag = 'banks:oauth:flow:authorize_code:1'
if tag not in CLIENT.workflows_by_tag:
            
    # This is the API that generates the claims you're going to use in the Auth
    claims_tag = 'banks:3.1:account-access-consents:create_max'
    
    call_tags = [
        'banks:oauth:client_credentials',
        claims_tag,
        'jwt:sign:authorize',
        'banks:oauth:authorize',
    ]
    for t in call_tags:
        assert t in CLIENT.calls_by_tag, f"API {t} does not exist"
    
    setup = {
      "meta": {
        "name": '[Manual] OAuth: Sign in as User (1/2)', 
        "workspace": "global", 
        "tags": [tag], 
      }, 
      "workflow": {
        "handle_cookies": False,
        "stop_on_failure": True,
        "call_ids": [CLIENT.calls_by_tag[t] for t in call_tags]
      }
    }
    call = CLIENT.create_workflow(setup)
    print(f"Created Workflow {call['meta']['name']} with id {call['id']}")

# The user sign-in flow step 2
tag = 'banks:oauth:flow:authorize_code:2'
if tag not in CLIENT.workflows_by_tag:
            
    call_tags = [
        'banks:oauth:code',
    ]
    for t in call_tags:
        assert t in CLIENT.calls_by_tag, f"API {t} does not exist"
    
    setup = {
      "meta": {
        "name": "[Manual] OAuth: Sign in as User (2/2)", 
        "workspace": "global", 
        "tags": [tag], 
      }, 
      "workflow": {
        "handle_cookies": False,
        "stop_on_failure": True,
        "call_ids": [CLIENT.calls_by_tag[t] for t in call_tags]
      }
    }
    call = CLIENT.create_workflow(setup)
    print(f"Created Workflow {call['meta']['name']} with id {call['id']}")<jupyter_output><empty_output><jupyter_text>Create Refresh token workflow<jupyter_code># A workflow to refresh our access token
tag = 'banks:oauth:flow:refresh_token'
if tag not in CLIENT.workflows_by_tag:
    call_tags = [
        'banks:oauth:refresh_token',
    ]
    for t in call_tags:
        assert t in CLIENT.calls_by_tag, f"API {t} does not exist"

    setup = {
      "meta": {
        "name": "OAuth: Refresh token", 
        "workspace": "global", 
        "tags": [tag], 
      }, 
      "workflow": {
        "handle_cookies": False,
        "stop_on_failure": True,
        "call_ids": [CLIENT.calls_by_tag[t] for t in call_tags]
      }
    }
    workflow = CLIENT.create_workflow(setup)
    print(f"Created Workflow {workflow['meta']['name']} with id {workflow['id']}")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14239863492321442, 'fraction_numerical': 0.0021532461200942554, 'mean_word_length': 2.273271276595745, 'pattern_counts': {'":': 302, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '107720', 'license': 'Apache-2.0', 'n_tokens_mistral': 8051, 'n_tokens_neox': 7821, 'n_words': 1660, 'path': 'OAuth APIs.ipynb', 'repo_name': 'APImetrics/openid-scripts', 'size': '24614'}