TypeScript SDK¶
The Smartsapp TypeScript SDK provides typed API client functions, request/response types, and Zod runtime validation schemas for all backend modules. It is auto-generated from the backend's OpenAPI specifications using @hey-api/openapi-ts.
Client configuration¶
Configure the HTTP client once at app startup:
import { client } from '@hey-api/client-fetch';
client.setConfig({
baseUrl: 'https://api.smartsapp.com',
headers: {
Authorization: `Bearer ${token}`,
},
});
Usage¶
The SDK provides two entry points so each app only sees its relevant APIs:
// Staff app
import { canteenStaff, school, auth } from '@smartsapp/sdk/staff';
const menus = await canteenStaff.getMenus({ query: { page: 0, size: 10 } });
// Parent app
import { canteenParent, schoolParent, auth } from '@smartsapp/sdk/parent';
const orders = await canteenParent.getMyOrders({ query: { page: 0, size: 10 } });
Tree-shaking ensures only the API groups you import are bundled.
Types¶
Every request and response type is exported alongside the SDK functions. Use import type to access them for type annotations, form state, component props, and more:
import type {
CanteenUserResponse,
OrderDetailResponse,
CreateOrderRequest,
MenuResponse,
} from '@smartsapp/sdk/staff';
// Type a component's props
interface OrderCardProps {
order: OrderDetailResponse;
}
// Type form state
const [formData, setFormData] = useState<CreateOrderRequest>({
studentId: '',
orderType: 'PRE_ORDER',
items: [],
});
// Type API responses
const handleFetchUsers = async (): Promise<CanteenUserResponse[]> => {
const result = await canteenStaff.listCanteenUsers({ query: { page: 0, size: 50 } });
return result.data.content;
};
Parent app types work the same way:
import type { ParentOrderResponse, ParentPlaceOrderRequest } from '@smartsapp/sdk/parent';
Types are compile-time only and add zero bytes to your bundle.
Runtime validation with Zod¶
The SDK includes auto-generated Zod schemas for every request and response type. Use them for runtime validation:
import { canteenStaff } from '@smartsapp/sdk/staff';
import { zCreateOrderRequest } from '@smartsapp/sdk/staff';
// Validate input before sending
const input = zCreateOrderRequest.parse({
studentId: "550e8400-...",
orderType: "PRE_ORDER",
items: [{ itemVariantId: "...", quantity: 1 }]
});
const order = await canteenStaff.createOrder({ body: input });
When validation fails, .parse() throws a ZodError. Use .safeParse() to handle errors gracefully instead:
import { zCreateMenuSectionRequest } from '@smartsapp/sdk/staff';
const result = zCreateMenuSectionRequest.safeParse({
selectionMode: 'INVALID_VALUE', // not a valid enum value
// name is missing (required)
});
if (!result.success) {
console.error(result.error.issues);
// [
// { path: ['name'], message: 'Required', code: 'invalid_type' },
// { path: ['selectionMode'], message: "Invalid enum value. Expected 'SINGLE' | 'MULTIPLE', received 'INVALID_VALUE'", code: 'invalid_enum_value' }
// ]
} else {
const validInput = result.data; // fully typed CreateMenuSectionRequest
}
Zod schemas are exported from the same entry points (@smartsapp/sdk/staff and @smartsapp/sdk/parent). Schema names follow the pattern z{TypeName} (e.g. zCreateOrderRequest, zOrderResponse).
Request headers¶
Request ID (X-Request-Id)¶
The SDK automatically generates a unique X-Request-Id header on every request. This is your primary correlation ID — use it when filing bug reports, matching error responses to requests, or grouping retries together.
- Auto-generated per request; no setup needed
- Included in every backend log line for the request
- Returned in error response bodies as
requestId - You control it — override per-request to correlate retries under the same ID:
const { data, error } = await canteenParent.getParentOrders({
headers: {
'X-Request-Id': 'my-custom-correlation-id',
},
});
When a request fails, the requestId is included in the error response body:
{
"type": "https://api.smartsapp.com/errors/not-found",
"title": "Order Not Found",
"status": 404,
"detail": "Order with id 550e8400-... not found",
"requestId": "a1b2c3d4-..."
}
Trace ID (X-Trace-Id)¶
Every response includes a server-generated X-Trace-Id header. If a support engineer asks you to provide a trace ID, grab it from the response headers — it lets them follow the request through backend services, logs, and async processing (e.g. Kafka).
const response = await canteenParent.placeParentOrder({ ... });
// response.headers['X-Trace-Id'] → "64f2b4a8e3d1c9f0a5b7d2e4f6a8c0d1"
Idempotency Key (Idempotency-Key)¶
Endpoints that create resources or trigger side effects require an Idempotency-Key header to prevent duplicate processing on retries. The key must be a unique string (typically a UUID) generated by the client.
Required on: placeParentOrder, confirmParentBatch
const idempotencyKey = crypto.randomUUID();
const { data, error } = await canteenParent.placeParentOrder({
body: orderRequest,
headers: {
'Idempotency-Key': idempotencyKey,
},
});
If the request fails due to a network error, retry with the same key — the server will return the original response without creating a duplicate order. Using a different key will create a new order.
| Response | Meaning |
|---|---|
| Original status (e.g. 201) | Cached response from first successful request |
400 |
Missing Idempotency-Key header |
409 |
A request with this key is already being processed |
Error handling¶
Every SDK function returns { data, error } — it never throws. When the API returns a 4xx or 5xx status, data is undefined and error contains a ProblemDetail (RFC 9457) object:
const { data: menu, error } = await canteenStaff.getMenu({
path: { id: "non-existent-id" }
});
if (error) {
console.error(error.title, error.detail);
return;
}
// data is now narrowed to the success type
The error object is a flat object with these fields:
| Field | Type | Description |
|---|---|---|
type |
string |
URI identifying the error category |
title |
string |
Short human-readable summary (e.g. "Not Found") |
status |
number |
HTTP status code |
detail |
string |
Explanation specific to this occurrence |
requestId |
string |
Request ID for tracing (matches X-Request-Id response header) |
For validation errors (400), the response includes an additional fieldErrors array:
{
"type": "https://api.smartsapp.com/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "name: must not be blank; role: must not be null",
"fieldErrors": [
{ "field": "name", "message": "must not be blank" },
{ "field": "role", "message": "must not be null" }
]
}
For not found errors (404), the response includes the resourceId:
{
"type": "https://api.smartsapp.com/errors/not-found",
"title": "Menu Not Found",
"status": 404,
"detail": "Menu with id 550e8400-... not found",
"resourceId": "550e8400-..."
}
Consumer integration¶
The SDK depends on @hey-api/client-fetch and zod at runtime. Install them in your consumer app:
npm install @hey-api/client-fetch zod
Then reference the SDK via TypeScript path aliases:
{
"compilerOptions": {
"paths": {
"@smartsapp/sdk/*": ["../../sdk/src/*"]
}
}
}
Or with npm/pnpm workspaces (which install @hey-api/client-fetch transitively):
{
"dependencies": {
"@smartsapp/sdk": "workspace:*"
}
}
API reference¶
- Staff SDK Reference — all functions available to the staff app
- Parent SDK Reference — all functions available to the parent app
Contributing¶
For SDK generation, API group mapping, and the pre-commit pipeline, see the SDK Development Guide.