Developer Search

Search developer docs, public resources, and endpoint references.

Get Started
Reference index

Generated API reference index for the planned public contract.

This index is generated from typed contract definitions so resources, schema fields, example payloads, endpoint summaries, and search results all stay in sync while /api/public/v1 is being formalized.

Generated from contracts

Treat this as the source-of-truth staging area for /api/public/v1.

Each schema below is backed by a typed contract definition, and every example payload is generated from the same source. Field tables, search results, and endpoint examples evolve together.

Return to API program

Customers

Private preview

Customer resources anchor the public platform. They give downstream systems a stable identity model for accounts, service locations, communication preferences, and lifecycle activity.

Base path
/api/public/v1/customers
read:customerswrite:customersdelete:customers
Schemas
Customer

Canonical public representation of a customer record in Asteri.

FieldType
iduuid
organization_iduuid
account_type"individual" | "business"
display_namestring
emailstring | null
phonestring | null
created_atdatetime
Generated example
{
  "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d",
  "account_type": "business",
  "display_name": "North Harbor Facilities",
  "email": "ops@northerborfacilities.com",
  "phone": "+1-404-555-0128",
  "created_at": "2026-03-18T15:40:12.000Z"
}
CreateCustomerRequest

Payload for creating a customer in the public API.

FieldType
account_type"individual" | "business"
display_namestring
company_namestring
first_namestring
last_namestring
emailstring
phonestring
Generated example
{
  "account_type": "business",
  "display_name": "North Harbor Facilities",
  "company_name": "North Harbor Facilities",
  "first_name": "Nora",
  "last_name": "Harbor",
  "email": "ops@northerborfacilities.com",
  "phone": "+1-404-555-0128"
}
CustomerListResponse

Response body for customer list endpoints.

