Official Go SDK for the XposedOrNot API
Check if your email or password has been exposed in data breaches
Note: This SDK uses the free public API from XposedOrNot.com - a free service to check if your email has been compromised in data breaches. A Plus API with enhanced features is available with an API key. Visit the XposedOrNot website to learn more.
- Features
- Installation
- Requirements
- Quick Start
- API Reference
- Error Handling
- Rate Limits
- Configuration Options
- Contributing
- License
- Links
- Simple API - Idiomatic Go client with functional options pattern
- Context Support - All methods accept
context.Contextfor cancellation and timeouts - Free and Plus APIs - Use the free API or unlock detailed results with an API key
- Detailed Analytics - Get breach details, risk scores, metrics, and paste exposures
- Error Handling - Typed error values compatible with
errors.As - Password Privacy - Passwords are hashed locally; only a short prefix is sent (k-anonymity)
- Rate Limiting - Built-in client-side rate limiting with automatic retry on 429 responses
- Secure - HTTPS enforced on all base URLs by default
go get github.com/XposedOrNot/XposedOrNot-Go- Go 1.21 or higher
package main
import (
"context"
"fmt"
"log"
xon "github.com/XposedOrNot/XposedOrNot-Go"
)
func main() {
client, err := xon.NewClient()
if err != nil {
log.Fatal(err)
}
// Check if an email has been breached
free, _, err := client.CheckEmail(context.Background(), "test@example.com")
if err != nil {
log.Fatal(err)
}
if len(free.Breaches) > 0 {
fmt.Printf("Email found in %d breaches:\n", len(free.Breaches))
for _, b := range free.Breaches {
fmt.Printf(" - %s\n", b)
}
} else {
fmt.Println("Good news! Email not found in any known breaches.")
}
}client, err := xon.NewClient(opts ...xon.ClientOption)Create a new client with functional options. See Configuration Options for all available options.
Check if an email address has been exposed in any data breaches. Returns the free API response or the Plus API response depending on whether an API key is configured.
// Free API (no API key)
free, _, err := client.CheckEmail(ctx, "user@example.com")
if err != nil {
log.Fatal(err)
}
fmt.Println("Breaches:", free.Breaches)// Plus API (with API key)
client, _ := xon.NewClient(xon.WithAPIKey("your-api-key"))
_, plus, err := client.CheckEmail(ctx, "user@example.com")
if err != nil {
log.Fatal(err)
}
for _, b := range plus.Breaches {
fmt.Printf("Breach: %s, Date: %s, Records: %d\n",
b.BreachID, b.BreachedDate, b.XposedRecords)
}Signature:
func (c *Client) CheckEmail(ctx context.Context, email string) (*CheckEmailFreeResponse, *CheckEmailPlusResponse, error)| Return Value | Type | Description |
|---|---|---|
free |
*CheckEmailFreeResponse |
Populated when no API key is set (Plus response is nil) |
plus |
*CheckEmailPlusResponse |
Populated when an API key is set (Free response is nil) |
err |
error |
Non-nil on validation failure, network error, or API error |
Retrieve a list of all known data breaches. Pass a non-empty domain string to filter results by domain.
// Get all breaches
all, err := client.GetBreaches(ctx, "")
if err != nil {
log.Fatal(err)
}
fmt.Println("Total known breaches:", len(all.ExposedBreaches))
// Filter by domain
adobe, err := client.GetBreaches(ctx, "adobe.com")
if err != nil {
log.Fatal(err)
}
fmt.Println("Adobe breaches:", len(adobe.ExposedBreaches))Signature:
func (c *Client) GetBreaches(ctx context.Context, domain string) (*GetBreachesResponse, error)| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Context for cancellation and timeouts |
domain |
string |
Filter by domain (pass "" for all breaches) |
Returns: *GetBreachesResponse containing a slice of ExposedBreach with fields including BreachID, BreachedDate, Domain, Industry, ExposedData, ExposedRecords, and Verified.
Get detailed breach analytics for an email address, including breach details, summary statistics, metrics breakdowns, and paste exposures.
analytics, err := client.BreachAnalytics(ctx, "user@example.com")
if err != nil {
log.Fatal(err)
}
fmt.Println("Breach details:", len(analytics.ExposedBreaches.BreachesDetails))
fmt.Println("Site:", analytics.BreachesSummary.Site)
fmt.Println("Total breaches:", analytics.BreachesSummary.TotalBreaches)
fmt.Println("Most recent:", analytics.BreachesSummary.MostRecentBreach)Signature:
func (c *Client) BreachAnalytics(ctx context.Context, email string) (*BreachAnalyticsResponse, error)| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Context for cancellation and timeouts |
email |
string |
Email address to analyze |
Returns: *BreachAnalyticsResponse with the following fields:
| Field | Type | Description |
|---|---|---|
ExposedBreaches |
ExposedBreachesWrapper |
Detailed list of each breach |
BreachesSummary |
BreachesSummary |
Summary stats (total breaches, date range, risk) |
BreachMetrics |
BreachMetrics |
Breakdowns by industry, password strength, year, and data type |
PastesSummary |
PasteSummary |
Paste exposure summary |
ExposedPastes |
[]interface{} |
Raw paste exposure entries |
Check whether a password has appeared in known data breaches. The password is hashed locally using Keccak-512 and only the first 10 characters of the hex digest are sent to the API. This k-anonymity approach ensures the full password never leaves your machine.
resp, err := client.CheckPassword(ctx, "password123")
if err != nil {
log.Fatal(err)
}
if resp.SearchPassAnon.Count != "0" {
fmt.Printf("Password has been exposed %s time(s)\n", resp.SearchPassAnon.Count)
} else {
fmt.Println("Password not found in known breaches")
}Signature:
func (c *Client) CheckPassword(ctx context.Context, password string) (*CheckPasswordResponse, error)| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Context for cancellation and timeouts |
password |
string |
The plaintext password to check (hashed locally before sending) |
All errors returned by the client are typed. Use errors.As to check for specific error conditions:
import "errors"
free, _, err := client.CheckEmail(ctx, "test@example.com")
if err != nil {
var notFound *xon.ErrNotFound
var validationErr *xon.ErrValidation
var rateLimitErr *xon.ErrRateLimit
var authErr *xon.ErrAuthentication
var networkErr *xon.ErrNetwork
var apiErr *xon.ErrAPI
switch {
case errors.As(err, ¬Found):
fmt.Println("Email not found in any breaches")
case errors.As(err, &validationErr):
fmt.Printf("Invalid input: %s\n", validationErr.Message)
case errors.As(err, &rateLimitErr):
fmt.Println("Rate limit exceeded, try again later")
case errors.As(err, &authErr):
fmt.Println("Invalid API key")
case errors.As(err, &networkErr):
fmt.Printf("Network error: %v\n", networkErr.Unwrap())
case errors.As(err, &apiErr):
fmt.Printf("API error (status %d): %s\n", apiErr.StatusCode, apiErr.Body)
default:
fmt.Printf("Unexpected error: %v\n", err)
}
}| Error Type | Description |
|---|---|
*ErrValidation |
Invalid input (e.g., malformed email address) |
*ErrNotFound |
Requested resource not found (HTTP 404) |
*ErrRateLimit |
API rate limit exceeded after all retries (HTTP 429) |
*ErrAuthentication |
Missing or invalid API key (HTTP 401/403) |
*ErrNetwork |
Network-level failure (DNS, connection, timeout) |
*ErrAPI |
Unexpected API error with status code and body |
| Free API | Plus API (with API key) | |
|---|---|---|
| Client-side limiting | 1 request per second (automatic) | None |
| Server-side limiting | Standard limits apply | Higher limits |
| 429 retry behavior | Exponential backoff (1s, 2s, 4s) | Exponential backoff (1s, 2s, 4s) |
The client automatically retries on HTTP 429 responses with exponential backoff, up to the configured maximum retries (default: 3).
All options are passed to NewClient as functional options:
client, err := xon.NewClient(
xon.WithAPIKey("your-api-key"),
xon.WithTimeout(15 * time.Second),
xon.WithMaxRetries(5),
)| Option | Default | Description |
|---|---|---|
WithAPIKey(key) |
"" |
Set API key for Plus API access |
WithTimeout(d) |
30s |
HTTP client timeout |
WithBaseURL(url) |
https://api.xposedornot.com |
Override the free API base URL |
WithPlusBaseURL(url) |
https://plus-api.xposedornot.com |
Override the Plus API base URL |
WithPasswordBaseURL(url) |
https://passwords.xposedornot.com/api |
Override the password API base URL |
WithMaxRetries(n) |
3 |
Maximum retries on HTTP 429 responses |
WithCustomHeaders(h) |
nil |
Additional headers included in every request |
WithAllowInsecure() |
false |
Disable HTTPS enforcement (testing only) |
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone the repository
git clone https://github.com/XposedOrNot/XposedOrNot-Go.git
cd XposedOrNot-Go
# Run tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests with race detector
go test -race ./...
# Vet
go vet ./...MIT - see the LICENSE file for details.
- XposedOrNot Website
- API Documentation
- GitHub Repository
- Go Package Reference
- XposedOrNot API Repository
Made with care by XposedOrNot