Go
Generate a context-first Go client for your API using only the standard library.
Typeship generates a context-first Go client for your API using only the standard library. This page covers Go-specific behavior; shared client behavior is on the SDK overview.
CLI and MCP packages are generated and released separately.
Module
- The module path comes from the project's Go destination repository:
github.com/parcel/parcel-go. Turn on Override module path at the bottom of the Go SDK settings only when the public import address must differ. Until a repository is set, the module path is a placeholder thatgo getcannot resolve. - The package name is read off the module path the way the Go ecosystem reads it.
github.com/parcel/parcel-goispackage parcel. A major-version suffix is ignored. go.modhas norequireblock. The runtime isnet/httpand the standard library.- Generated source is formatted with
gofmt. Rungo build ./...in the package directory before using it. sdk_test.gocalls one operation per resource against a localhttptestserver, checks that a 404 returns*NotFoundError, and walks two pages when the API paginates. Rungo test ./...in the package directory. The tests use only the standard library.
Idioms
- Context first. Every method takes a
context.Context. Cancel it to abort a request, a paginated iteration, or a stream. (T, error)returns. Methods with no response body returnerroralone.- Params by pointer. Query and header parameters, and the fields of an inline object body, live in one
XxxParamsstruct passed by pointer.nilmeans no parameters. Optional fields are pointers so absent and zero stay distinct;parcel.Ptr(v)makes one inline. - Named bodies are arguments. A request body that is a
$refto a named schema, an array, or plain text is a positionalbodyargument with its own type (ShipmentParams,[]ShipmentParams,string), so a required body is enforced by the compiler. - Functional options.
parcel.New(parcel.WithBearerToken(...), parcel.WithTimeout(...))for the client,parcel.WithRequestTimeout(...)and friends per call. - Iterators.
it := client.Shipments.List(ctx, nil), thenfor it.Next() { it.Value() }andit.Err(). - Initialisms. Identifiers follow Go's convention:
ID,ShipmentID,URL,HTTPStatus.
Choose credentials for each endpoint
Use WithCredential(name, value) for a bearer token or API key, WithCredentialFunc(name, callback) for a token or API key that changes, or WithBasicCredential(name, username, password) for basic auth. Each name matches a scheme in your Spec's components.securitySchemes. API-key callbacks always go through WithCredentialFunc; bearer callbacks can also use WithBearerTokenFunc.
WithCredentialFuncContext(name, callback) and WithBearerTokenFuncContext(callback) take a func(ctx context.Context) (string, error) and receive the request's context, so a lookup can read request-scoped values and stop when the call is cancelled. WithClientCredentials fetches tokens under the request's context too.
The client sends the first complete permitted credential combination for each endpoint. A combined requirement needs all its schemes; partial combinations send no credentials. Public endpoints receive no generated credentials. Token callbacks run only when selected and run again on a retry. Your API still enforces validity and permissions.
If a callback returns an error, the call returns that error unchanged, so errors.Is and errors.As match it, and nothing is sent or retried. If a request authenticated by a callback or by WithClientCredentials receives 401, the client discards the cached client-credentials token, resolves the credential again, and sends the request once more with the same body. That resend does not count against the retry budget; a second 401 is returned as an *APIError. Requests authenticated only by static credentials are not resent.
Named options take precedence over matching convenience options and environment variables. New returns an error for unknown scheme names or nil callbacks. Use explicit request headers or WithOnRequest for application-managed authentication.
Use different credentials per request
client.WithCredentials(opts...) returns a client that authenticates with opts alone. Use it to call as a different user or tenant without building a new client:
userClient := client.WithCredentials(parcel.WithBearerToken(userToken))
shipment, err := userClient.Shipments.Get(ctx, shipmentID)The new client shares the original's *http.Client, base URL, timeouts, retries, and hooks. Every credential is replaced: none is inherited, and credential environment variables such as PARCEL_TOKEN are not read again. WithCredentials() with no options gives an unauthenticated client.
WithCredentials accepts only credential options: WithBearerToken, WithBearerTokenFunc, WithBearerTokenFuncContext, the API-key options, WithBasicAuth, WithClientCredentials, WithCredential, WithCredentialFunc, WithCredentialFuncContext, and WithBasicCredential. Passing any other option, an unknown scheme name, or a nil callback returns a client whose every call fails with that configuration error before sending a request.
Raw requests
client.Raw(ctx, parcel.RawRequest{...}) sends a request the generated methods do not cover through the same transport, retries, and typed errors. RawRequest.Auth chooses its credentials: nil uses the Spec's root security requirement when it has exactly one alternative and no credential otherwise, &parcel.RawAuth{Scheme: name} uses one named scheme, and &parcel.RawAuth{None: true} sends none of the client's credentials.
Credential headers (such as Authorization, Cookie, Proxy-Authorization, and the API's own API-key headers) and API-key query parameters are rejected in RawRequest and in per-call options such as WithRequestHeader, unless Auth is &parcel.RawAuth{None: true}. In that case the request carries exactly the credentials you supply, which covers schemes the client does not configure. Host, Content-Type, Content-Length, Transfer-Encoding, and Connection are always rejected; set the body type with RawRequest.ContentType.
Environment variables
New reads these before options apply, so options always win:
| Variable | Sets |
|---|---|
PARCEL_TOKEN | bearer token |
PARCEL_API_KEY | the API key, when the spec declares one |
PARCEL_USERNAME / PARCEL_PASSWORD | basic auth |
PARCEL_BASE_URL | base URL |
PARCEL_WEBHOOK_KEY | webhook key |
The prefix comes from your package name. Credential variables use the same names as the CLI, which can follow the API's convention (such as PARCEL_API_KEY for a bearer scheme named for an API key) or your auth.credential_variables setting; see Auth and configuration. The README lists the names your package reads.
Errors
APIError carries a stable Code, Status, raw Body []byte, RequestID, and Message, plus a Decode(v any) error helper for the body. Its Error() text includes the next step. Each status family has one type, returned whether or not the operation documents the status: *BadRequestError, *UnauthorizedError, *ForbiddenError, *NotFoundError, *ConflictError, *UnprocessableEntityError, *RateLimitError, and *ServerError (any 5xx). They embed APIError, so errors.As matches at either precision. A 304 Not Modified, which arrives only when you send a conditional header, returns *NotModifiedError; see Conditional requests. Any other undocumented status returns a bare *APIError. A network failure returns *TransportError with Code, Method, URL, and a wrapped Err. Malformed successful responses return *ResponseParseError with the same common fields. A cancelled context returns ctx.Err().
Webhooks
client.Webhooks.Unwrap(payload, header) verifies and returns a *WebhookEvent carrying Type and the raw Data. Go has no sum types, so each declared event gets an accessor: event.AsShipmentUpdated() returns the typed payload or an error if the type does not match. Events your module has not seen yet still arrive intact. VerifyWebhook and SignWebhook are exported for handlers that do not hold a client.
Streaming
client.Events.Stream(ctx) returns a *Stream. Read it like bufio.Scanner: Next, Event (an SSEEvent), Err, and Close. The stream uses a copy of the HTTP client with no timeout, so cancel through the context.
Uploads
A multipart/form-data field is a parcel.Upload{Name, ContentType, Reader} on the params struct: the name and content type the server sees, and an io.Reader for the bytes. A []parcel.Upload field sends one part per file. The other fields travel alongside as form parts: a struct or map is one JSON part (application/json), and a type the spec's encoding names for a field is used for that part. A raw binary body (application/octet-stream, image/png, ...) is a positional body io.Reader, sent under the spec's content type.
Unions
Go has no sum types. A oneOf/anyOf becomes a named union type that holds the JSON as received and decodes on request: As<Variant>() (Variant, error) and From<Variant>(v) per variant, Raw() for the bytes, and Discriminator() when the spec names one. Use the accessors to inspect a supported variant and Raw() to preserve data from a variant your client does not yet know.
Timestamps
A date-time field or parameter, and a GraphQL DateTime or DateTimeISO scalar, is a parcel.DateTime. It embeds time.Time, so shipment.CreatedAt.Before(cutoff) works directly and shipment.CreatedAt.Time is the plain value. A date without a time stays a string. Optional and nullable timestamps follow the usual rules: *parcel.DateTime, or *parcel.Nullable[parcel.DateTime] in a request.
Decoding never fails because of a timestamp. RFC 3339 is read as is, a value without a UTC offset is read as UTC, and a space may separate the date and time. A value that cannot be read, such as "last Tuesday" or a number, leaves Time zero and keeps the API's text in Raw(), so check Raw() when IsZero() reports true.
A decoded value is sent back exactly as received until you change Time. A value you build with parcel.DateTime{Time: t} is sent as RFC 3339, including in query and path parameters:
it := client.Shipments.List(ctx, &parcel.ShipmentsListParams{
CreatedAfter: parcel.Ptr(parcel.DateTime{Time: time.Now().Add(-24 * time.Hour)}),
})Response metadata
WithAPIResponse(&meta) on any call fills an APIResponse{StatusCode, Header, RequestID, Body} from the last HTTP response (rate-limit headers, a Location, a request id to quote, or raw bytes for a future field or variant) without going through the global WithOnResponse hook. Go string-backed enum types already preserve unknown server values; generated constants provide autocomplete without closing the wire type.
GraphQL
A GraphQL schema generates one method per query and mutation field on client.Query and client.Mutation. Arguments are the params struct and become variables. Every scalar field to depth 2 is selected by default, with a fragment per concrete type for unions and interfaces, and WithSelection("{ id name }") overrides that per call on methods that return an object. Fields a selection leaves out hold their zero values, and json.Marshal of a result writes only the fields the response carried, explicit nulls included. Connection fields return an *Iter[T] like everything else. Errors in a 200 return *GraphQLError (which unwraps to *APIError) carrying the raw Errors.