Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions server/src/schemas/catalog-openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,89 @@ registry.registerPath({
404: { description: 'Dispute not found', content: { 'application/json': { schema: ErrorSchema } } },
},
});

// ── GET /api/registry/catalog (browse) ──────────────────────────
// The read/consume side of the fact loop: resolve identifiers → sync/browse
// the catalog locally → build lists.

const CatalogBrowseEntrySchema = z.object({
property_rid: z.string(),
property_id: z.string().nullable(),
classification: z.string().openapi({ example: 'property' }),
source: z.string().openapi({ example: 'adagents_json' }),
status: z.string().openapi({ example: 'active' }),
identifiers: z.array(CatalogIdentifierSchema),
}).openapi('CatalogBrowseEntry');

const CatalogBrowseResponseSchema = z.object({
entries: z.array(CatalogBrowseEntrySchema),
total: z.number().int(),
next_cursor: z.string().nullable().openapi({ description: 'Opaque cursor for the next page; null when exhausted.' }),
}).openapi('CatalogBrowseResponse');

registry.registerPath({
method: 'get',
path: '/api/registry/catalog',
operationId: 'browseCatalog',
summary: 'Browse the property catalog',
description:
'Browse the property fact-graph with filters. Cursor-paginated (opaque `cursor` → `next_cursor`). Each entry carries the property\'s identifiers. `property_rid` is a non-authoritative join/match handle, never an authorization credential.',
tags: ['Property Catalog'],
request: {
query: z.object({
classification: z.string().optional().openapi({ example: 'property' }),
source: z.string().optional(),
status: z.string().optional().openapi({ example: 'active' }),
identifier_type: z.string().optional().openapi({ example: 'domain' }),
search: z.string().optional(),
min_resolves: z.string().optional().openapi({ description: 'Minimum lifetime resolve count (integer).' }),
active_since: z.string().optional().openapi({ description: 'ISO 8601 — only properties with resolve activity since this time.' }),
limit: z.string().optional().openapi({ description: 'Page size (integer).' }),
cursor: z.string().optional().openapi({ description: 'Opaque pagination cursor from a prior `next_cursor`.' }),
}),
},
responses: {
200: { description: 'Catalog page', content: { 'application/json': { schema: CatalogBrowseResponseSchema } } },
},
});

// ── GET /api/registry/catalog/sync (delta) ──────────────────────

const CatalogSyncEntrySchema = z.object({
property_rid: z.string(),
property_id: z.string().nullable(),
classification: z.string(),
source: z.string(),
status: z.string(),
adagents_url: z.string().nullable(),
created_by: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
source_updated_at: z.string(),
}).openapi('CatalogSyncEntry');

const CatalogSyncResponseSchema = z.object({
entries: z.array(CatalogSyncEntrySchema),
server_timestamp: z.string().openapi({ description: 'Watermark to pass as `since` on the next sync (avoids clock skew).' }),
next_cursor: z.string().nullable(),
}).openapi('CatalogSyncResponse');

