postio

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MIT Imports: 12 Imported by: 0

README

Postio Go SDK

Go Reference Go Report Card

Go SDK for the Postio API — UK address, email, and phone validation. Backed by Royal Mail PAF and Ordnance Survey. Stdlib net/http only, zero dependencies.

Install

go get github.com/postio-uk/postio-go

Requires Go 1.22+.

30-second example

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/postio-uk/postio-go"
)

func main() {
    client, err := postio.NewClient(postio.WithAPIKey("pk_live_..."))
    if err != nil {
        log.Fatal(err)
    }

    result, err := client.Address.Search(context.Background(), "downing street", nil)
    if err != nil {
        log.Fatal(err)
    }
    for _, hit := range result.Results {
        fmt.Println(hit.UDPRN, hit.Suggestion)
    }
    fmt.Println("request id:", result.Meta.RequestID)
}

API key may also come from POSTIO_API_KEY.

API

Method Returns
client.Address.Search(ctx, q, opts) *AddressSearchEnvelope
client.Address.Postcode(ctx, postcode, opts) *AddressPostcodeEnvelope
client.Address.UDPRN(ctx, udprn) *AddressUDPRNEnvelope
client.Email.Validate(ctx, address) *EmailEnvelope
client.Phone.Validate(ctx, number) *PhoneEnvelope
client.Connect(ctx) *ConnectSuccess

opts may be nil for default behaviour. Every method takes a context.Context for cancellation/timeout — pass context.Background() if you don't have one.

Errors

Every non-2xx response returns a *postio.Error. Match by sentinel with errors.Is, or by struct with errors.As for full detail.

result, err := client.Address.Postcode(ctx, "not-a-postcode", nil)
if errors.Is(err, postio.ErrValidation) {
    var e *postio.Error
    errors.As(err, &e)
    fmt.Printf("validation failed (request_id=%s): %s\n", e.RequestID, e.Details)
}
Sentinel HTTP
postio.ErrValidation 400 / 422
postio.ErrInvalidKey 401
postio.ErrOutOfCredit 402
postio.ErrForbidden 403
postio.ErrNotFound 404
postio.ErrRateLimit 429 (.RetryAfter populated when sent)
postio.ErrServer 5xx
postio.ErrTimeout local request timeout
postio.ErrConnection transport error

The *Error struct exposes Status, Code, Message, Details, RequestID, RetryAfter, the raw Envelope, and a Cause wrapping the underlying transport error.

Configuration

client, err := postio.NewClient(
    postio.WithAPIKey("pk_live_..."),
    postio.WithBaseURL("https://api.postio.co.uk/v1"),  // default
    postio.WithTimeout(10 * time.Second),                // default
    postio.WithRetries(2),                               // default; 0 to disable
    postio.WithRetryBackoff(500*time.Millisecond, 8*time.Second),
    postio.WithHeader("x-tracking-id", "..."),
)

Default retry policy: 2 retries on 408/409/429/5xx + network/timeout, exponential backoff with full jitter (500ms → 8s cap).

License

MIT — see LICENSE.

Postio is a trading name of Onno Group Limited, registered in England & Wales (company no. 08622799). Registered office: Suite 22 Trym Lodge, 1 Henbury Road, Westbury-On-Trym, Bristol BS9 3HQ.

Documentation

Overview

Package postio is the Go SDK for the Postio API — UK address, email, and phone validation, backed by Royal Mail PAF and Ordnance Survey.

Quick start:

client, err := postio.NewClient(postio.WithAPIKey("pk_live_..."))
if err != nil {
    log.Fatal(err)
}

result, err := client.Address.Search(context.Background(), "downing street", nil)
if err != nil {
    log.Fatal(err)
}
for _, hit := range result.Results {
    fmt.Println(hit.UDPRN, hit.Suggestion)
}

The API key may also be supplied via the POSTIO_API_KEY environment variable, in which case WithAPIKey is optional.

Index

Constants

