Skip to content

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:

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@smartsapp/sdk/*": ["../../sdk/src/*"]
    }
  }
}

Or with npm/pnpm workspaces (which install @hey-api/client-fetch transitively):

package.json
{
  "dependencies": {
    "@smartsapp/sdk": "workspace:*"
  }
}

API reference

Contributing

For SDK generation, API group mapping, and the pre-commit pipeline, see the SDK Development Guide.