Files
flatrender/backend/contracts/rest/identity.openapi.yaml
T
soroush.asadi 90ac0b81d1 feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file,
render, notification, gateway (Go) services with vendored deps, plus DB
migrations, event/API contracts, and an init-db script.

Wire the Next.js frontend to the gateway: server-side JWT auth routes
(login/register/refresh/logout/me), gateway fetch helper, and session/
cookie/jwt helpers under src/lib.

Containerize the stack via docker-compose.v2.yml and per-service
Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and
MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via
next/font/local to avoid Google Fonts (geo-blocked).

Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:29:31 +03:30

1304 lines
40 KiB
YAML

openapi: 3.0.3
info:
title: FlatRender Identity Service (internal)
version: 1.0.0
description: |
Internal API for the Identity Service. Owned by .NET service.
Handles users, tenants, plans, payments, MFA, sessions.
Called by the Gateway and by other services via service tokens.
servers:
- url: http://identity-svc.internal/v1
security:
- BearerAuth: []
- ServiceToken: []
# Common types are referenced from ../common/types.yaml
# In production this is bundled via openapi-merge or similar.
tags:
- name: Auth
- name: Users
- name: Tenants
- name: Plans
- name: Payments
- name: Discounts
- name: ApiKeys
- name: Webhooks
- name: Gamification
- name: Admin
paths:
# ===================== AUTH =====================
/auth/register:
post:
tags: [Auth]
summary: Register a new user (email/password)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_slug, password]
properties:
tenant_slug: { type: string }
email: { type: string, format: email }
phone_number: { type: string }
password: { type: string, minLength: 8 }
full_name: { type: string }
affiliate_code: { type: string }
accept_terms: { type: boolean }
responses:
'201':
description: Created (verification email/SMS sent)
content:
application/json:
schema:
type: object
properties:
user_id: { type: string, format: uuid }
verification_required: { type: boolean }
/auth/login:
post:
tags: [Auth]
summary: Login with email/phone + password
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_slug, password]
properties:
tenant_slug: { type: string }
email: { type: string }
phone_number: { type: string }
password: { type: string }
device_id: { type: string }
device_name: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AuthTokens'
'401': { description: Invalid credentials }
'403': { description: MFA required, returns mfa_token }
/auth/login/oauth/{provider}:
post:
tags: [Auth]
summary: OAuth login (google/telegram)
parameters:
- name: provider
in: path
required: true
schema: { type: string, enum: [google, telegram] }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_slug, code]
properties:
tenant_slug: { type: string }
code: { type: string }
redirect_uri: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/AuthTokens' }
/auth/refresh:
post:
tags: [Auth]
summary: Exchange refresh token for new access token
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [refresh_token]
properties:
refresh_token: { type: string }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AuthTokens' }
/auth/logout:
post:
tags: [Auth]
summary: Revoke current session
responses:
'204': { description: Logged out }
/auth/sessions:
get:
tags: [Auth]
summary: List active sessions for current user
responses:
'200':
content:
application/json:
schema:
type: object
properties:
sessions:
type: array
items: { $ref: '#/components/schemas/Session' }
/auth/sessions/{session_id}:
delete:
tags: [Auth]
summary: Revoke a specific session
parameters:
- { name: session_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Revoked }
/auth/verify/email:
post:
tags: [Auth]
summary: Verify email via OTP code
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, code]
properties:
token: { type: string }
code: { type: string }
responses:
'200': { description: Verified }
/auth/verify/phone:
post:
tags: [Auth]
summary: Verify phone via OTP code
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, code]
properties:
token: { type: string }
code: { type: string }
responses:
'200': { description: Verified }
/auth/password/reset/request:
post:
tags: [Auth]
summary: Request password reset (email/SMS)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_slug]
properties:
tenant_slug: { type: string }
email: { type: string }
phone_number: { type: string }
responses:
'202': { description: Sent if account exists }
/auth/password/reset/confirm:
post:
tags: [Auth]
summary: Reset password with token
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, new_password]
properties:
token: { type: string }
new_password: { type: string, minLength: 8 }
responses:
'200': { description: OK }
/auth/password/change:
post:
tags: [Auth]
summary: Change password (logged-in)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [current_password, new_password]
properties:
current_password: { type: string }
new_password: { type: string, minLength: 8 }
responses:
'204': { description: Changed }
# MFA
/auth/mfa/setup:
post:
tags: [Auth]
summary: Initiate MFA setup (returns TOTP secret/QR)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [factor_type]
properties:
factor_type: { type: string, enum: [TOTP, SMS] }
label: { type: string }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
factor_id: { type: string, format: uuid }
secret: { type: string }
qr_code_url: { type: string }
recovery_codes: { type: array, items: { type: string } }
/auth/mfa/verify:
post:
tags: [Auth]
summary: Verify MFA factor with first code
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [factor_id, code]
properties:
factor_id: { type: string, format: uuid }
code: { type: string }
responses:
'200': { description: Verified }
/auth/mfa/challenge:
post:
tags: [Auth]
summary: Complete login after MFA prompt
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [mfa_token, code]
properties:
mfa_token: { type: string }
code: { type: string }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/AuthTokens' }
# Push subscriptions (PWA)
/auth/push/subscribe:
post:
tags: [Auth]
summary: Register browser Web Push subscription
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [endpoint, keys]
properties:
endpoint: { type: string }
keys:
type: object
required: [p256dh, auth]
properties:
p256dh: { type: string }
auth: { type: string }
user_agent: { type: string }
responses:
'201': { description: Created }
/auth/push/unsubscribe:
post:
tags: [Auth]
summary: Remove a push subscription
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
endpoint: { type: string }
responses:
'204': { description: Removed }
# ===================== USERS =====================
/users/me:
get:
tags: [Users]
summary: Current user profile
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
patch:
tags: [Users]
summary: Update profile
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UserUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
/users/me/balance:
get:
tags: [Users]
summary: Get current balance + affiliate balance
responses:
'200':
content:
application/json:
schema:
type: object
properties:
balance_minor: { type: integer, format: int64 }
affiliate_balance_minor: { type: integer, format: int64 }
currency: { type: string }
daily_remain_render_count: { type: integer }
parallel_rendering_ceiling: { type: integer }
/users/me/avatar:
post:
tags: [Users]
summary: Pick avatar (from library) or upload custom
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
avatar_id: { type: string, format: uuid }
avatar_url: { type: string }
responses:
'200': { description: Updated }
/users/{user_id}:
get:
tags: [Users]
summary: (Admin) Get any user
parameters:
- { name: user_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
/users:
get:
tags: [Users]
summary: (Admin) Search users
parameters:
- { name: q, in: query, schema: { type: string } }
- { name: tenant_id, in: query, schema: { type: string, format: uuid } }
- { name: page, in: query, schema: { type: integer, default: 1 } }
- { name: page_size, in: query, schema: { type: integer, default: 20 } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/User' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
/users/{user_id}/ban:
post:
tags: [Admin]
summary: Ban a user
parameters:
- { name: user_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [reason]
properties:
reason: { type: string }
unblock_date: { type: string, format: date-time }
responses:
'204': { description: Banned }
# ===================== TENANTS =====================
/tenants:
get:
tags: [Tenants]
summary: (Admin) List tenants
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Tenant' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
post:
tags: [Tenants]
summary: Create a tenant (Admin or signup flow)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/TenantCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Tenant' }
/tenants/by-slug/{slug}:
get:
tags: [Tenants]
summary: Resolve tenant by slug/domain (used by Gateway)
parameters:
- { name: slug, in: path, required: true, schema: { type: string } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Tenant' }
/tenants/{tenant_id}:
get:
tags: [Tenants]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Tenant' }
patch:
tags: [Tenants]
summary: Update tenant settings/contact
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/TenantUpdate' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Tenant' }
/tenants/{tenant_id}/branding:
get:
tags: [Tenants]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/TenantBranding' }
put:
tags: [Tenants]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/TenantBranding' }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/TenantBranding' }
/tenants/{tenant_id}/domains/verify:
post:
tags: [Tenants]
summary: Start domain verification (returns DNS challenge)
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [domain]
properties:
domain: { type: string }
method: { type: string, enum: [DNS_TXT, HTTP_FILE], default: DNS_TXT }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
verification_id: { type: string, format: uuid }
challenge_record: { type: string }
expires_at: { type: string, format: date-time }
/tenants/{tenant_id}/usage:
get:
tags: [Tenants]
summary: Usage time-series for billing/dashboard
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: from, in: query, schema: { type: string, format: date } }
- { name: to, in: query, schema: { type: string, format: date } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: '#/components/schemas/TenantUsageDay' }
# ===================== API KEYS =====================
/tenants/{tenant_id}/api-keys:
get:
tags: [ApiKeys]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: '#/components/schemas/ApiKey' }
post:
tags: [ApiKeys]
summary: Create new API key (full secret returned ONCE)
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ApiKeyCreate' }
responses:
'201':
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/ApiKey'
- type: object
properties:
secret_key:
type: string
description: Shown only on creation. Format fr_live_xxx... or fr_test_xxx...
/tenants/{tenant_id}/api-keys/{api_key_id}:
delete:
tags: [ApiKeys]
summary: Revoke API key
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: api_key_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
content:
application/json:
schema:
type: object
properties:
reason: { type: string }
responses:
'204': { description: Revoked }
/api-keys/validate:
post:
tags: [ApiKeys]
summary: (Internal) Validate API key + signature (called by Gateway)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [key_prefix, key_hash]
properties:
key_prefix: { type: string }
key_hash: { type: string }
ip_address: { type: string }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
valid: { type: boolean }
tenant_id: { type: string, format: uuid }
scopes: { type: array, items: { type: string } }
rate_limit_rpm: { type: integer }
# ===================== WEBHOOKS =====================
/tenants/{tenant_id}/webhooks:
get:
tags: [Webhooks]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Webhook' } }
post:
tags: [Webhooks]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/WebhookCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/Webhook' }
/tenants/{tenant_id}/webhooks/{webhook_id}:
delete:
tags: [Webhooks]
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: webhook_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'204': { description: Deleted }
/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries:
get:
tags: [Webhooks]
summary: Recent delivery attempts
parameters:
- { name: tenant_id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: webhook_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: '#/components/schemas/WebhookDelivery' }
# ===================== PLANS =====================
/plans:
get:
tags: [Plans]
summary: List active plans (tenant-scoped via auth)
parameters:
- { name: scope, in: query, schema: { type: string, enum: [User, Tenant] } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Plan' } }
/plans/{plan_id}:
get:
tags: [Plans]
parameters:
- { name: plan_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Plan' }
/users/me/plan:
get:
tags: [Plans]
summary: Current active plan
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/UserPlan' }
/users/me/plan/purchase:
post:
tags: [Plans]
summary: Start purchase flow (returns payment redirect URL)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [plan_id]
properties:
plan_id: { type: string, format: uuid }
gateway: { type: string, enum: [ZarinPal, IdPay, Bazaar, Stripe, Balance] }
discount_code: { type: string }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
payment_id: { type: string, format: uuid }
redirect_url: { type: string }
# ===================== PAYMENTS =====================
/payments:
get:
tags: [Payments]
summary: List user's payments
parameters:
- { name: page, in: query, schema: { type: integer } }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Payment' } }
meta: { $ref: '#/components/schemas/PaginationMeta' }
/payments/{payment_id}:
get:
tags: [Payments]
parameters:
- { name: payment_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Payment' }
/payments/callback/zarinpal:
get:
tags: [Payments]
summary: ZarinPal callback (verifies payment)
parameters:
- { name: Authority, in: query, schema: { type: string } }
- { name: Status, in: query, schema: { type: string } }
responses:
'302': { description: Redirect to UI }
/payments/callback/stripe:
post:
tags: [Payments]
summary: Stripe webhook
responses:
'200': { description: OK }
/payments/refund:
post:
tags: [Payments]
summary: (Internal) Issue refund (called by render service on failure)
security: [ServiceToken: []]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [payment_id, reason]
properties:
payment_id: { type: string, format: uuid }
amount_minor: { type: integer, format: int64 }
reason: { type: string }
refund_to: { type: string, enum: [Balance, OriginalMethod, Plan] }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
refund_id: { type: string, format: uuid }
status: { type: string }
# ===================== DISCOUNTS =====================
/discounts/validate:
post:
tags: [Discounts]
summary: Validate a discount code
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code: { type: string }
plan_id: { type: string, format: uuid }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
valid: { type: boolean }
discount_minor: { type: integer, format: int64 }
kind: { type: string }
value: { type: number }
/discounts:
get:
tags: [Discounts]
summary: (Admin) List discounts
responses:
'200': { description: OK }
post:
tags: [Discounts]
summary: (Admin) Create discount
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/DiscountCreate' }
responses:
'201': { description: Created }
# ===================== GAMIFICATION =====================
/quests:
get:
tags: [Gamification]
summary: Active quests for current user
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/Quest' } }
/quests/{quest_id}/claim:
post:
tags: [Gamification]
summary: Claim a completed quest prize
parameters:
- { name: quest_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200': { description: Claimed }
/gifts/earned:
get:
tags: [Gamification]
summary: List earned gifts not yet used
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/EarnedGift' } }
/gifts/earned/{earned_gift_id}/use:
post:
tags: [Gamification]
summary: Claim/use an earned gift
parameters:
- { name: earned_gift_id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
'200': { description: Used }
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
ServiceToken:
type: http
scheme: bearer
schemas:
PaginationMeta:
type: object
properties:
page: { type: integer }
page_size: { type: integer }
total: { type: integer }
has_more: { type: boolean }
AuthTokens:
type: object
required: [access_token, refresh_token, expires_in]
properties:
access_token: { type: string }
refresh_token: { type: string }
token_type: { type: string, enum: [Bearer] }
expires_in: { type: integer, description: seconds }
user: { $ref: '#/components/schemas/User' }
tenant: { $ref: '#/components/schemas/Tenant' }
User:
type: object
properties:
id: { type: string, format: uuid }
tenant_id: { type: string, format: uuid }
email: { type: string, nullable: true }
email_verified: { type: boolean }
phone_number: { type: string, nullable: true }
phone_verified: { type: boolean }
full_name: { type: string, nullable: true }
avatar_url: { type: string, nullable: true }
is_admin: { type: boolean }
is_tenant_admin: { type: boolean }
register_mode: { type: string }
last_active_date: { type: string, format: date-time }
balance_minor: { type: integer, format: int64 }
affiliate_balance_minor: { type: integer, format: int64 }
loyalty_score: { type: integer }
daily_remain_render_count: { type: integer }
max_daily_render_count: { type: integer }
parallel_rendering_ceiling: { type: integer }
used_storage_bytes: { type: integer, format: int64 }
register_date: { type: string, format: date-time }
UserUpdate:
type: object
properties:
full_name: { type: string }
slogan: { type: string }
about_me: { type: string }
company_name: { type: string }
website_name: { type: string }
birth_date: { type: string, format: date }
gender: { type: string, enum: [Male, Female, Other, PreferNotToSay] }
email_tell_me: { type: boolean }
sms_tell_me: { type: boolean }
push_tell_me: { type: boolean }
telegram_tell_me: { type: boolean }
Session:
type: object
properties:
id: { type: string, format: uuid }
device_name: { type: string }
user_agent: { type: string }
ip_address: { type: string }
issued_at: { type: string, format: date-time }
last_used_at: { type: string, format: date-time }
is_current: { type: boolean }
Tenant:
type: object
properties:
id: { type: string, format: uuid }
slug: { type: string }
name: { type: string }
kind: { type: string, enum: [Internal, Reseller, Enterprise] }
status: { type: string, enum: [Active, Trial, Suspended, Cancelled] }
custom_domain: { type: string, nullable: true }
domain_verified: { type: boolean }
contact_email: { type: string }
max_users: { type: integer, nullable: true }
max_storage_gb: { type: integer, nullable: true }
monthly_render_qty: { type: integer, nullable: true }
trial_ends_at: { type: string, format: date-time, nullable: true }
created_at: { type: string, format: date-time }
TenantCreate:
type: object
required: [slug, name, contact_email]
properties:
slug: { type: string }
name: { type: string }
kind: { type: string, enum: [Reseller, Enterprise] }
contact_name: { type: string }
contact_email: { type: string, format: email }
contact_phone: { type: string }
TenantUpdate:
type: object
properties:
name: { type: string }
contact_name: { type: string }
contact_email: { type: string }
contact_phone: { type: string }
billing_email: { type: string }
allowed_origins: { type: array, items: { type: string } }
TenantBranding:
type: object
properties:
display_name: { type: string }
logo_url: { type: string }
logo_dark_url: { type: string }
favicon_url: { type: string }
og_image_url: { type: string }
primary_color: { type: string }
secondary_color: { type: string }
accent_color: { type: string }
background_color: { type: string }
font_family: { type: string }
email_from_name: { type: string }
email_from_address: { type: string }
email_reply_to: { type: string }
email_footer_html: { type: string }
support_url: { type: string }
terms_url: { type: string }
privacy_url: { type: string }
embed_enabled: { type: boolean }
embed_allowed_hosts: { type: array, items: { type: string } }
watermark_text: { type: string }
watermark_image_url: { type: string }
watermark_enabled: { type: boolean }
TenantUsageDay:
type: object
properties:
usage_date: { type: string, format: date }
renders_completed: { type: integer }
render_seconds: { type: integer, format: int64 }
storage_bytes: { type: integer, format: int64 }
api_calls: { type: integer, format: int64 }
active_users: { type: integer }
amount_billed_minor: { type: integer, format: int64 }
billing_currency: { type: string }
billing_status: { type: string }
ApiKey:
type: object
properties:
id: { type: string, format: uuid }
tenant_id: { type: string, format: uuid }
name: { type: string }
environment: { type: string, enum: [Live, Test] }
key_prefix: { type: string }
last4: { type: string }
scopes: { type: array, items: { type: string } }
allowed_ips: { type: array, items: { type: string } }
rate_limit_rpm: { type: integer }
is_active: { type: boolean }
expires_at: { type: string, format: date-time, nullable: true }
last_used_at: { type: string, format: date-time, nullable: true }
usage_count: { type: integer, format: int64 }
created_at: { type: string, format: date-time }
ApiKeyCreate:
type: object
required: [name, scopes]
properties:
name: { type: string }
environment: { type: string, enum: [Live, Test] }
scopes:
type: array
items:
type: string
enum:
- renders:create
- renders:read
- renders:cancel
- projects:read
- projects:write
- templates:read
- exports:read
- exports:download
- users:read
- users:write
- webhooks:manage
allowed_ips: { type: array, items: { type: string } }
rate_limit_rpm: { type: integer }
expires_at: { type: string, format: date-time, nullable: true }
Webhook:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
url: { type: string }
events: { type: array, items: { type: string } }
is_active: { type: boolean }
last_triggered_at: { type: string, format: date-time, nullable: true }
last_status_code: { type: integer, nullable: true }
consecutive_failures: { type: integer }
created_at: { type: string, format: date-time }
WebhookCreate:
type: object
required: [name, url, events]
properties:
name: { type: string }
url: { type: string, format: uri }
events:
type: array
items: { type: string }
WebhookDelivery:
type: object
properties:
id: { type: string, format: uuid }
event_type: { type: string }
request_url: { type: string }
response_status: { type: integer, nullable: true }
response_body: { type: string, nullable: true }
duration_ms: { type: integer }
attempt: { type: integer }
succeeded: { type: boolean }
error_message: { type: string, nullable: true }
delivered_at: { type: string, format: date-time, nullable: true }
created_at: { type: string, format: date-time }
Plan:
type: object
properties:
id: { type: string, format: uuid }
code: { type: string }
name: { type: string }
description: { type: string }
price_minor: { type: integer, format: int64 }
before_price_minor: { type: integer, format: int64, nullable: true }
currency: { type: string }
billing_period: { type: string }
seconds_charge: { type: integer }
monthly_renders_quota: { type: integer, nullable: true }
storage_gb: { type: integer }
parallel_renders: { type: integer }
max_resolution: { type: string }
render_speed_factor: { type: number }
icon: { type: string }
is_featured: { type: boolean }
features: { type: object, additionalProperties: true }
UserPlan:
type: object
properties:
id: { type: string, format: uuid }
plan_id: { type: string, format: uuid }
plan_code: { type: string }
plan_name: { type: string }
initial_seconds_charge: { type: integer }
remain_charge_sec: { type: integer }
monthly_renders_used: { type: integer }
starts_at: { type: string, format: date-time }
expires_at: { type: string, format: date-time }
cancelled_at: { type: string, format: date-time, nullable: true }
auto_renew: { type: boolean }
Payment:
type: object
properties:
id: { type: string, format: uuid }
gateway: { type: string }
status: { type: string }
action: { type: string }
amount_minor: { type: integer, format: int64 }
currency: { type: string }
title: { type: string }
description: { type: string }
card_last4: { type: string }
confirmed_at: { type: string, format: date-time, nullable: true }
failed_at: { type: string, format: date-time, nullable: true }
failure_reason: { type: string, nullable: true }
created_at: { type: string, format: date-time }
DiscountCreate:
type: object
required: [name, code, kind, value]
properties:
name: { type: string }
code: { type: string }
kind: { type: string, enum: [Percentage, FixedAmount, FreeMonths, RenderCredits] }
value: { type: number }
owner_user_id: { type: string, format: uuid }
owner_profit_percentage: { type: number }
max_use_count: { type: integer }
applies_to_plan_ids: { type: array, items: { type: string, format: uuid } }
starts_at: { type: string, format: date-time }
expires_at: { type: string, format: date-time }
Quest:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
challenge: { type: string }
why: { type: string }
hint: { type: string }
icon: { type: string }
quest_type: { type: string, enum: [OneTime, Daily, Weekly, Onboarding, Milestone] }
target_count: { type: integer }
current_count: { type: integer }
is_completed: { type: boolean }
prize_claimed: { type: boolean }
prize_type: { type: string }
prize_amount: { type: integer, format: int64 }
expires_at: { type: string, format: date-time, nullable: true }
EarnedGift:
type: object
properties:
id: { type: string, format: uuid }
gift_id: { type: string, format: uuid }
name: { type: string }
description: { type: string }
prize_type: { type: string }
value: { type: integer, format: int64 }
unit: { type: string }
earned_at: { type: string, format: date-time }
expires_at: { type: string, format: date-time, nullable: true }
is_used: { type: boolean }