Error Handling¶
Every exception the SDK defines derives from PineconeError, so except PineconeError
is the one blanket catch, and it is the right catch for anything that reaches the
network.
It is not the catch for calling the SDK wrongly. A misspelled or unsupported keyword argument fails the call before anything is sent, with the argument named:
TypeError: Index() got unexpected keyword arguments: ['retry_config']
Depending on the method, that error is a built-in TypeError, a PineconeTypeError,
or, for the assistants methods that accept 9.x parameter aliases, a
PineconeValueError. These are programming mistakes that fail on the first call, so
catch them in your tests, not in an except block.
Every class below is importable from the package root or from pinecone.errors;
the two paths reach the same class.
from pinecone import NotFoundError
from pinecone.errors import NotFoundError # same class
Exception Hierarchy¶
PineconeError
├── ApiError # the server answered with an HTTP error status
│ ├── UnauthorizedError # 401
│ ├── PaymentRequiredError # 402
│ ├── ForbiddenError # 403
│ ├── NotFoundError # 404
│ ├── ConflictError # 409
│ ├── FailedPreconditionError # 412
│ ├── PayloadTooLargeError # 413
│ ├── RateLimitError # 429
│ └── ServiceError # 5xx
├── PineconeValueError # also a built-in ValueError
├── PineconeTypeError # also a built-in TypeError
├── PineconeConnectionError # the request never got an answer
├── PineconeTimeoutError # also a built-in TimeoutError
├── ResponseParsingError # the answer arrived and could not be decoded
├── IndexInitFailedError # a polled index entered InitializationFailed
└── IndexTerminatedError # a polled index entered Terminating or Disabled
Three of these multiply inherit from a Python built-in, which changes what your existing handlers catch:
Class |
Also inherits |
Consequence |
|---|---|---|
|
|
An existing |
|
|
An existing |
|
|
An existing |
The inheritance runs one way only. except PineconeError does not catch a plain
ValueError or TimeoutError raised by something else in your stack.
A status the SDK has no subclass for, such as the 400 the server returns for a
request it rejects as malformed, raises ApiError itself. Catch ApiError if you want
every server-side failure; catch a subclass to single one out.
Full API reference, including each class’s attributes: Exceptions.
Who Raises What¶
The SDK runs a few cheap argument checks before sending a request. The server
validates everything else and reports a rejection as an ApiError:
Raised by |
Class |
Cause |
|---|---|---|
The SDK, before the request |
|
An argument’s value is unusable, such as an empty name or two mutually exclusive arguments passed together |
The SDK, before the request |
|
An argument has the wrong type, such as non-dict metadata or a request body that cannot be JSON-encoded |
The transport |
|
DNS failure, connection refused, TLS failure, or a reset mid-stream: the request never got an answer |
The transport or a readiness wait |
|
A request deadline elapsed, or a call that waits for an index or assistant to become ready or be deleted ran past its |
The server |
|
The request reached Pinecone and came back with an error status |
The SDK, after the response |
|
The response arrived but did not decode into the expected shape |
PineconeConnectionError and PineconeTimeoutError can come out of any method
that talks to Pinecone. No method’s own documentation repeats them, because the
answer is the same everywhere: the call did not complete, and whether the write
landed is unknown.
Catching Specific Errors¶
from pinecone import Pinecone
from pinecone.errors import (
NotFoundError,
ConflictError,
UnauthorizedError,
ForbiddenError,
PaymentRequiredError,
FailedPreconditionError,
PayloadTooLargeError,
RateLimitError,
ServiceError,
PineconeConnectionError,
PineconeTimeoutError,
)
pc = Pinecone()
try:
pc.indexes.describe("nonexistent-index")
except NotFoundError:
print("Index does not exist")
except ConflictError:
print("Operation conflicts with current state")
except UnauthorizedError:
print("Invalid or missing API key")
except ForbiddenError:
print("API key lacks permission for this operation")
except PaymentRequiredError as exc:
print(f"Billing blocks this operation: {exc.message}")
except FailedPreconditionError as exc:
print(f"Resource is not in a state that permits this: {exc.message}")
except PayloadTooLargeError:
print("Request body over the size cap; send fewer items per request")
except RateLimitError:
print("Rate limited; backing off before retrying")
except ServiceError as exc:
print(f"Server error {exc.status_code}: {exc.message}")
except PineconeConnectionError:
print("Network error; check your connection")
except PineconeTimeoutError:
print("Request timed out")
Order matters: an except ApiError placed before the status-specific clauses catches
those statuses first, so the specific clauses never run.
ApiError Attributes¶
ApiError and every subclass carry structured context:
Attribute |
Type |
Description |
|---|---|---|
|
|
The HTTP status the server returned |
|
|
The server’s own error message, already unwrapped from the response body |
|
|
The parsed JSON response body, or |
|
|
HTTP reason phrase |
|
|
Response headers |
|
|
The server’s machine-readable error code, when the body carries one |
|
|
Request ID, for correlating with Pinecone support |
str(exc) renders the ones that matter: [404 NOT_FOUND] message (request_id: ...).
from pinecone.errors import ApiError
try:
pc.indexes.describe("my-index")
except ApiError as exc:
print(exc.status_code)
print(exc.message)
print(exc.request_id)
You do not need to read exc.body["error"]["message"] yourself; exc.message already
holds it. Reach for body only for fields the SDK does not lift
out.
RateLimitError adds one attribute of its own: retry_after, the response’s
Retry-After header as a number of seconds. It is None when the header was
absent, negative, or an HTTP-date, which the SDK does not parse.
Retry It, or Give Up¶
Whether an exception is worth a second attempt is a property of the class, not of the call site:
Class |
Retry by hand? |
|---|---|
|
Yes, but the write may already have landed |
|
Yes, after |
|
Yes |
|
Yes, once you satisfy the precondition; |
|
Only after shrinking the request; a bare retry answers 413 again |
|
Only after resolving the conflict; a bare retry answers 409 again |
|
No, with one exception: see When 404 does not mean “not found” |
|
No. Fix the key or its permissions |
|
No. An organization owner has to resolve the billing state |
|
No. The request was never sent; fix the argument |
|
No |
The SDK retries connection errors, timeouts, 429 and most 5xx responses several times
before raising, so reaching one of them means those retries were spent and an
immediate re-call is unlikely to help. Retries and Resilience covers what the SDK
tried and how long it waited.
If you wrap a control-plane delete such as pc.indexes.delete() in your own retry
loop, treat NotFoundError as success: a retried delete answers 404 once the first
attempt has removed the resource.
ConflictError when creating an index¶
If you call pc.indexes.create() and an index with that name already exists, the
server answers 409 and the SDK raises ConflictError. The idiomatic fix is to
guard the create call with pc.indexes.exists():
from pinecone import Pinecone
pc = Pinecone()
if not pc.indexes.exists("my-index"):
pc.indexes.create(
name="my-index",
schema={"fields": {"your_vector": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
)
exists() returns True if an index with that name is present, False otherwise.
When several callers can race between the check and the create, catch ConflictError
and treat it as a no-op:
from pinecone import Pinecone
from pinecone.errors import ConflictError
pc = Pinecone()
try:
pc.indexes.create(
name="my-index",
schema={"fields": {"your_vector": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
)
except ConflictError:
pass # index already exists, nothing to do
When 404 does not mean “not found”¶
pc.restore_jobs.describe() can answer 404 for a transient server-side failure, not
only for a job that does not exist, so NotFoundError from that call means “could not
produce this job”. Retry before you give up on the job, delete local state, or report
the restore as gone. Do not match on the message text either: a job whose target index
was deleted also answers 404, with a different message.
from pinecone.errors import NotFoundError
try:
job = pc.restore_jobs.describe(job_id="rj-abc123")
except NotFoundError:
# Possibly transient. Retry before concluding the job is gone.
...
Retries¶
The SDK retries transient failures automatically, on every HTTP method, before raising anything. Bulk operations also lower their concurrency when a host pushes back.
What is retried, how long the backoff waits, what is configurable, and what to do in a multi-process fan-out: Retries and Resilience.
Timeouts¶
The default request timeout is 30 seconds. Pass timeout to the Pinecone
constructor to change the client-wide default:
pc = Pinecone(timeout=10.0)
Many methods also accept a per-request timeout that overrides the client default
for that call, and the bulk methods accept a total_timeout that bounds the whole
operation. timeout is the deadline for a single HTTP attempt and each retry gets a
fresh one, so a call can outlive its timeout several times over. Retries and Resilience covers total_timeout.
A timeout that elapses raises PineconeTimeoutError, which also inherits from
Python’s built-in TimeoutError:
except TimeoutError:
# Catches PineconeTimeoutError as well
...
PineconeTimeoutError carries a response attribute, which is None for an
ordinary request timeout. On the bulk paths that raise instead of returning,
upsert_from_dataframe(on_error="raise") and the gRPC upsert, it holds the partial
result, and response.failed_items tells you what to re-send.