FieldType
dataCustomer[]
paginationPagination
Generated example
{
  "data": [
    {
      "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d",
      "account_type": "business",
      "display_name": "North Harbor Facilities",
      "email": "ops@northerborfacilities.com",
      "phone": "+1-404-555-0128",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Endpoints
GET/api/public/v1/customers
Private preview

List customers in the active organization with filtering and pagination.

read:customers
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
CustomerListResponse
{
  "data": [
    {
      "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d",
      "account_type": "business",
      "display_name": "North Harbor Facilities",
      "email": "ops@northerborfacilities.com",
      "phone": "+1-404-555-0128",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/customers \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.customers.list();
POST/api/public/v1/customers
Private preview

Create a customer with organization-scoped identity and contact data.

write:customers
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 409 Conflict · 429 Rate limited · 500 Internal error
Request body
CreateCustomerRequest
{
  "account_type": "business",
  "display_name": "North Harbor Facilities",
  "company_name": "North Harbor Facilities",
  "first_name": "Nora",
  "last_name": "Harbor",
  "email": "ops@northerborfacilities.com",
  "phone": "+1-404-555-0128"
}
Response body
CreateCustomerResponse
{
  "data": {
    "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d",
    "account_type": "business",
    "display_name": "North Harbor Facilities",
    "email": "ops@northerborfacilities.com",
    "phone": "+1-404-555-0128",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
409
Conflict

Duplicate customer email or conflicting customer state.

Usually happens when: A create request reuses a primary email that already belongs to another active customer in the same organization.
What to do: Fetch the existing customer first or update your upstream dedupe logic before retrying the create.
{
  "error": "Customer email already exists in this organization",
  "field": "email"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X POST \
  http://localhost:3000/api/public/v1/customers \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
  "account_type": "business",
  "display_name": "North Harbor Facilities",
  "company_name": "North Harbor Facilities",
  "first_name": "Nora",
  "last_name": "Harbor",
  "email": "ops@northerborfacilities.com",
  "phone": "+1-404-555-0128"
}'
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.customers.create({
  "account_type": "business",
  "display_name": "North Harbor Facilities",
  "company_name": "North Harbor Facilities",
  "first_name": "Nora",
  "last_name": "Harbor",
  "email": "ops@northerborfacilities.com",
  "phone": "+1-404-555-0128"
});
GET/api/public/v1/customers/{customerId}
Private preview

Retrieve one customer and related profile metadata.

read:customers
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
CustomerResponse
{
  "data": {
    "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d",
    "account_type": "business",
    "display_name": "North Harbor Facilities",
    "email": "ops@northerborfacilities.com",
    "phone": "+1-404-555-0128",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested customer record was not found.

Usually happens when: The requested `customerId` does not exist in the active organization or has been removed from the public view.
What to do: Refresh your local record cache and confirm the customer belongs to the organization id you sent.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/customers/8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3 \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.customers.get({ customerId: '8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3' });

Appointments

Private preview

Appointments expose the time-based operational core of Asteri. The public contract should cover scheduling, assignment, completion state, and event-friendly timestamps.

Base path
/api/public/v1/appointments
read:appointmentswrite:appointmentsdelete:appointments
Schemas
Appointment

Public representation of a scheduled service appointment.

FieldType
iduuid
customer_iduuid
statusAppointmentStatus
scheduled_startdatetime
scheduled_enddatetime
assigned_team_member_idsuuid[]
Generated example
{
  "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "status": "confirmed",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}
AppointmentUpsertRequest

Payload for creating or updating appointments.

FieldType
customer_iduuid
scheduled_startdatetime
scheduled_enddatetime
assigned_team_member_idsuuid[]
Generated example
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}
AppointmentListResponse

Response body for appointment collection endpoints.

FieldType
dataAppointment[]
paginationPagination
Generated example
{
  "data": [
    {
      "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "status": "confirmed",
      "scheduled_start": "2026-03-24T13:00:00.000Z",
      "scheduled_end": "2026-03-24T15:00:00.000Z",
      "assigned_team_member_ids": [
        "95cb6a1e-3792-451a-b905-011946edc191"
      ]
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Endpoints
GET/api/public/v1/appointments
Private preview

List appointments by date range, status, customer, or technician.

read:appointments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
AppointmentListResponse
{
  "data": [
    {
      "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "status": "confirmed",
      "scheduled_start": "2026-03-24T13:00:00.000Z",
      "scheduled_end": "2026-03-24T15:00:00.000Z",
      "assigned_team_member_ids": [
        "95cb6a1e-3792-451a-b905-011946edc191"
      ]
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/appointments \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.appointments.list();
POST/api/public/v1/appointments
Private preview

Create a scheduled appointment with service, technician, and timing context.

write:appointments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
Request body
AppointmentUpsertRequest
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}
Response body
CreateAppointmentResponse
{
  "data": {
    "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "confirmed",
    "scheduled_start": "2026-03-24T13:00:00.000Z",
    "scheduled_end": "2026-03-24T15:00:00.000Z",
    "assigned_team_member_ids": [
      "95cb6a1e-3792-451a-b905-011946edc191"
    ]
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X POST \
  http://localhost:3000/api/public/v1/appointments \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}'
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.appointments.create({
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
});
GET/api/public/v1/appointments/{appointmentId}
Private preview

Retrieve one appointment and assignment state.

read:appointments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
AppointmentResponse
{
  "data": {
    "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "confirmed",
    "scheduled_start": "2026-03-24T13:00:00.000Z",
    "scheduled_end": "2026-03-24T15:00:00.000Z",
    "assigned_team_member_ids": [
      "95cb6a1e-3792-451a-b905-011946edc191"
    ]
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested appointment record was not found.

Usually happens when: The requested `appointmentId` does not exist in the active organization.
What to do: Re-list appointments for the active org before retrying the direct fetch.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/appointments/10bdff89-2f4f-4f06-8595-5f971ca0119a \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.appointments.get({ appointmentId: '10bdff89-2f4f-4f06-8595-5f971ca0119a' });
PATCH/api/public/v1/appointments/{appointmentId}
Private preview

Update appointment timing, assignment, or lifecycle state.

write:appointments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
Request body
AppointmentUpsertRequest
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}
Response body
UpdateAppointmentResponse
{
  "data": {
    "id": "10bdff89-2f4f-4f06-8595-5f971ca0119a",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "confirmed",
    "scheduled_start": "2026-03-24T13:00:00.000Z",
    "scheduled_end": "2026-03-24T15:00:00.000Z",
    "assigned_team_member_ids": [
      "95cb6a1e-3792-451a-b905-011946edc191"
    ]
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested appointment record was not found.

Usually happens when: The requested `appointmentId` does not exist in the active organization.
What to do: Re-list appointments for the active org before retrying the update.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X PATCH \
  http://localhost:3000/api/public/v1/appointments/10bdff89-2f4f-4f06-8595-5f971ca0119a \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
}'
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.appointments.update({ appointmentId: '10bdff89-2f4f-4f06-8595-5f971ca0119a' }, {
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "scheduled_start": "2026-03-24T13:00:00.000Z",
  "scheduled_end": "2026-03-24T15:00:00.000Z",
  "assigned_team_member_ids": [
    "95cb6a1e-3792-451a-b905-011946edc191"
  ]
});

Estimates

Private preview

Estimates are one of Asteri’s highest-value public surfaces. The resource model should preserve quote lines, acceptance state, and approval timestamps without leaking internal UI structure.

Base path
/api/public/v1/estimates
read:estimateswrite:estimatesdelete:estimates
Schemas
Estimate

Public representation of an estimate or quote.

FieldType
iduuid
customer_iduuid
statusEstimateStatus
currencystring
subtotal_amountnumber
total_amountnumber
Generated example
{
  "id": "97e08590-a443-4725-aa47-7e3cc767a28d",
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "status": "sent",
  "currency": "USD",
  "subtotal_amount": 1825,
  "total_amount": 1950
}
CreateEstimateRequest

Payload for creating an estimate.

FieldType
customer_iduuid
currencystring
line_itemsArray<{ name: string; quantity: number; unit_price: number; }>
Generated example
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "currency": "USD",
  "line_items": [
    {
      "name": "Quarterly hood cleaning",
      "quantity": 1,
      "unit_price": 1825
    }
  ]
}
EstimateResponse

Single-estimate response body.

FieldType
dataEstimate
Generated example
{
  "data": {
    "id": "97e08590-a443-4725-aa47-7e3cc767a28d",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "currency": "USD",
    "subtotal_amount": 1825,
    "total_amount": 1950
  }
}
Endpoints
GET/api/public/v1/estimates
Private preview

List estimates with status, customer, and date-based filtering.

read:estimates
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
EstimateListResponse
{
  "data": [
    {
      "id": "97e08590-a443-4725-aa47-7e3cc767a28d",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "status": "sent",
      "currency": "USD",
      "subtotal_amount": 1825,
      "total_amount": 1950
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/estimates \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.estimates.list();
POST/api/public/v1/estimates
Private preview

Create a new estimate with service lines and pricing metadata.

write:estimates
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
Request body
CreateEstimateRequest
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "currency": "USD",
  "line_items": [
    {
      "name": "Quarterly hood cleaning",
      "quantity": 1,
      "unit_price": 1825
    }
  ]
}
Response body
EstimateResponse
{
  "data": {
    "id": "97e08590-a443-4725-aa47-7e3cc767a28d",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "currency": "USD",
    "subtotal_amount": 1825,
    "total_amount": 1950
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Referenced customer record was not found.

Usually happens when: The `customer_id` on the estimate create payload is not visible in the active organization.
What to do: Create or fetch the customer first, then retry the estimate mutation with that id.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X POST \
  http://localhost:3000/api/public/v1/estimates \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "currency": "USD",
  "line_items": [
    {
      "name": "Quarterly hood cleaning",
      "quantity": 1,
      "unit_price": 1825
    }
  ]
}'
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.estimates.create({
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "currency": "USD",
  "line_items": [
    {
      "name": "Quarterly hood cleaning",
      "quantity": 1,
      "unit_price": 1825
    }
  ]
});
GET/api/public/v1/estimates/{estimateId}
Private preview

Retrieve one estimate and current pricing status.

read:estimates
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
EstimateResponse
{
  "data": {
    "id": "97e08590-a443-4725-aa47-7e3cc767a28d",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "currency": "USD",
    "subtotal_amount": 1825,
    "total_amount": 1950
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested estimate record was not found.

Usually happens when: The requested `estimateId` does not exist in the active organization.
What to do: Re-list estimates or verify the estimate was created in the same organization context.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/estimates/97e08590-a443-4725-aa47-7e3cc767a28d \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.estimates.get({ estimateId: '97e08590-a443-4725-aa47-7e3cc767a28d' });
POST/api/public/v1/estimates/{estimateId}/send
Private preview

Trigger delivery of an estimate to the customer.

write:estimates
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
EstimateSendResponse
{
  "data": {
    "estimate_id": "97e08590-a443-4725-aa47-7e3cc767a28d",
    "status": "queued"
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested estimate record was not found.

Usually happens when: The requested `estimateId` does not exist in the active organization.
What to do: Re-fetch the estimate before retrying send, especially after local cache invalidation or org changes.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X POST \
  http://localhost:3000/api/public/v1/estimates/97e08590-a443-4725-aa47-7e3cc767a28d/send \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.estimates.send({ estimateId: '97e08590-a443-4725-aa47-7e3cc767a28d' });

Invoices

Private preview

Invoices connect work completion to revenue recognition. The public index should make invoice state transitions and payment-linked events explicit and predictable.

Base path
/api/public/v1/invoices
read:invoiceswrite:invoicesdelete:invoices
Schemas
Invoice

Public representation of an invoice record.

FieldType
iduuid
customer_iduuid
statusInvoiceStatus
payment_statusPaymentStatus
balance_duenumber
due_datedate
Generated example
{
  "id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "status": "sent",
  "payment_status": "pending",
  "balance_due": 1950,
  "due_date": "2026-04-15"
}
CreateInvoiceRequest

Payload for creating an invoice.

FieldType
customer_iduuid
due_datedate
line_itemsArray<{ name: string; quantity: number; unit_price: number; }>
Generated example
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "due_date": "2026-04-15",
  "line_items": [
    {
      "name": "Kitchen exhaust cleaning",
      "quantity": 1,
      "unit_price": 1950
    }
  ]
}
InvoiceResponse

Single-invoice response body.

FieldType
dataInvoice
Generated example
{
  "data": {
    "id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "payment_status": "pending",
    "balance_due": 1950,
    "due_date": "2026-04-15"
  }
}
Endpoints
GET/api/public/v1/invoices
Private preview

List invoices by customer, status, or date range.

read:invoices
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
InvoiceListResponse
{
  "data": [
    {
      "id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "status": "sent",
      "payment_status": "pending",
      "balance_due": 1950,
      "due_date": "2026-04-15"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/invoices \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.invoices.list();
POST/api/public/v1/invoices
Private preview

Create an invoice with line items, due dates, and balance state.

write:invoices
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
Request body
CreateInvoiceRequest
{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "due_date": "2026-04-15",
  "line_items": [
    {
      "name": "Kitchen exhaust cleaning",
      "quantity": 1,
      "unit_price": 1950
    }
  ]
}
Response body
InvoiceResponse
{
  "data": {
    "id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "payment_status": "pending",
    "balance_due": 1950,
    "due_date": "2026-04-15"
  }
}
Error responses
400
Bad request

Missing organization context or invalid request payload.

Usually happens when: The request body is missing required fields, includes invalid field values, or omits org context.
What to do: Validate the payload against the generated request schema and fix any `issues` entries before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks write scope or organization access for the mutation target.
What to do: Grant the missing write scope or use a key/session from the correct organization.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle mutation throughput and avoid blind retries from queues or worker pools.
Retry guidance: Honor the `Retry-After` header and replay writes only after the relevant limit window resets.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while validating or persisting the mutation.
What to do: Capture the response payload and contract version, then retry once before escalating.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X POST \
  http://localhost:3000/api/public/v1/invoices \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "due_date": "2026-04-15",
  "line_items": [
    {
      "name": "Kitchen exhaust cleaning",
      "quantity": 1,
      "unit_price": 1950
    }
  ]
}'
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.invoices.create({
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "due_date": "2026-04-15",
  "line_items": [
    {
      "name": "Kitchen exhaust cleaning",
      "quantity": 1,
      "unit_price": 1950
    }
  ]
});
GET/api/public/v1/invoices/{invoiceId}
Private preview

Retrieve one invoice and current balance/payment metadata.

read:invoices
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
InvoiceResponse
{
  "data": {
    "id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "status": "sent",
    "payment_status": "pending",
    "balance_due": 1950,
    "due_date": "2026-04-15"
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested invoice record was not found.

Usually happens when: The requested `invoiceId` does not exist in the active organization.
What to do: Refresh invoice ids from a list call or confirm the invoice belongs to the org in your request.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/invoices/4dd2dc3d-51a9-4e91-a225-67d1d69cf42e \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.invoices.get({ invoiceId: '4dd2dc3d-51a9-4e91-a225-67d1d69cf42e' });

Payments

Private preview

Payments extend the public revenue surface beyond invoices so downstream systems can reconcile collections and cash movement without scraping invoice state alone.

Base path
/api/public/v1/payments
read:paymentswrite:paymentsdelete:payments
Schemas
Payment

Public representation of a collected or recorded payment.

FieldType
iduuid
customer_iduuid
invoice_iduuid
amountnumber
statusPaymentStatus
payment_methodstring
payment_datedate
created_atdatetime
Generated example
{
  "id": "1fc10a48-2c62-4d17-a7f1-4b1f42cb6c84",
  "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
  "invoice_id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
  "amount": 1950,
  "status": "completed",
  "payment_method": "card",
  "payment_date": "2026-03-22",
  "created_at": "2026-03-22T15:40:12.000Z"
}
PaymentListResponse

Collection response for payments.

FieldType
dataPayment[]
paginationPagination
Generated example
{
  "data": [
    {
      "id": "1fc10a48-2c62-4d17-a7f1-4b1f42cb6c84",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "invoice_id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
      "amount": 1950,
      "status": "completed",
      "payment_method": "card",
      "payment_date": "2026-03-22",
      "created_at": "2026-03-22T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
PaymentResponse

Single-payment response body.

FieldType
dataPayment
Generated example
{
  "data": {
    "id": "1fc10a48-2c62-4d17-a7f1-4b1f42cb6c84",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "invoice_id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
    "amount": 1950,
    "status": "completed",
    "payment_method": "card",
    "payment_date": "2026-03-22",
    "created_at": "2026-03-22T15:40:12.000Z"
  }
}
Endpoints
GET/api/public/v1/payments
Private preview

List payments by customer, invoice, status, or date range.

read:payments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
PaymentListResponse
{
  "data": [
    {
      "id": "1fc10a48-2c62-4d17-a7f1-4b1f42cb6c84",
      "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
      "invoice_id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
      "amount": 1950,
      "status": "completed",
      "payment_method": "card",
      "payment_date": "2026-03-22",
      "created_at": "2026-03-22T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/payments \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.payments.list();
GET/api/public/v1/payments/{paymentId}
Private preview

Retrieve one payment and its invoice/customer linkage.

read:payments
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
PaymentResponse
{
  "data": {
    "id": "1fc10a48-2c62-4d17-a7f1-4b1f42cb6c84",
    "customer_id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "invoice_id": "4dd2dc3d-51a9-4e91-a225-67d1d69cf42e",
    "amount": 1950,
    "status": "completed",
    "payment_method": "card",
    "payment_date": "2026-03-22",
    "created_at": "2026-03-22T15:40:12.000Z"
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested payment record was not found.

Usually happens when: The requested `paymentId` does not exist in the active organization.
What to do: Refresh payments from a list call or confirm the payment belongs to the org context in your request.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/payments/sample-paymentId \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.payments.get({ paymentId: 'sample-paymentId' });

Services

Private preview

Services expose the customer-facing catalog layer so outside systems can understand what can be estimated, booked, and sold without reading internal admin UI state.

Base path
/api/public/v1/services
read:serviceswrite:servicesdelete:services
Schemas
Service

Public representation of a service catalog item.

FieldType
iduuid
namestring
descriptionstring | null
pricenumber
duration_minutesinteger
is_activeboolean
service_typestring | null
created_atdatetime
Generated example
{
  "id": "10fbd700-2b14-44c8-8b7b-f7786b3d9f74",
  "name": "Kitchen exhaust cleaning",
  "description": "Full hood, duct, and fan cleaning service.",
  "price": 1950,
  "duration_minutes": 180,
  "is_active": true,
  "service_type": "commercial_kitchen",
  "created_at": "2026-03-18T15:40:12.000Z"
}
ServiceListResponse

Collection response for services.

FieldType
dataService[]
paginationPagination
Generated example
{
  "data": [
    {
      "id": "10fbd700-2b14-44c8-8b7b-f7786b3d9f74",
      "name": "Kitchen exhaust cleaning",
      "description": "Full hood, duct, and fan cleaning service.",
      "price": 1950,
      "duration_minutes": 180,
      "is_active": true,
      "service_type": "commercial_kitchen",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
ServiceResponse

Single-service response body.

FieldType
dataService
Generated example
{
  "data": {
    "id": "10fbd700-2b14-44c8-8b7b-f7786b3d9f74",
    "name": "Kitchen exhaust cleaning",
    "description": "Full hood, duct, and fan cleaning service.",
    "price": 1950,
    "duration_minutes": 180,
    "is_active": true,
    "service_type": "commercial_kitchen",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Endpoints
GET/api/public/v1/services
Private preview

List services by active state, catalog visibility, or search query.

read:services
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
ServiceListResponse
{
  "data": [
    {
      "id": "10fbd700-2b14-44c8-8b7b-f7786b3d9f74",
      "name": "Kitchen exhaust cleaning",
      "description": "Full hood, duct, and fan cleaning service.",
      "price": 1950,
      "duration_minutes": 180,
      "is_active": true,
      "service_type": "commercial_kitchen",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/services \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.services.list();
GET/api/public/v1/services/{serviceId}
Private preview

Retrieve one service and its public catalog metadata.

read:services
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
ServiceResponse
{
  "data": {
    "id": "10fbd700-2b14-44c8-8b7b-f7786b3d9f74",
    "name": "Kitchen exhaust cleaning",
    "description": "Full hood, duct, and fan cleaning service.",
    "price": 1950,
    "duration_minutes": 180,
    "is_active": true,
    "service_type": "commercial_kitchen",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested service record was not found.

Usually happens when: The requested `serviceId` does not exist in the active organization.
What to do: Refresh the service catalog or confirm the service belongs to the org in your request.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/services/sample-serviceId \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.services.get({ serviceId: 'sample-serviceId' });

Files

Private preview

Files expose a narrow storage-facing contract so integrations can reason about uploaded assets and related metadata without depending on internal upload flows.

Base path
/api/public/v1/files
read:fileswrite:filesdelete:files
Schemas
File

Public representation of a file record managed by Asteri.

FieldType
iduuid
original_namestring
content_typestring
size_bytesinteger
statusstring
bucketstring
created_atdatetime
Generated example
{
  "id": "1e8e8395-5581-4ad0-8462-0b4780d2fdd2",
  "original_name": "before-cleaning-photo.jpg",
  "content_type": "image/jpeg",
  "size_bytes": 452301,
  "status": "ready",
  "bucket": "uploads",
  "created_at": "2026-03-18T15:40:12.000Z"
}
FileListResponse

Collection response for files.

FieldType
dataFile[]
paginationPagination
Generated example
{
  "data": [
    {
      "id": "1e8e8395-5581-4ad0-8462-0b4780d2fdd2",
      "original_name": "before-cleaning-photo.jpg",
      "content_type": "image/jpeg",
      "size_bytes": 452301,
      "status": "ready",
      "bucket": "uploads",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
FileResponse

Single-file response body.

FieldType
dataFile
Generated example
{
  "data": {
    "id": "1e8e8395-5581-4ad0-8462-0b4780d2fdd2",
    "original_name": "before-cleaning-photo.jpg",
    "content_type": "image/jpeg",
    "size_bytes": 452301,
    "status": "ready",
    "bucket": "uploads",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Endpoints
GET/api/public/v1/files
Private preview

List files by status, content type, or filename search.

read:files
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
FileListResponse
{
  "data": [
    {
      "id": "1e8e8395-5581-4ad0-8462-0b4780d2fdd2",
      "original_name": "before-cleaning-photo.jpg",
      "content_type": "image/jpeg",
      "size_bytes": 452301,
      "status": "ready",
      "bucket": "uploads",
      "created_at": "2026-03-18T15:40:12.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "limit": 25,
    "next_cursor": null
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/files \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.files.list();
GET/api/public/v1/files/{fileId}
Private preview

Retrieve one file record and current metadata state.

read:files
Common errors: 400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 429 Rate limited · 500 Internal error
No request body contract for this endpoint.
Response body
FileResponse
{
  "data": {
    "id": "1e8e8395-5581-4ad0-8462-0b4780d2fdd2",
    "original_name": "before-cleaning-photo.jpg",
    "content_type": "image/jpeg",
    "size_bytes": 452301,
    "status": "ready",
    "bucket": "uploads",
    "created_at": "2026-03-18T15:40:12.000Z"
  }
}
Error responses
400
Bad request

Missing or invalid organization context.

Usually happens when: The request omits `x-organization-id` or sends malformed query/body data.
What to do: Send the active organization id and validate request fields against the contract examples before retrying.
{
  "error": "Missing organization context. Provide x-organization-id.",
  "issues": [
    {
      "path": "email",
      "message": "Invalid email address"
    }
  ]
}
401
Unauthorized

Missing, invalid, or expired API credentials.

Usually happens when: The API key is invalid, expired, or no valid authenticated session is attached to the request.
What to do: Verify the key value, key status, expiration, and whether you are sending `x-api-key` or bearer auth correctly.
{
  "error": "Invalid API key"
}
403
Forbidden

Missing scopes or organization access for this request.

Usually happens when: The caller is authenticated but lacks the required scope or organization membership for the target resource.
What to do: Grant the missing scope in Security settings or switch to an org/key with access to the requested resource.
{
  "error": "API key missing required scopes",
  "missingScopes": [
    "write:customers"
  ]
}
404
Not found

Requested file record was not found.

Usually happens when: The requested `fileId` does not exist in the active organization or is no longer visible.
What to do: Refresh the file list or confirm the file belongs to the org context in your request.
{
  "error": "Customer not found"
}
429
Rate limited

Configured API key rate limit exceeded.

Usually happens when: The current key has exceeded its per-minute or per-day preview limit.
What to do: Throttle request volume and avoid immediate fan-out retries from parallel workers.
Retry guidance: Honor the `Retry-After` header for minute-level limits and back off longer if the `window` is `day`.
{
  "error": "API key minute rate limit exceeded",
  "limit": 60,
  "window": "minute"
}
500
Internal error

Unexpected preview API failure.

Usually happens when: The preview API hit an unexpected internal failure while reading or serializing the resource.
What to do: Log the contract version and response body, then retry once before escalating to Asteri support.
{
  "error": "Failed to fetch customers"
}
Usage
cURL
curl -X GET \
  http://localhost:3000/api/public/v1/files/sample-fileId \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.files.get({ fileId: 'sample-fileId' });

Webhook Events

Available now

Webhook event delivery is already part of the developer program. These event contracts should evolve into a formal reference set alongside the public REST surface.

Base path
/developers/webhooks
manage:webhooks
Schemas
WebhookEventPayload

Signed webhook payload delivered to subscribers.

FieldType
eventstring
timestampdatetime
dataobject
Generated example
{
  "event": "customer.created",
  "timestamp": "2026-03-20T14:30:00.000Z",
  "data": {
    "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d"
  }
}
Endpoints
POSTcustomer.created | customer.updated | customer.deleted
Available now

Customer lifecycle events delivered to subscribed webhook endpoints.

manage:webhooks
No request body contract for this endpoint.
Response body
WebhookEventPayload
{
  "event": "customer.created",
  "timestamp": "2026-03-20T14:30:00.000Z",
  "data": {
    "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d"
  }
}
Usage
cURL
curl -X POST \
  http://localhost:3000customer.created | customer.updated | customer.deleted \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.webhook-events.eventsCustomers();
POSTappointment.created | appointment.updated | appointment.completed | appointment.cancelled
Available now

Appointment lifecycle events delivered with signed payloads.

manage:webhooks
No request body contract for this endpoint.
Response body
WebhookEventPayload
{
  "event": "customer.created",
  "timestamp": "2026-03-20T14:30:00.000Z",
  "data": {
    "id": "8af2ca8e-8f31-4e6e-a46c-0703b61f7ca3",
    "organization_id": "dce3853d-4f94-4bc8-a816-4d0d74c11a9d"
  }
}
Usage
cURL
curl -X POST \
  http://localhost:3000appointment.created | appointment.updated | appointment.completed | appointment.cancelled \
  -H "x-api-key: ast_live_your_key" \
  -H "x-organization-id: ${ORG_ID}"
TypeScript SDK
import { AsteriPublicApiClient } from '@asteri/public-api-sdk';

const client = new AsteriPublicApiClient({
  baseUrl: 'http://localhost:3000',
  organizationId: process.env.ASTERI_ORG_ID!,
  apiKey: process.env.ASTERI_API_KEY!,
});

const response = await client.webhook-events.eventsAppointments();
Next evolution

The next step after this generated index is to connect these contracts to real route handlers, response serializers, and machine-readable output such as OpenAPI. The docs surface is now ready for that handoff.