Skip to content

Error Handling

Introduction

This section covers error handling in PSEngine. While not every module is discussed individually, the error handling concepts are consistent across all modules. The examples below illustrate how to recognize, interpret, and handle errors generated by PSEngine.

The first examples show errors as they appear without handling, followed by examples demonstrating proper error handling.

Examples

Warning

The following examples demonstrate how to use this module. Be sure to add appropriate error handling as needed; all possible errors for each method or function are listed in the API Reference page.

Additionally, you must configure the RF_TOKEN environment variable before getting started. For instructions, see Learn.

1: Handling missing or wrong token

In this4: example, we set the RF_TOKEN environment variable to an empty string before importing psengine. This is just a workaround to make the example reproducible, to show what happens if you run PSEngine without the variable configured.

1
2
3
4
5
6
7
8
import os

os.environ['RF_TOKEN'] = ''

from psengine.enrich import LookupMgr

mgr = LookupMgr()
mgr.lookup('8.8.8.8', 'ip')

The whole exception traceback is:

Traceback (most recent call last):
  File "/examples/error_handling/example_1.py", line 7, in <module>
    mgr = LookupMgr()
          ^^^^^^^^^^^
  File "/examples/.venv/lib/python3.12/site-packages/psengine/enrich/lookup_mgr.py", line 45, in __init__
    self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
                                                                   ^^^^^^^^^^
  File "/examples/.venv/lib/python3.12/site-packages/psengine/rf_client.py", line 84, in __init__
    raise ValueError('Missing Recorded Future API token.')
ValueError: Missing Recorded Future API token.

The ValueError at the bottom indicates that the API token is missing.

Another instance of ValueError regarding the token is a first level of validation that PSEngine performs when a token is passed. Recorded Future tokens have 32 alphanumeric characters, all lowercase, from a to f and from 0 to 9. If you pass a token with invalid syntax, PSEngine will error out.

1
2
3
4
from psengine.enrich import LookupMgr

value = 'moise'
mgr = LookupMgr(rf_token=value)

In this case the traceback will be:

Traceback (most recent call last):
  File "/examples/error_handling/example_3.py", line 3, in <module>
    mgr = LookupMgr(rf_token='moise')
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/examples/.venv/lib/python3.12/site-packages/psengine/enrich/lookup_mgr.py", line 45, in __init__
    self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/examples.venv/lib/python3.12/site-packages/psengine/rf_client.py", line 86, in __init__
    raise ValueError(
ValueError: Invalid Recorded Future API token: must match regex ^[a-f0-9]{32}$

Showing that the token provided does not conform to the properties described above.

2: Handling missing token permissions

In this example, we perform a lookup of a password using the IdentityMgr. We removed the Identity module from the token permissions.

1
2
3
4
from psengine.identity import IdentityMgr

mgr = IdentityMgr()
mgr.lookup_password(hash_prefix='abdcef', algorithm='ntlm')

After running it, the error we get is:

1
2
3
File "/examples/.venv/lib/python3.12/site-packages/psengine/helpers/helpers.py", line 86, in wrapped
    raise exception_to_raise(message=str(err)) from err
psengine.identity.errors.IdentityLookupError: 403 Client Error: Forbidden for url: https://api.recordedfuture.com/identity/password/lookup, Cause: User does not have access to this API

This is an IdentityLookupError, which is defined within the identity module. It shows an HTTP 403 error with the forbidden URL that was called. The fact that it's a 403 and the "Cause: User does not have access to this API" indicates that the token has not been configured properly.

3: Understanding ValidationError on wrong fields

Another common error you might see in PSEngine is the ValidationError. This can be generated from two locations:

  • A method call
  • A method return value

The example below shows case 1, where you supply an incorrect parameter. Case 2 is rarer and you cannot do anything to solve it, since it comes from an error during validation of what is sent by the Recorded Future API to PSEngine and can either mean an issue in the Recorded Future API or a bug in PSEngine. In this scenario, raise a support ticket to Recorded Future with the code sample you ran and the error.

The LookupMgr.lookup method expects as the entity_type parameter (the second one) a string that must be one of the entity types defined in PSEngine. In this example, we pass an invalid string: an ip address.

1
2
3
4
from psengine.enrich import LookupMgr

mgr = LookupMgr()
mgr.lookup('8.8.8.8', 'an ip address')

The ValidationError raised will be:

1
2
3
4
pydantic_core._pydantic_core.ValidationError: 1 validation error for LookupMgr.lookup
2
  Input should be 'company', 'company_by_domain', 'company/by_domain', 'domain', 'hash', 'ip', 'malware', 'url', 'vulnerability', 'Company', 'Organization', 'InternetDomainName', 'Hash', 'IpAddress', 'Malware', 'URL' or 'CyberVulnerability' [type=literal_error, input_value='an ip address', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error

It has a couple of parts to be aware of:

  • The first line tells you the method that raised the error: LookupMgr.lookup.
  • The 2 on the second line indicates the second parameter.
  • The third line shows all the possible literal values you are allowed to enter.

4: Proper error handling

When writing code with PSEngine, it is good practice to surround PSEngine calls with the related error handling. You can find which exception a method will raise in three ways:

  1. Via the API reference in the "Errors" section of each module.
  2. Via the API reference in the manager section; in each method there is a "Raises" table showing which exceptions will be raised.
  3. Via the method docstring. In any editor, you should be able to see a method's docstring if you hover over the method. The docstring shows the exception it will raise.

Generally speaking, the exception names are intuitive. For example, the ClassicAlertMgr.fetch method raises AlertFetchError; LookupMgr.lookup raises EnrichmentLookupError. The structure of the name is <Functionality><Method>Error.

Every manager (except for stix2) will have some scenarios where it raises a ValidationError.

Given the above, we can write a safe enrichment the following way:

from psengine.enrich import EnrichmentLookupError, LookupMgr
from pydantic import ValidationError

try:
    mgr = LookupMgr()
except ValueError as ve:
    print(
        'Possible token issue check environment variable.',
        ve,
    )
    exit(1)

entities = {
    '8.8.8.8': 'ip',
    'example.com': 'domain',
    1: 'example',
    'example2.com': 'domain',
}

for entity, entity_type in entities.items():
    try:
        enriched_data = mgr.lookup(entity, entity_type)
    except ValidationError:
        print(
            f'{entity} or {entity_type} type are wrong.',
        )
        continue
    except EnrichmentLookupError as ele:
        print(
            'Authentication issue, or some API issues.\n',
            ele,
        )
        exit(2)

    if enriched_data.is_enriched:
        print(enriched_data)

In this example, we first catch any potential ValueError from the LookupMgr initialization. If there is a missing token or a wrong token, print a message along with the ve exception and interrupt the script.

If everything is correct, we start the lookup of entities. In this example, we purposefully show the lookup of each entity instead of using lookup_bulk to make the example clearer.

The third entity that we need to enrich has two issues:

  1. The type of the key is incorrect; 1 should be a string.
  2. The entity_type value is incorrect; it should be one of the supported entity_type values.

This raises a ValidationError, but we decide to skip any entity that is not enrichable.

An error with the API or the response raises an EnrichmentLookupError, which we do not want to ignore; hence we print a message, the exception, and exit with error code 2. This error code is not required but is useful to understand at what point the script failed.

Lastly, if the entity has been enriched, we print the details. The output should be:

1
2
3
4
5
6
7
8
EnrichedIP: 8.8.8.8, Risk Score: 0, Last Seen: 2025-09-03 10:03:12
EnrichedDomain: example.com, Risk Score: 24, Last Seen: 2025-09-03 10:21:54


WARNING: The entity "1", or the entity_type "example" are wrong. I will ignore it.


EnrichedDomain: example2.com, Risk Score: 0, Last Seen: 2025-09-03 10:18:15

This way, the script does not crash if some scenarios can be handled more gracefully.