View Source
const (
	DeliverabilityDeliverable   = "deliverable"
	DeliverabilityUndeliverable = "undeliverable"
	DeliverabilityRisky         = "risky"
	DeliverabilityUnknown       = "unknown"
	DeliverabilityInvalid       = "invalid"
)

Deliverability constants — mirror the OpenAPI Deliverability enum.

View Source
const DefaultBaseURL = "https://api.postio.co.uk/v1"

DefaultBaseURL is the production Postio API endpoint.

View Source
const DefaultTimeout = 10 * time.Second

DefaultTimeout is the per-request HTTP timeout when no explicit http.Client is provided.

View Source
const Version = "0.1.1"

Version is the current SDK version. Mirrors the git tag.

Variables

View Source
var (
	ErrInvalidKey  = errors.New("postio: invalid api key (401)")
	ErrOutOfCredit = errors.New("postio: out of credit (402)")
	ErrForbidden   = errors.New("postio: forbidden (403)")
	ErrNotFound    = errors.New("postio: not found (404)")
	ErrValidation  = errors.New("postio: validation error (400/422)")
	ErrRateLimit   = errors.New("postio: rate limit (429)")
	ErrServer      = errors.New("postio: server error (5xx)")
	ErrTimeout     = errors.New("postio: request timeout")
	ErrConnection  = errors.New("postio: connection error")
)

Sentinel errors. Use errors.Is(err, ...) to match.

Functions

This section is empty.

Types

type Address

type Address struct {
	UDPRN                   int      `json:"udprn"`
	Postcode                string   `json:"postcode"`
	PostcodeOutward         *string  `json:"postcode_outward,omitempty"`
	PostcodeInward          *string  `json:"postcode_inward,omitempty"`
	PostcodeType            *string  `json:"postcode_type,omitempty"`
	AddressLine1            *string  `json:"address_line_1,omitempty"`
	AddressLine2            *string  `json:"address_line_2,omitempty"`
	AddressLine3            *string  `json:"address_line_3,omitempty"`
	PostTown                *string  `json:"post_town,omitempty"`
	OrganisationName        *string  `json:"organisation_name,omitempty"`
	DepartmentName          *string  `json:"department_name,omitempty"`
	BuildingName            *string  `json:"building_name,omitempty"`
	BuildingNumber          *string  `json:"building_number,omitempty"`
	SubBuildingName         *string  `json:"sub_building_name,omitempty"`
	POBox                   *string  `json:"po_box,omitempty"`
	Thoroughfare            *string  `json:"thoroughfare,omitempty"`
	DependentThoroughfare   *string  `json:"dependent_thoroughfare,omitempty"`
	DependentLocality       *string  `json:"dependent_locality,omitempty"`
	DoubleDependentLocality *string  `json:"double_dependent_locality,omitempty"`
	DeliveryPointSuffix     *string  `json:"delivery_point_suffix,omitempty"`
	Country                 *string  `json:"country,omitempty"`
	County                  *string  `json:"county,omitempty"`
	District                *string  `json:"district,omitempty"`
	Ward                    *string  `json:"ward,omitempty"`
	Latitude                *float64 `json:"latitude,omitempty"`
	Longitude               *float64 `json:"longitude,omitempty"`
	Eastings                *int     `json:"eastings,omitempty"`
	Northings               *int     `json:"northings,omitempty"`
}

Address is a full UK address record from Royal Mail PAF + Ordnance Survey. Most fields are optional and may be empty strings.

type AddressPostcodeEnvelope

type AddressPostcodeEnvelope struct {
	Success bool      `json:"success"`
	Results []Address `json:"results"`
	Meta    Meta      `json:"meta"`
}

AddressPostcodeEnvelope is the response from /address/postcode/{postcode}.

type AddressSearchEnvelope

type AddressSearchEnvelope struct {
	Success bool                  `json:"success"`
	Results []AddressSearchResult `json:"results"`
	Meta    Meta                  `json:"meta"`
}

AddressSearchEnvelope is the response from /address/search.