registry.registerPath({
method: 'get',
path: '/api/registry/catalog/sync',
operationId: 'syncCatalog',
summary: 'Sync catalog changes since a watermark',
description:
'Delta sync for local mirrors: returns catalog entries created/updated since `since` (a prior `server_timestamp`), capped at 10,000 per page. Pagination is by the returned `server_timestamp` watermark, distinct from browseCatalog\'s opaque cursor.',
tags: ['Property Catalog'],
request: {
query: z.object({
since: z.string().openapi({ description: 'Watermark from a prior response `server_timestamp` (required).', example: '2026-03-27T10:00:00Z' }),
limit: z.string().optional().openapi({ description: 'Page size, capped at 10,000.' }),
}),
},
responses: {
200: { description: 'Catalog delta', content: { 'application/json': { schema: CatalogSyncResponseSchema } } },
400: { description: 'Missing required `since` parameter', content: { 'application/json': { schema: ErrorSchema } } },
},
});
212 changes: 212 additions & 0 deletions static/openapi/registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3415,6 +3415,111 @@ components:
- status
- created_at
additionalProperties: {}
CatalogBrowseResponse:
type: object
properties:
entries:
type: array
items:
$ref: "#/components/schemas/CatalogBrowseEntry"
total:
type: integer
next_cursor:
type:
- string
- "null"
description: Opaque cursor for the next page; null when exhausted.
required:
- entries
- total
- next_cursor
CatalogBrowseEntry:
type: object
properties:
property_rid:
type: string
property_id:
type:
- string
- "null"
classification:
type: string
example: property
source:
type: string
example: adagents_json
status:
type: string
example: active
identifiers:
type: array
items:
$ref: "#/components/schemas/CatalogIdentifier"
required:
- property_rid
- property_id
- classification
- source
- status
- identifiers
CatalogSyncResponse:
type: object
properties:
entries:
type: array
items:
$ref: "#/components/schemas/CatalogSyncEntry"
server_timestamp:
type: string
description: Watermark to pass as `since` on the next sync (avoids clock skew).
next_cursor:
type:
- string
- "null"
required:
- entries
- server_timestamp
- next_cursor
CatalogSyncEntry:
type: object
properties:
property_rid:
type: string
property_id:
type:
- string
- "null"
classification:
type: string
source:
type: string
status:
type: string
adagents_url:
type:
- string
- "null"
created_by:
type:
- string
- "null"
created_at:
type: string
updated_at:
type: string
source_updated_at:
type: string
required:
- property_rid
- property_id
- classification
- source
- status
- adagents_url
- created_by
- created_at
- updated_at
- source_updated_at
AdagentsJson:
type: object
properties:
Expand Down Expand Up @@ -10647,6 +10752,113 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
/api/registry/catalog:
get:
operationId: browseCatalog
summary: Browse the property catalog
description: Browse the property fact-graph with filters. Cursor-paginated (opaque `cursor` → `next_cursor`). Each entry carries the property's identifiers. `property_rid` is a non-authoritative join/match handle, never an authorization credential.
tags:
- Property Catalog
parameters:
- schema:
type: string
example: property
required: false
name: classification
in: query
- schema:
type: string
required: false
name: source
in: query
- schema:
type: string
example: active
required: false
name: status
in: query
- schema:
type: string
example: domain
required: false
name: identifier_type
in: query
- schema:
type: string
required: false
name: search
in: query
- schema:
type: string
description: Minimum lifetime resolve count (integer).
required: false
description: Minimum lifetime resolve count (integer).
name: min_resolves
in: query
- schema:
type: string
description: ISO 8601 — only properties with resolve activity since this time.
required: false
description: ISO 8601 — only properties with resolve activity since this time.
name: active_since
in: query
- schema:
type: string
description: Page size (integer).
required: false
description: Page size (integer).
name: limit
in: query
- schema:
type: string
description: Opaque pagination cursor from a prior `next_cursor`.
required: false
description: Opaque pagination cursor from a prior `next_cursor`.
name: cursor
in: query
responses:
"200":
description: Catalog page
content:
application/json:
schema:
$ref: "#/components/schemas/CatalogBrowseResponse"
/api/registry/catalog/sync:
get:
operationId: syncCatalog
summary: Sync catalog changes since a watermark
description: "Delta sync for local mirrors: returns catalog entries created/updated since `since` (a prior `server_timestamp`), capped at 10,000 per page. Pagination is by the returned `server_timestamp` watermark, distinct from browseCatalog's opaque cursor."
tags:
- Property Catalog
parameters:
- schema:
type: string
description: Watermark from a prior response `server_timestamp` (required).
example: 2026-03-27T10:00:00Z
required: true
description: Watermark from a prior response `server_timestamp` (required).
name: since
in: query
- schema:
type: string
description: Page size, capped at 10,000.
required: false
description: Page size, capped at 10,000.
name: limit
in: query
responses:
"200":
description: Catalog delta
content:
application/json:
schema:
$ref: "#/components/schemas/CatalogSyncResponse"
"400":
description: Missing required `since` parameter
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
tags:
- name: Onboarding
description: Explicitly bootstrap a third-party integration into the AAO registry. Most callers don't need this tag — `POST /api/me/agents` auto-creates the org (for fresh users) and the member profile (for first-time agent registration) without a separate round trip. Use `POST /api/organizations` only when you need to override the auto-derived org name / company_type / revenue_tier. Tier transitions happen via the billing flow only; the Stripe webhook is the sole writer of `organizations.membership_tier`.
Expand Down
Loading