The TypeScript SDK with resource methods, automatic idempotency keys and typed errors, and the generated Python and Go clients.
One document, three SDKs
Every SDK is generated from the API's OpenAPI document (openapi.json), so it never lags the API, and every one is tested against the API on each change. Operation ids in the API reference name the same operation in every SDK:
The guides' TypeScript samples use this minket. baseUrl points it at another API address. Resource methods follow the API's groups: emails, uploads, attachments, templates, domains, webhooks, events, inbound (with inbound.routes), mailboxes, suppressions, contacts (with contacts.imports and contacts.properties), segments (with segments.contacts), topics, broadcasts, automations, keys, logs and members. Path parameters come first, then the body or the query, then options: minket.webhooks.replay(id, { since }), minket.contacts.get("jane@example.com"). Each returns the typed success body; the operation table names the method for every operation.
Idempotency
Every POST carries an Idempotency-Key: a fresh UUIDv7 unless you give one, so a retry by a proxy or a network layer can never send twice. To retry safely yourself, pass the same key again (Idempotency):
A key in the body (idempotency_key) is sent as the header too, since the API prefers the header.
Errors
Every failure throws MinketError:
TypeScript
import { isMinketError } from "@minket/sdk";
try {
await minket.emails.send(receipt, { idempotencyKey });
} catch (error) {
if (isMinketError(error) && error.retryable) {
// safe to retry with the same idempotency key, after error.retryAfterSeconds if set
}
throw error;
}
Property
code
The API's error code (validation_error, not_found, ...), or network_error when no response arrived and unexpected_response for a body that is not the API's
status
HTTP status, 0 without a response
message
What happened and what to do next
retryable
True when the same request with the same idempotency key can succeed
requestId
The request's id, for minket.logs.list() and support
issues
Validation issues: [{ path: "to.0", message }]
retryAfterSeconds
From retry-after, on rate limits and in-flight idempotency keys
error.toJSON() gives the snake_case wire shape for logs; it never contains request content.
Files and lists
minket.uploads.upload({ purpose, filename, content_type, content }) creates a presigned upload, stores the bytes (without the API key) and returns the completed upload, ready for minket.attachments.create({ upload_id }) or minket.contacts.import({ upload_id }). Use it for attachments and imports above 4 MB.
paginate walks a list with its after cursor:
TypeScript
import { paginate } from "@minket/sdk";
for await (const entry of paginate((cursor) => minket.suppressions.list({ ...cursor, reason: "hard_bounce" }))) {
console.log(entry.email);
}
Any operation, and the raw client
minket.request(operationId, { path, query, body }) calls any operation by its id, typed from the document, with the same auth, idempotency and errors. The generated functions are exported too, for code that prefers { data, error } results to exceptions:
verifyWebhook is exported for receivers (Verify signatures). Pass your own fetch to new Minket({ fetch }) in tests.
Python
bash sdks/python/generate.sh writes the project to sdks/python/minket; install it with pip install ./sdks/python/minket. Every operation is a module under minket.api.<group> with sync, sync_detailed, asyncio and asyncio_detailed; the detailed variants return the status code, headers and parsed body, and error statuses parse into ApiError.
Python
import os
from minket import AuthenticatedClient
from minket.api.emails import get_email, send_email
from minket.models import ApiError, SendEmailRequest
base_url = "https://api.minket.com"
token = os.environ["MINKET_API_KEY"]
client = AuthenticatedClient(base_url=base_url, token=token)
# A client per idempotency key: with_headers() also changes a client that has made a request.
keyed = AuthenticatedClient(base_url=base_url, token=token, headers={"Idempotency-Key": "receipt-10452"})
sent = send_email.sync_detailed(
client=keyed,
body=SendEmailRequest(
from_="Acme <billing@mail.acme.example>",
to=["jane@example.com"],
subject="Your receipt for order 10452",
text="Thanks, Jane.",
),
)
if isinstance(sent.parsed, ApiError):
print(sent.parsed.error.code, sent.parsed.error.retryable)
else:
email = get_email.sync(sent.parsed.id, client=client)
from is from_ because it is a keyword. Path parameters are positional, query parameters keyword arguments, and bodies model instances (or Model.from_dict({...})).
Go
bash sdks/go/generate.sh writes package minket (client.gen.go): a typed client with request and response types for every operation. Add the bearer token and an idempotency key with a request editor:
Go
client, err := minket.NewClientWithResponses(
"https://api.minket.com",
minket.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
req.Header.Set("Authorization", "Bearer "+os.Getenv("MINKET_API_KEY"))
if req.Method == http.MethodPost && req.Header.Get("Idempotency-Key") == "" {
req.Header.Set("Idempotency-Key", newKey())
}
return nil
}),
)
if err != nil {
return err
}
var to minket.SendEmailRequest_To
_ = to.FromSendEmailRequestTo1([]string{"jane@example.com"})
from, subject, text := "Acme <billing@mail.acme.example>", "Your receipt", "Thanks, Jane."
res, err := client.SendEmailWithResponse(ctx, minket.SendEmailRequest{From: &from, To: to, Subject: &subject, Text: &text})
if err != nil {
return err
}
if res.JSON200 != nil {
fmt.Println(res.JSON200.Id, res.JSON200.Status)
} else if res.JSON422 != nil {
fmt.Println(res.JSON422.Error.Code, res.JSON422.Error.Message)
}
Response structs carry one field per documented status (JSON200, JSON404, ...), so error bodies are typed. Fields that take one address or a list are unions set with From...To0 (a string) or From...To1 (a slice).
Operations
Every operation in the current document, by group, with its name in each SDK. The operation links to its reference entry.