type AddressSearchResult

type AddressSearchResult struct {
	UDPRN      int    `json:"udprn"`
	Suggestion string `json:"suggestion"`
}

AddressSearchResult is a single typeahead hit.

type AddressService

type AddressService struct {
	// contains filtered or unexported fields
}

AddressService groups the /address/* endpoints.

func (*AddressService) Postcode

func (s *AddressService) Postcode(ctx context.Context, postcode string, opts *PostcodeOptions) (*AddressPostcodeEnvelope, error)

Postcode returns the full PAF address records for a UK postcode.

func (*AddressService) Search

Search performs a free-text typeahead lookup. Pass nil for default options.

func (*AddressService) UDPRN

func (s *AddressService) UDPRN(ctx context.Context, udprn int) (*AddressUDPRNEnvelope, error)

UDPRN looks up a single PAF address by its UDPRN.

type AddressUDPRNEnvelope

type AddressUDPRNEnvelope struct {
	Success bool      `json:"success"`
	Results []Address `json:"results"`
	Meta    Meta      `json:"meta"`
}

AddressUDPRNEnvelope is the response from /address/udprn/{udprn}.

type Client

type Client struct {

	// Resource namespaces.
	Address *AddressService
	Email   *EmailService
	Phone   *PhoneService
	// contains filtered or unexported fields
}

Client is the top-level Postio API client. Safe for concurrent use.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient constructs a Postio client. If WithAPIKey is not passed, the POSTIO_API_KEY environment variable is used.

func (*Client) Connect

func (c *Client) Connect(ctx context.Context) (*ConnectSuccess, error)

Connect calls /connect — a free health probe that confirms the API is reachable and the key is valid.

type ConnectSuccess

type ConnectSuccess struct {
	Success bool        `json:"success"`
	Meta    MetaConnect `json:"meta"`
}

ConnectSuccess is the response from /connect.

type EmailEnvelope

type EmailEnvelope struct {
	Success bool          `json:"success"`
	Results []EmailResult `json:"results"`
	Meta    Meta          `json:"meta"`
}

EmailEnvelope is the response from /email/{address}.

type EmailResult

type EmailResult struct {
	Email          string  `json:"email"`
	IsValidSyntax  bool    `json:"isValidSyntax"`
	DidYouMean     *string `json:"didYouMean"`
	IsDisposable   bool    `json:"isDisposable"`
	IsFreeProvider bool    `json:"isFreeProvider"`
	IsRoleAccount  bool    `json:"isRoleAccount"`
	MXFound        bool    `json:"mxFound"`
	SMTPCheck      *string `json:"smtpCheck"`
	IsCatchAll     *bool   `json:"isCatchAll"`
	Deliverability string  `json:"deliverability"`
}

EmailResult is the validation verdict for a single email address.

type EmailService

type EmailService struct {
	// contains filtered or unexported fields
}

EmailService groups the /email/* endpoints.

func (*EmailService) Validate

func (s *EmailService) Validate(ctx context.Context, address string) (*EmailEnvelope, error)

Validate runs syntax / MX / SMTP checks against an email address.

type Error

type Error struct {
	// Status is the HTTP status code (0 for network/timeout errors).
	Status int
	// Code is the API's error code (e.g. "invalid_api_key"). Empty for
	// transport-level errors.
	Code string
	// Message is the human-readable error string from the envelope.
	Message string
	// Details is the optional details field from the envelope.
	Details string
	// RequestID is the API's request_id — quote this in support tickets.
	RequestID string
	// Envelope is the raw decoded error body, for any field this struct
	// doesn't surface explicitly.
	Envelope *ErrorEnvelope
	// RetryAfter is set on rate-limit errors when the API sends a
	// Retry-After header.
	RetryAfter float64
	// Cause is the underlying transport error, if any.
	Cause error
}

Error is every typed Postio API error. Use errors.As to discriminate.

var e *postio.Error
if errors.As(err, &e) && e.Status == 401 { ... }

Or use the sentinel values:

if errors.Is(err, postio.ErrInvalidKey) { ... }

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

Is implements errors.Is dispatch against the sentinel errors below.

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorEnvelope

type ErrorEnvelope struct {
	Success bool          `json:"success"`
	Error   string        `json:"error"`
	Details *string       `json:"details,omitempty"`
	Results []interface{} `json:"results"`
	Meta    Meta          `json:"meta"`
}

ErrorEnvelope is the API's standard error response body. Surfaced via the typed error types in errors.go.

type Meta

type Meta struct {
	CountResults int         `json:"countResults"`
	RequestID    string      `json:"requestId"`
	Performance  Performance `json:"performance"`
}

Meta is the response envelope metadata — request_id, count, timing.

type MetaConnect

type MetaConnect struct {
	RequestID   string      `json:"requestId"`
	Performance Performance `json:"performance"`
}

MetaConnect is the meta block for /connect (no count field).

type Option

type Option func(*Client) error

Option configures the Client at construction time.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key explicitly. Overrides POSTIO_API_KEY env var.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the base URL. Default is DefaultBaseURL. Useful for local testing or stage-api.postio.co.uk.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a custom *http.Client (for proxies, custom transports, etc.). The client's Timeout is honoured as-is; pass a configured client.

func WithHeader

func WithHeader(key, value string) Option

WithHeader adds a header to every request. x-api-key and accept cannot be overridden.

func WithRetries

func WithRetries(max int) Option

WithRetries configures the retry policy. Pass 0 to disable retries. Default: 2 retries, exp backoff 500ms → 8s with full jitter.

func WithRetryBackoff

func WithRetryBackoff(base, cap time.Duration) Option

WithRetryBackoff overrides the exponential backoff parameters.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout when using the default http.Client. Ignored if WithHTTPClient is also passed.

type Performance

type Performance struct {
	WorkerMs int `json:"workerMs"`
	LookupMs int `json:"lookupMs"`
}

Performance is the per-request timing breakdown returned with every successful response.

type PhoneEnvelope

type PhoneEnvelope struct {
	Success bool          `json:"success"`
	Results []PhoneResult `json:"results"`
	Meta    Meta          `json:"meta"`
}

PhoneEnvelope is the response from /phone/{number}.

type PhoneResult

type PhoneResult struct {
	Number              string  `json:"number"`
	IsValid             bool    `json:"isValid"`
	IsPossible          bool    `json:"isPossible"`
	Type                *string `json:"type"`
	CountryCode         *string `json:"countryCode"`
	CountryName         *string `json:"countryName"`
	NationalFormat      *string `json:"nationalFormat"`
	InternationalFormat *string `json:"internationalFormat"`
	E164Format          *string `json:"e164Format"`
	OriginalCarrier     *string `json:"originalCarrier"`
	CurrentCarrier      *string `json:"currentCarrier"`
	IsPorted            *bool   `json:"isPorted"`
	IsReachable         *bool   `json:"isReachable"`
	MCC                 *string `json:"mcc"`
	MNC                 *string `json:"mnc"`
	Level               *string `json:"level"`
	LookupError         *string `json:"lookupError,omitempty"`
}

PhoneResult is the validation verdict for a single phone number.

type PhoneService

type PhoneService struct {
	// contains filtered or unexported fields
}

PhoneService groups the /phone/* endpoints.

func (*PhoneService) Validate

func (s *PhoneService) Validate(ctx context.Context, number string) (*PhoneEnvelope, error)

Validate runs format / carrier / reachability checks against a phone number. Accepts E.164 (preferred) or other formats; the API normalises.

type PostcodeOptions

type PostcodeOptions struct {
	MaxResults int
}

PostcodeOptions tunes a postcode lookup.

type SearchOptions

type SearchOptions struct {
	// MaxResults caps the number of hits returned. API default is 10
	// when zero. Maximum 100.
	MaxResults int
}

SearchOptions tunes a free-text address search.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL