openapi: 3.1.0
info:
title: Federated Personal Digital Platform API
version: 0.1.0
summary: Target API contract for the FPDP MVP
description: |
Bilingual-ready REST contract for FPDP. This specification describes the
intended MVP API; most HTTP operations are not implemented in the current
lightweight repository yet.
contact:
name: FPDP Maintainers
servers:
- url: http://localhost
description: Local development
tags:
- name: System
- name: Authentication
- name: Profiles
- name: Content
- name: External Sources
- name: Commerce
- name: Payments
- name: Administration
- name: Federation
paths:
/api/v1/health:
get:
tags: [System]
operationId: getHealth
summary: Get application health
security: []
responses:
'200':
description: Application is available
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/auth/register:
post:
tags: [Authentication]
operationId: registerOwner
summary: Register an owner and initial node
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterRequest'
responses:
'201':
description: Owner and node created
content:
application/json:
schema:
$ref: '#/components/schemas/AuthResponse'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/auth/login:
post:
tags: [Authentication]
operationId: login
summary: Authenticate with email and password
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: Authentication succeeded
content:
application/json:
schema:
$ref: '#/components/schemas/AuthResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/auth/logout:
post:
tags: [Authentication]
operationId: logout
summary: Revoke the current access token
responses:
'204':
description: Token revoked
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/me:
get:
tags: [Authentication]
operationId: getCurrentUser
summary: Get the authenticated user and node context
responses:
'200':
description: Current user context
content:
application/json:
schema:
$ref: '#/components/schemas/MeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/profiles/{handle}:
parameters:
- $ref: '#/components/parameters/Handle'
get:
tags: [Profiles]
operationId: getPublicProfile
summary: Get a public profile by handle
security: []
responses:
'200':
description: Public profile
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileResponse'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/me/profile:
patch:
tags: [Profiles]
operationId: updateMyProfile
summary: Update the authenticated owner's profile
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileUpdate'
responses:
'200':
description: Updated profile
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/posts:
get:
tags: [Content]
operationId: listPosts
summary: List visible posts
security: []
parameters:
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/SourceTypeFilter'
- name: author_handle
in: query
schema:
type: string
responses:
'200':
description: Paginated posts
content:
application/json:
schema:
$ref: '#/components/schemas/PostCollectionResponse'
post:
tags: [Content]
operationId: createPost
summary: Create a local post
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PostWrite'
responses:
'201':
description: Post created
content:
application/json:
schema:
$ref: '#/components/schemas/PostResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/posts/{postId}:
parameters:
- $ref: '#/components/parameters/PostId'
get:
tags: [Content]
operationId: getPost
summary: Get a visible post
security: []
responses:
'200':
description: Post details
content:
application/json:
schema:
$ref: '#/components/schemas/PostResponse'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Content]
operationId: updatePost
summary: Update an owned local post
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PostPatch'
responses:
'200':
description: Updated post
content:
application/json:
schema:
$ref: '#/components/schemas/PostResponse'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
delete:
tags: [Content]
operationId: deletePost
summary: Soft-delete an owned local post
responses:
'204':
description: Post deleted
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/timeline:
get:
tags: [Content]
operationId: getTimeline
summary: Get normalized local, external, and federated content
security:
- {}
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/SourceTypeFilter'
responses:
'200':
description: Paginated timeline
content:
application/json:
schema:
$ref: '#/components/schemas/PostCollectionResponse'
/api/v1/external-sources:
get:
tags: [External Sources]
operationId: listExternalSources
summary: List the owner's external sources and sync health
responses:
'200':
description: External sources
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceCollectionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [External Sources]
operationId: createExternalSource
summary: Save and schedule an external source
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceWrite'
responses:
'201':
description: Source connected
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
'502':
$ref: '#/components/responses/UpstreamError'
/api/v1/external-sources/test:
post:
tags: [External Sources]
operationId: testExternalSource
summary: Validate and preview an external source without saving it
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceTestRequest'
responses:
'200':
description: Source is reachable and preview was normalized
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceTestResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
'502':
$ref: '#/components/responses/UpstreamError'
/api/v1/external-sources/{sourceId}:
parameters:
- $ref: '#/components/parameters/SourceId'
get:
tags: [External Sources]
operationId: getExternalSource
summary: Get source configuration and sync health
responses:
'200':
description: External source
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceResponse'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [External Sources]
operationId: updateExternalSource
summary: Update source interval, visibility, or enabled state
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceUpdate'
responses:
'200':
description: Updated source
content:
application/json:
schema:
$ref: '#/components/schemas/ExternalSourceResponse'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
delete:
tags: [External Sources]
operationId: disconnectExternalSource
summary: Disconnect an external source
parameters:
- name: purge_imported_content
in: query
description: Remove previously imported posts when true
schema:
type: boolean
default: false
responses:
'204':
description: Source disconnected
'404':
$ref: '#/components/responses/NotFound'
/api/v1/external-sources/{sourceId}/sync:
parameters:
- $ref: '#/components/parameters/SourceId'
post:
tags: [External Sources]
operationId: syncExternalSource
summary: Queue an on-demand source synchronization
responses:
'202':
description: Synchronization queued
content:
application/json:
schema:
$ref: '#/components/schemas/JobResponse'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
/api/v1/products:
get:
tags: [Commerce]
operationId: listProducts
summary: List available products
security: []
parameters:
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: Products
content:
application/json:
schema:
$ref: '#/components/schemas/ProductCollectionResponse'
post:
tags: [Commerce]
operationId: createProduct
summary: Create a local product
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductWrite'
responses:
'201':
description: Product created
content:
application/json:
schema:
$ref: '#/components/schemas/ProductResponse'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/products/{productId}:
parameters:
- $ref: '#/components/parameters/ProductId'
get:
tags: [Commerce]
operationId: getProduct
summary: Get product details
security: []
responses:
'200':
description: Product details
content:
application/json:
schema:
$ref: '#/components/schemas/ProductResponse'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Commerce]
operationId: updateProduct
summary: Update an owned product
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductWrite'
responses:
'200':
description: Updated product
content:
application/json:
schema:
$ref: '#/components/schemas/ProductResponse'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/orders:
post:
tags: [Commerce]
operationId: createOrder
summary: Create an order with an immutable price snapshot
security:
- {}
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OrderCreate'
responses:
'201':
description: Order created
content:
application/json:
schema:
$ref: '#/components/schemas/OrderResponse'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/orders/{orderId}:
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/OrderToken'
get:
tags: [Commerce]
operationId: getOrder
summary: Get an order
security:
- bearerAuth: []
- orderToken: []
responses:
'200':
description: Order details
content:
application/json:
schema:
$ref: '#/components/schemas/OrderResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/orders/{orderId}/payments:
parameters:
- $ref: '#/components/parameters/OrderId'
- $ref: '#/components/parameters/OrderToken'
- $ref: '#/components/parameters/IdempotencyKey'
post:
tags: [Payments]
operationId: createPayment
summary: Create an idempotent payment attempt
security:
- bearerAuth: []
- orderToken: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentCreate'
responses:
'201':
description: Payment attempt created
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
'502':
$ref: '#/components/responses/UpstreamError'
/api/v1/payments/{paymentId}:
parameters:
- $ref: '#/components/parameters/PaymentId'
- $ref: '#/components/parameters/OrderToken'
get:
tags: [Payments]
operationId: getPayment
summary: Get normalized payment state
security:
- bearerAuth: []
- orderToken: []
responses:
'200':
description: Payment details
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/payments/{paymentId}/cancel:
parameters:
- $ref: '#/components/parameters/PaymentId'
- $ref: '#/components/parameters/OrderToken'
post:
tags: [Payments]
operationId: cancelPayment
summary: Cancel a pending payment
security:
- bearerAuth: []
- orderToken: []
responses:
'200':
description: Payment cancelled
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'409':
$ref: '#/components/responses/Conflict'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/payments/{paymentId}/refunds:
parameters:
- $ref: '#/components/parameters/PaymentId'
- $ref: '#/components/parameters/IdempotencyKey'
post:
tags: [Payments]
operationId: refundPayment
summary: Request a full or partial refund
x-required-role: ADMIN
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RefundCreate'
responses:
'202':
description: Refund accepted
content:
application/json:
schema:
$ref: '#/components/schemas/RefundResponse'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/webhooks/payments/{gatewayCode}:
parameters:
- $ref: '#/components/parameters/GatewayCode'
post:
tags: [Payments]
operationId: receivePaymentWebhook
summary: Receive a signed payment-provider webhook
description: Signature headers are provider-specific. Valid duplicates return 200.
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
'200':
description: Event verified and acknowledged
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookAcknowledgement'
'401':
description: Signature verification failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/admin/payment-gateways:
get:
tags: [Administration]
operationId: listPaymentGateways
summary: List gateway capabilities and configuration state
x-required-role: ADMIN
responses:
'200':
description: Gateway definitions
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentGatewayCollectionResponse'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/admin/payment-gateways/{gatewayCode}:
parameters:
- $ref: '#/components/parameters/GatewayCode'
put:
tags: [Administration]
operationId: configurePaymentGateway
summary: Configure and activate a payment gateway
x-required-role: ADMIN
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentGatewayConfigWrite'
responses:
'200':
description: Gateway configured; secrets are masked
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentGatewayResponse'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
/api/v1/admin/integration-jobs:
get:
tags: [Administration]
operationId: listIntegrationJobs
summary: Inspect external integration jobs
x-required-role: ADMIN
parameters:
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
- name: status
in: query
schema:
$ref: '#/components/schemas/JobStatus'
responses:
'200':
description: Integration jobs
content:
application/json:
schema:
$ref: '#/components/schemas/JobCollectionResponse'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/admin/integration-jobs/{jobId}/retry:
parameters:
- $ref: '#/components/parameters/JobId'
post:
tags: [Administration]
operationId: retryIntegrationJob
summary: Retry a failed integration job
x-required-role: ADMIN
responses:
'202':
description: Retry queued
content:
application/json:
schema:
$ref: '#/components/schemas/JobResponse'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
/.well-known/fpdp:
get:
tags: [Federation]
operationId: discoverNode
summary: Discover the node identity and capabilities
security: []
responses:
'200':
description: Node discovery document
content:
application/json:
schema:
$ref: '#/components/schemas/NodeDiscovery'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque
orderToken:
type: apiKey
in: header
name: X-Order-Token
parameters:
Cursor:
name: cursor
in: query
schema:
type: string
maxLength: 512
Limit:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
Handle:
name: handle
in: path
required: true
schema:
type: string
pattern: '^[a-z0-9][a-z0-9-]{2,62}$'
PostId:
name: postId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
SourceId:
name: sourceId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
ProductId:
name: productId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
OrderId:
name: orderId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
PaymentId:
name: paymentId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
JobId:
name: jobId
in: path
required: true
schema: { $ref: '#/components/schemas/Uuid' }
GatewayCode:
name: gatewayCode
in: path
required: true
schema:
type: string
pattern: '^[A-Z][A-Z0-9_]{1,63}$'
examples: [DUMMY, MIDTRANS]
OrderToken:
name: X-Order-Token
in: header
required: false
description: Guest capability token; do not place it in URLs or logs.
schema:
type: string
minLength: 32
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 16
maxLength: 128
SourceTypeFilter:
name: source_type
in: query
schema:
type: array
items:
$ref: '#/components/schemas/SourceType'
style: form
explode: false
responses:
Unauthorized:
description: Authentication is missing or invalid
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
Forbidden:
description: Authenticated principal is not allowed to perform this operation
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
NotFound:
description: Resource does not exist or is not visible to the caller
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
Conflict:
description: Resource state or idempotency conflict
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
ValidationError:
description: Request failed semantic validation
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
UpstreamError:
description: An external provider failed or returned an invalid response
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
ServiceUnavailable:
description: Application or a critical dependency is unavailable
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
schemas:
Uuid:
type: string
format: uuid
Timestamp:
type: string
format: date-time
Uri:
type: string
format: uri
maxLength: 2048
RequestMeta:
type: object
required: [request_id]
properties:
request_id:
type: string
PaginationMeta:
allOf:
- $ref: '#/components/schemas/RequestMeta'
- type: object
required: [has_more]
properties:
next_cursor:
type: [string, 'null']
has_more:
type: boolean
ErrorDetail:
type: object
properties:
field:
type: string
reason:
type: string
Error:
type: object
required: [code, message, request_id]
properties:
code:
type: string
examples: [VALIDATION_ERROR]
message:
type: string
details:
type: array
items: { $ref: '#/components/schemas/ErrorDetail' }
request_id:
type: string
ErrorResponse:
type: object
required: [error]
properties:
error: { $ref: '#/components/schemas/Error' }
HealthResponse:
type: object
required: [data, meta]
properties:
data:
type: object
required: [status, version, dependencies]
properties:
status:
type: string
enum: [OK, DEGRADED]
version:
type: string
dependencies:
type: object
additionalProperties:
type: string
enum: [UP, DOWN, UNKNOWN]
meta: { $ref: '#/components/schemas/RequestMeta' }
RegisterRequest:
type: object
additionalProperties: false
required: [email, password, handle, display_name]
properties:
email:
type: string
format: email
maxLength: 254
password:
type: string
format: password
minLength: 12
maxLength: 128
handle:
type: string
pattern: '^[a-z0-9][a-z0-9-]{2,62}$'
display_name:
type: string
minLength: 1
maxLength: 128
locale:
type: string
enum: [en, id]
default: id
LoginRequest:
type: object
additionalProperties: false
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
format: password
User:
type: object
required: [id, email, role, created_at]
properties:
id: { $ref: '#/components/schemas/Uuid' }
email:
type: string
format: email
role:
type: string
enum: [OWNER, ADMIN]
created_at: { $ref: '#/components/schemas/Timestamp' }
Node:
type: object
required: [id, domain, status]
properties:
id: { $ref: '#/components/schemas/Uuid' }
domain:
type: string
format: hostname
status:
type: string
enum: [PROVISIONING, ACTIVE, SUSPENDED]
Token:
type: object
required: [access_token, token_type, expires_in]
properties:
access_token:
type: string
token_type:
type: string
const: Bearer
expires_in:
type: integer
minimum: 1
AuthData:
type: object
required: [user, node, token]
properties:
user: { $ref: '#/components/schemas/User' }
node: { $ref: '#/components/schemas/Node' }
token: { $ref: '#/components/schemas/Token' }
AuthResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/AuthData' }
meta: { $ref: '#/components/schemas/RequestMeta' }
MeData:
type: object
required: [user, node, profile]
properties:
user: { $ref: '#/components/schemas/User' }
node: { $ref: '#/components/schemas/Node' }
profile: { $ref: '#/components/schemas/Profile' }
MeResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/MeData' }
meta: { $ref: '#/components/schemas/RequestMeta' }
Visibility:
type: string
enum: [PUBLIC, UNLISTED, PRIVATE]
Profile:
type: object
required: [id, handle, display_name, visibility, canonical_url]
properties:
id: { $ref: '#/components/schemas/Uuid' }
handle:
type: string
display_name:
type: string
bio:
type: [string, 'null']
maxLength: 2000
avatar_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
links:
type: array
maxItems: 20
items:
type: object
required: [label, url]
properties:
label:
type: string
maxLength: 64
url: { $ref: '#/components/schemas/Uri' }
visibility: { $ref: '#/components/schemas/Visibility' }
canonical_url: { $ref: '#/components/schemas/Uri' }
ProfileUpdate:
type: object
additionalProperties: false
minProperties: 1
properties:
display_name:
type: string
minLength: 1
maxLength: 128
bio:
type: [string, 'null']
maxLength: 2000
avatar_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
visibility: { $ref: '#/components/schemas/Visibility' }
ProfileResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Profile' }
meta: { $ref: '#/components/schemas/RequestMeta' }
SourceType:
type: string
enum: [LOCAL, EXTERNAL, FEDERATED]
PostType:
type: string
enum: [NOTE, ARTICLE, MEDIA]
Author:
type: object
required: [display_name]
properties:
handle:
type: [string, 'null']
display_name:
type: string
profile_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
avatar_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
Media:
type: object
additionalProperties: false
required: [type, url]
properties:
type:
type: string
enum: [IMAGE, VIDEO, AUDIO, FILE]
url:
type: string
format: uri
maxLength: 2048
pattern: '^https://'
description: HTTPS URL without embedded user credentials.
alt_text:
type: [string, 'null']
maxLength: 500
Post:
type: object
required:
- id
- post_type
- source_type
- source_provider
- canonical_url
- author
- visibility
- published_at
properties:
id: { $ref: '#/components/schemas/Uuid' }
title:
type: [string, 'null']
maxLength: 255
content:
type: string
post_type: { $ref: '#/components/schemas/PostType' }
source_type: { $ref: '#/components/schemas/SourceType' }
source_provider:
type: string
examples: [FPDP, RSS, ATOM, CUSTOM_API]
canonical_url: { $ref: '#/components/schemas/Uri' }
author: { $ref: '#/components/schemas/Author' }
media:
type: array
items: { $ref: '#/components/schemas/Media' }
visibility: { $ref: '#/components/schemas/Visibility' }
published_at: { $ref: '#/components/schemas/Timestamp' }
updated_at: { $ref: '#/components/schemas/Timestamp' }
PostWrite:
type: object
additionalProperties: false
required: [content, post_type, visibility]
properties:
title:
type: [string, 'null']
maxLength: 255
content:
type: string
minLength: 1
maxLength: 100000
post_type: { $ref: '#/components/schemas/PostType' }
visibility: { $ref: '#/components/schemas/Visibility' }
published_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
media:
type: array
maxItems: 10
items: { $ref: '#/components/schemas/Media' }
PostPatch:
type: object
additionalProperties: false
minProperties: 1
properties:
title:
type: [string, 'null']
maxLength: 255
content:
type: string
minLength: 1
maxLength: 100000
post_type: { $ref: '#/components/schemas/PostType' }
visibility: { $ref: '#/components/schemas/Visibility' }
published_at:
description: Set to null to return the post to draft status.
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
media:
description: Replaces the complete ordered media list when provided.
type: array
maxItems: 10
items: { $ref: '#/components/schemas/Media' }
PostResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Post' }
meta: { $ref: '#/components/schemas/RequestMeta' }
PostCollectionResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/Post' }
meta: { $ref: '#/components/schemas/PaginationMeta' }
ExternalProvider:
type: string
enum: [RSS, ATOM, CUSTOM_API]
ExternalSourceStatus:
type: string
enum: [PENDING, ACTIVE, ERROR, DISCONNECTED]
ExternalSource:
type: object
required: [id, provider, source_url, sync_enabled, sync_interval, status]
properties:
id: { $ref: '#/components/schemas/Uuid' }
provider: { $ref: '#/components/schemas/ExternalProvider' }
source_url: { $ref: '#/components/schemas/Uri' }
display_name:
type: [string, 'null']
sync_enabled:
type: boolean
sync_interval:
type: integer
minimum: 300
maximum: 604800
description: Interval in seconds
default_visibility: { $ref: '#/components/schemas/Visibility' }
status: { $ref: '#/components/schemas/ExternalSourceStatus' }
last_sync_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
next_sync_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
last_error:
type: [string, 'null']
description: Sanitized error; credentials must never be included.
ExternalSourceWrite:
type: object
additionalProperties: false
required: [provider, source_url]
properties:
provider: { $ref: '#/components/schemas/ExternalProvider' }
source_url: { $ref: '#/components/schemas/Uri' }
sync_enabled:
type: boolean
default: true
sync_interval:
type: integer
minimum: 300
maximum: 604800
default: 3600
default_visibility:
$ref: '#/components/schemas/Visibility'
credentials:
type: object
additionalProperties:
type: string
writeOnly: true
description: Encrypted at rest and never returned.
ExternalSourceUpdate:
type: object
additionalProperties: false
minProperties: 1
properties:
sync_enabled:
type: boolean
sync_interval:
type: integer
minimum: 300
maximum: 604800
default_visibility: { $ref: '#/components/schemas/Visibility' }
ExternalSourceTestRequest:
allOf:
- $ref: '#/components/schemas/ExternalSourceWrite'
ExternalSourceTestData:
type: object
required: [valid, provider, preview]
properties:
valid:
type: boolean
provider: { $ref: '#/components/schemas/ExternalProvider' }
display_name:
type: [string, 'null']
preview:
type: array
maxItems: 5
items: { $ref: '#/components/schemas/Post' }
warnings:
type: array
items:
type: string
ExternalSourceTestResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/ExternalSourceTestData' }
meta: { $ref: '#/components/schemas/RequestMeta' }
ExternalSourceResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/ExternalSource' }
meta: { $ref: '#/components/schemas/RequestMeta' }
ExternalSourceCollectionResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/ExternalSource' }
meta: { $ref: '#/components/schemas/RequestMeta' }
Money:
type: object
required: [amount, currency]
properties:
amount:
type: string
pattern: '^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$'
examples: ['250000.00']
currency:
type: string
pattern: '^[A-Z]{3}$'
examples: [IDR]
ProductStatus:
type: string
enum: [DRAFT, ACTIVE, INACTIVE]
Product:
type: object
required: [id, name, price, status, source_type, canonical_url]
properties:
id: { $ref: '#/components/schemas/Uuid' }
name:
type: string
description:
type: [string, 'null']
price: { $ref: '#/components/schemas/Money' }
stock:
type: [integer, 'null']
minimum: 0
status: { $ref: '#/components/schemas/ProductStatus' }
source_type: { $ref: '#/components/schemas/SourceType' }
canonical_url: { $ref: '#/components/schemas/Uri' }
ProductWrite:
type: object
additionalProperties: false
required: [name, price, status]
properties:
name:
type: string
minLength: 1
maxLength: 255
description:
type: [string, 'null']
maxLength: 10000
price: { $ref: '#/components/schemas/Money' }
stock:
type: [integer, 'null']
minimum: 0
status: { $ref: '#/components/schemas/ProductStatus' }
ProductResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Product' }
meta: { $ref: '#/components/schemas/RequestMeta' }
ProductCollectionResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/Product' }
meta: { $ref: '#/components/schemas/PaginationMeta' }
OrderStatus:
type: string
enum: [CREATED, AWAITING_PAYMENT, PAID, CANCELLED, FULFILLED, REFUNDED]
OrderLineCreate:
type: object
additionalProperties: false
required: [product_id, quantity]
properties:
product_id: { $ref: '#/components/schemas/Uuid' }
quantity:
type: integer
minimum: 1
maximum: 999
Customer:
type: object
required: [email, name]
properties:
email:
type: string
format: email
name:
type: string
maxLength: 128
OrderCreate:
type: object
additionalProperties: false
required: [items, customer]
properties:
items:
type: array
minItems: 1
maxItems: 100
items: { $ref: '#/components/schemas/OrderLineCreate' }
customer: { $ref: '#/components/schemas/Customer' }
OrderLine:
type: object
required: [product_id, name, quantity, unit_price, subtotal]
properties:
product_id: { $ref: '#/components/schemas/Uuid' }
name:
type: string
quantity:
type: integer
unit_price: { $ref: '#/components/schemas/Money' }
subtotal: { $ref: '#/components/schemas/Money' }
Order:
type: object
required: [id, status, items, total, customer, created_at]
properties:
id: { $ref: '#/components/schemas/Uuid' }
status: { $ref: '#/components/schemas/OrderStatus' }
items:
type: array
items: { $ref: '#/components/schemas/OrderLine' }
total: { $ref: '#/components/schemas/Money' }
customer: { $ref: '#/components/schemas/Customer' }
created_at: { $ref: '#/components/schemas/Timestamp' }
order_token:
type: string
writeOnly: true
description: Returned only at guest order creation.
OrderResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Order' }
meta: { $ref: '#/components/schemas/RequestMeta' }
PaymentStatus:
type: string
enum: [PENDING, PAID, FAILED, EXPIRED, CANCELLED, PARTIALLY_REFUNDED, REFUNDED]
PaymentCreate:
type: object
additionalProperties: false
properties:
gateway_code:
type: string
pattern: '^[A-Z][A-Z0-9_]{1,63}$'
payment_method:
type: [string, 'null']
maxLength: 64
return_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
Payment:
type: object
required: [id, order_id, gateway_code, amount, status, created_at]
properties:
id: { $ref: '#/components/schemas/Uuid' }
order_id: { $ref: '#/components/schemas/Uuid' }
gateway_code:
type: string
external_transaction_id:
type: [string, 'null']
payment_method:
type: [string, 'null']
amount: { $ref: '#/components/schemas/Money' }
fee: { $ref: '#/components/schemas/Money' }
status: { $ref: '#/components/schemas/PaymentStatus' }
payment_url:
oneOf:
- $ref: '#/components/schemas/Uri'
- type: 'null'
expires_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
paid_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
created_at: { $ref: '#/components/schemas/Timestamp' }
PaymentResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Payment' }
meta: { $ref: '#/components/schemas/RequestMeta' }
RefundCreate:
type: object
additionalProperties: false
required: [amount, reason]
properties:
amount: { $ref: '#/components/schemas/Money' }
reason:
type: string
minLength: 1
maxLength: 500
Refund:
type: object
required: [id, payment_id, amount, status, created_at]
properties:
id: { $ref: '#/components/schemas/Uuid' }
payment_id: { $ref: '#/components/schemas/Uuid' }
amount: { $ref: '#/components/schemas/Money' }
status:
type: string
enum: [PENDING, SUCCEEDED, FAILED]
created_at: { $ref: '#/components/schemas/Timestamp' }
RefundResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/Refund' }
meta: { $ref: '#/components/schemas/RequestMeta' }
WebhookAcknowledgement:
type: object
required: [received, duplicate]
properties:
received:
type: boolean
const: true
duplicate:
type: boolean
PaymentGateway:
type: object
required: [code, name, status, environment, capabilities, configured]
properties:
code:
type: string
name:
type: string
status:
type: string
enum: [ACTIVE, INACTIVE]
environment:
type: string
enum: [SANDBOX, PRODUCTION]
capabilities:
type: array
items:
type: string
enum: [REFUND, RECURRING, QRIS, VA, CREDIT_CARD, EWALLET]
configured:
type: boolean
masked_configuration:
type: object
additionalProperties:
type: string
readOnly: true
PaymentGatewayConfigWrite:
type: object
additionalProperties: false
required: [status, environment, configuration]
properties:
status:
type: string
enum: [ACTIVE, INACTIVE]
environment:
type: string
enum: [SANDBOX, PRODUCTION]
configuration:
type: object
additionalProperties:
type: string
writeOnly: true
PaymentGatewayResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/PaymentGateway' }
meta: { $ref: '#/components/schemas/RequestMeta' }
PaymentGatewayCollectionResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/PaymentGateway' }
meta: { $ref: '#/components/schemas/RequestMeta' }
JobStatus:
type: string
enum: [QUEUED, RUNNING, SUCCEEDED, FAILED, RETRY_SCHEDULED]
IntegrationJob:
type: object
required: [id, provider, job_type, status, retry_count, created_at]
properties:
id: { $ref: '#/components/schemas/Uuid' }
provider:
type: string
job_type:
type: string
status: { $ref: '#/components/schemas/JobStatus' }
retry_count:
type: integer
minimum: 0
next_retry_at:
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
last_error:
type: [string, 'null']
description: Sanitized; must not contain credentials.
created_at: { $ref: '#/components/schemas/Timestamp' }
JobResponse:
type: object
required: [data, meta]
properties:
data: { $ref: '#/components/schemas/IntegrationJob' }
meta: { $ref: '#/components/schemas/RequestMeta' }
JobCollectionResponse:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/IntegrationJob' }
meta: { $ref: '#/components/schemas/PaginationMeta' }
NodeDiscovery:
type: object
required: [node_id, domain, protocol_version, api_base_url, capabilities]
properties:
node_id: { $ref: '#/components/schemas/Uuid' }
domain:
type: string
format: hostname
protocol_version:
type: string
api_base_url: { $ref: '#/components/schemas/Uri' }
capabilities:
type: array
items:
type: string
enum: [PROFILE, CONTENT, FEDERATION, PRODUCTS, ORDERS, PAYMENTS]
public_key:
type: [string, 'null']
security:
- bearerAuth: []