Exception Hierarchy¶
The Singer SDK defines a structured exception hierarchy rooted at SingerSDKError.
Every exception raised by the SDK is a subclass of this root, so you can always catch
all SDK-level errors with a single except SingerSDKError clause.
Full hierarchy¶
SingerSDKError
├── ConfigurationError
│ └── ConfigValidationError
├── DiscoveryError
│ └── InvalidReplicationKeyException
├── MappingError
│ ├── ConformedNameClashException
│ ├── MapExpressionError
│ └── StreamMapConfigError
├── SyncError
│ ├── FatalSyncError
│ │ ├── FatalAPIError
│ │ ├── InvalidStreamSortException
│ │ ├── MissingKeyPropertiesError
│ │ ├── RecordsWithoutSchemaException
│ │ ├── TapStreamConnectionFailure
│ │ └── TooManyRecordsException
│ ├── RetriableSyncError
│ │ └── RetriableAPIError
│ ├── IgnorableSyncError
│ │ ├── IgnorableAPIError
│ │ └── InvalidRecord
│ └── DataError
│ └── InvalidJSONSchema
└── SyncLifecycleSignal
├── RequestedAbortException
│ └── MaxRecordsLimitException
└── AbortedSyncExceptionBase (abstract)
├── AbortedSyncFailedException
└── AbortedSyncPausedException
All of these names can be imported from singer_sdk.exceptions.
Exception groups¶
ConfigurationError¶
Raised during plugin startup when the provided configuration is invalid or missing required values.
Class |
When raised |
|---|---|
|
The config dict fails JSON Schema validation. Carries |
Tap developers — you do not normally raise these directly. The SDK raises
ConfigValidationError automatically when Tap.config_jsonschema validation fails.
DiscoveryError¶
Raised during catalog discovery (the --discover run) before any records are synced.
Class |
When raised |
|---|---|
|
Generic discovery failure; subclass for specific cases. |
|
The stream’s |
MappingError¶
Raised when a stream map configuration or expression is invalid.
Class |
When raised |
|---|---|
|
Generic mapping failure; subclass for specific cases. |
|
A Jinja/eval expression in a map definition could not be evaluated. |
|
The stream map configuration itself is structurally invalid. |
|
Two or more columns conform to the same output name after name normalization. |
SyncError — base for runtime errors¶
All errors that occur during data extraction or loading inherit from SyncError.
They are subdivided by recovery strategy.
Fatal (FatalSyncError)¶
The SDK should abort the sync immediately and exit with a non-zero code.
Class |
When raised |
|---|---|
|
An HTTP/API error that cannot be retried (e.g. 403 Forbidden, 400 Bad Request). |
|
The stream connection was lost and cannot be recovered. |
|
The query returned more records than |
|
A target received |
|
A received record is missing one or more declared key properties. |
|
Records arrived out of order, violating the stream’s sort invariant. |
Raising in tap code:
from singer_sdk.exceptions import FatalAPIError
def validate_response(self, response: requests.Response) -> None:
if response.status_code == 403:
msg = f"Access denied: {response.url}"
raise FatalAPIError(msg)
super().validate_response(response)
Retriable (RetriableSyncError)¶
The SDK should retry the request with exponential backoff. The sync aborts only if all retry attempts are exhausted.
Class |
When raised |
|---|---|
|
A transient HTTP/API error that is safe to retry (e.g. 429 Too Many Requests, 503 Service Unavailable). Carries an optional |
Raising in tap code:
from singer_sdk.exceptions import RetriableAPIError
def validate_response(self, response: requests.Response) -> None:
if response.status_code == 429:
msg = f"Rate limited: {response.url}"
raise RetriableAPIError(msg, response=response)
super().validate_response(response)
Ignorable (IgnorableSyncError)¶
The SDK should log a warning, skip the current record or page, and continue. The sync completes normally (exit code 0).
Class |
When raised |
|---|---|
|
An HTTP/API error for an expected non-fatal response (e.g. 404 on a per-record enrichment endpoint). No retry is attempted. |
|
A record fails schema validation. Carries |
Raising in tap code:
from singer_sdk.exceptions import IgnorableAPIError
def validate_response(self, response: requests.Response) -> None:
if response.status_code == 404:
msg = f"Resource not found: {response.url}"
raise IgnorableAPIError(msg)
super().validate_response(response)
Data quality (DataError)¶
Raised when a data quality or schema violation is detected. The SDK logs a warning and continues; severity is configurable.
Class |
When raised |
|---|---|
|
A stream’s declared JSON Schema is structurally invalid. |
SyncLifecycleSignal¶
These are control-flow signals, not errors. They are raised to manage graceful
shutdown. The SDK catches them, emits a final STATE message where possible, and
exits cleanly.
Class |
When raised |
|---|---|
|
A graceful abort was requested (e.g. SIGTERM). |
|
The |
|
Abstract base; use one of the concrete subclasses below. |
|
The sync stopped in a non-resumable state. |
|
The sync stopped cleanly and emitted a resumable state artifact. |
Catching SDK exceptions broadly¶
from singer_sdk.exceptions import (
SingerSDKError,
FatalSyncError,
RetriableSyncError,
IgnorableSyncError,
)
try:
stream.sync()
except IgnorableSyncError as e:
logger.warning("Skipping: %s", e)
except RetriableSyncError as e:
# handled automatically by the SDK's backoff decorator
raise
except FatalSyncError:
raise
except SingerSDKError:
# catch-all for any other SDK error
raise
Choosing the right exception¶
Situation |
Raise |
|---|---|
HTTP error, must abort |
|
HTTP error, safe to retry |
|
HTTP error, expected / skip silently |
|
Config value is wrong |
|
Replication key not in schema |
|
Map expression fails at runtime |
|
Record missing primary key |
|
Record fails schema validation |
|