Error Reference
Complete reference for all API error codes, their meanings, and how to handle them.
Error format
All API errors follow a consistent JSON structure:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly address limit reached. Top up credits or upgrade your plan.",
"upgrade_url": "https://oneshotemail.com/pricing"
}
}
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code. Use this for programmatic handling. |
message | string | Human-readable description. |
upgrade_url | string | Present only on QUOTA_EXCEEDED errors. Links to the pricing page. |
Error codes by HTTP status
400 Bad Request
VALIDATION_ERROR
The request body is malformed or contains invalid values.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "ttl_seconds must be between 60 and 86400."
}
}
Common causes:
ttl_secondsis out of range for your plan.- Missing required fields in the
sendobject. - Invalid email address format in
send.to. - Attachment
content_base64is not valid base64.
How to handle: Fix the request. This is not retryable.
401 Unauthorized
UNAUTHORIZED
The API key is missing, invalid, or has been revoked.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key."
}
}
Common causes:
- No
Authorizationheader. - Typo in the API key.
- Using a revoked key (regenerated from account settings).
How to handle: Check your API key configuration. This is not retryable.
SDK exceptions:
- Python:
UnauthorizedError - Go:
*APIErrorwithStatusCode == 401 - Ruby:
OneShot::UnauthorizedError - JavaScript:
UnauthorizedError - Java:
OneShotExceptionwithstatusCode == 401
402 Payment Required
QUOTA_EXCEEDED
Your monthly allocation is exhausted and you have no remaining credits.
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly address limit reached. Top up credits or upgrade your plan.",
"upgrade_url": "https://oneshotemail.com/pricing"
}
}
How to handle:
- Purchase credit packs via
POST /account/creditsor the CLI (oneshot credits buy). - Upgrade your plan.
- Wait for the billing cycle to reset (check
billing_cycle_endsin your account info).
SDK exceptions:
- Python:
QuotaExceededError(hasupgrade_urlattribute) - Go:
*APIErrorwithStatusCode == 402andUpgradeURLfield - Ruby:
OneShot::QuotaExceededError - JavaScript:
QuotaExceededError(hasupgradeUrlproperty) - Java:
OneShotExceptionwithstatusCode == 402
404 Not Found
NOT_FOUND
The requested resource does not exist.
{
"error": {
"code": "NOT_FOUND",
"message": "Address not found."
}
}
Contexts where this occurs:
GET /addresses/{id}— The address ID does not exist.GET /addresses/{id}/email— The address exists but no email has arrived yet (status iswaiting).GET /addresses/{id}/email/attachments/{index}— The attachment index is out of bounds.
How to handle:
- For address lookups: verify the address ID is correct.
- For email retrieval: the email has not arrived yet. Use
wait_for_email()instead of polling manually.
SDK exceptions:
- Python:
NotFoundError - Go:
*APIErrorwithStatusCode == 404 - Ruby:
OneShot::NotFoundError - JavaScript:
NotFoundError - Java:
OneShotExceptionwithstatusCode == 404
410 Gone
EXPIRED
The address has expired and all associated data has been permanently deleted.
{
"error": {
"code": "EXPIRED",
"message": "This address has expired and been deleted."
}
}
How to handle: The data is gone. Create a new address and retry the operation that triggers the email.
SDK exceptions:
- Python:
ExpiredError - Go:
*APIErrorwithStatusCode == 410 - Ruby:
OneShot::ExpiredError - JavaScript:
ExpiredError - Java:
OneShotExceptionwithstatusCode == 410
422 Unprocessable Entity
VALIDATION_ERROR
Semantically invalid request (the JSON is well-formed but the values are invalid).
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email size exceeds your plan limit of 5 MB."
}
}
Common causes:
- Email or attachment exceeds plan size limits.
- TTL exceeds plan maximum.
How to handle: Adjust the request to comply with your plan limits, or upgrade.
429 Too Many Requests
RATE_LIMITED
You have exceeded the rate limit for your plan.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 2 seconds."
}
}
Response headers:
| Header | Description |
|---|---|
Retry-After | Seconds to wait before retrying. |
How to handle: Wait for the duration specified in Retry-After, then retry. Use exponential backoff with jitter if you are making concurrent requests.
SDK exceptions:
- Python:
RateLimitedError(hasretry_afterattribute) - Go:
*APIErrorwithStatusCode == 429 - Ruby:
OneShot::RateLimitedError - JavaScript:
RateLimitedError - Java:
OneShotExceptionwithstatusCode == 429
Retryable vs non-retryable errors
| Code | Retryable | Strategy |
|---|---|---|
VALIDATION_ERROR | No | Fix the request. |
UNAUTHORIZED | No | Fix the API key. |
QUOTA_EXCEEDED | No | Buy credits, upgrade, or wait for cycle reset. |
NOT_FOUND | Maybe | If waiting for email, poll again. Otherwise, not retryable. |
EXPIRED | No | Create a new address. |
RATE_LIMITED | Yes | Wait Retry-After seconds, then retry. |
| 5xx errors | Yes | Retry with exponential backoff (max 3 attempts). |
SDK error handling examples
Python
import oneshot
from oneshot.exceptions import (
NotFoundError,
ExpiredError,
QuotaExceededError,
RateLimitedError,
WaitTimeoutError,
)
try:
email = oneshot.wait_for_email(addr.id, timeout=30)
except WaitTimeoutError:
print("Email did not arrive in time. Check your app's email delivery.")
except ExpiredError:
print("Address expired before the email arrived. Increase TTL.")
except QuotaExceededError as e:
print(f"Out of quota. Upgrade at: {e.upgrade_url}")
except RateLimitedError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds.")
Go
email, err := client.WaitForEmail(ctx, addr.ID, &oneshot.WaitOptions{
Timeout: 30 * time.Second,
})
if err != nil {
var apiErr *oneshot.APIError
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 402:
log.Fatalf("Quota exceeded: %s", apiErr.Message)
case 410:
log.Fatalf("Address expired: %s", apiErr.Message)
case 429:
log.Printf("Rate limited, retrying...")
}
}
log.Fatal(err)
}
JavaScript
import { OneShotClient, QuotaExceededError, RateLimitedError } from "oneshot-mail";
try {
const email = await client.waitForEmail(addr.id, { timeout: 30000 });
} catch (err) {
if (err instanceof QuotaExceededError) {
console.error(`Quota exceeded. Upgrade: ${err.upgradeUrl}`);
} else if (err instanceof RateLimitedError) {
console.log("Rate limited, retrying...");
} else {
throw err;
}
}
Ruby
begin
email = client.wait_for_email(addr["id"], timeout: 30)
rescue OneShot::QuotaExceededError
puts "Quota exceeded. Please upgrade your plan."
rescue OneShot::TimeoutError
puts "Email did not arrive within 30 seconds."
rescue OneShot::ExpiredError
puts "Address expired before receiving an email."
end
Java
try {
Email email = client.waitForEmail(addr.id, Duration.ofSeconds(30));
} catch (OneShotException e) {
switch (e.getStatusCode()) {
case 402 -> System.err.println("Quota exceeded: " + e.getMessage());
case 410 -> System.err.println("Address expired: " + e.getMessage());
case 429 -> System.out.println("Rate limited, retrying...");
default -> throw e;
}
}