Files
Sankofa/docs/phoenix/API_SPECIFICATION.md
defiQUG 33d50fb91e
Some checks failed
API CI / API Lint (push) Successful in 47s
API CI / API Type Check (push) Failing after 47s
API CI / API Test (push) Successful in 1m0s
API CI / API Build (push) Failing after 50s
API CI / Build Docker Image (push) Has been skipped
Build Crossplane Provider / build (push) Failing after 5m51s
CD Pipeline / Deploy to Staging (push) Failing after 29s
CI Pipeline / Lint and Type Check (push) Failing after 36s
CI Pipeline / Build (push) Has been skipped
CI Pipeline / Test Backend (push) Failing after 1m33s
CI Pipeline / Test Frontend (push) Failing after 30s
CI Pipeline / Security Scan (push) Failing after 1m16s
Crossplane Provider CI / Go Test (push) Failing after 3m23s
Crossplane Provider CI / Go Lint (push) Failing after 7m27s
Crossplane Provider CI / Go Build (push) Failing after 3m27s
Deploy to Staging / Deploy to Staging (push) Failing after 30s
Portal CI / Portal Lint (push) Failing after 21s
Portal CI / Portal Type Check (push) Failing after 21s
Portal CI / Portal Test (push) Failing after 21s
Portal CI / Portal Build (push) Failing after 22s
Test Suite / frontend-tests (push) Failing after 30s
Test Suite / api-tests (push) Failing after 49s
Test Suite / blockchain-tests (push) Failing after 30s
Type Check / type-check (map[directory:. name:root]) (push) Failing after 23s
Type Check / type-check (map[directory:api name:api]) (push) Failing after 21s
Type Check / type-check (map[directory:portal name:portal]) (push) Failing after 19s
Validate Configuration Files / validate (push) Failing after 1m52s
CD Pipeline / Deploy to Production (push) Has been skipped
chore: consolidate local WIP (repo cleanup 20260707)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 09:41:34 -07:00

23 KiB

Phoenix Operating Model - API Specification

Complete API specification for all five control planes

This document provides detailed API specifications for the Phoenix operating model, including GraphQL schemas, REST endpoints, and integration patterns.


Executive Summary

The Phoenix operating model exposes APIs for all five control planes:

  1. Commercial Plane API - Client and billing operations
  2. Tenancy Plane API - Tenant and identity operations
  3. Subscription Plane API - Subscription and quota operations
  4. Environment Plane API - Environment and deployment operations
  5. Content & DevOps Plane API - Content and Git operations

All APIs support:

  • GraphQL interface (primary)
  • REST interface (alternative)
  • Authentication via Keycloak
  • Authorization via RBAC
  • Audit logging
  • Multi-region support

I. Commercial Plane API

GraphQL Schema

# Client Management
type Client {
  id: ID!
  name: String!
  legalEntity: LegalEntity!
  contract: Contract
  msa: MSA
  invoicingConfig: InvoicingConfig!
  paymentInstruments: [PaymentInstrument!]!
  costCenters: [CostCenter!]!
  tenants: [Tenant!]!
  usageAggregation: UsageAggregation
  chargebackRules: [ChargebackRule!]!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type LegalEntity {
  name: String!
  registrationNumber: String
  jurisdiction: String!
  taxId: String
  address: Address!
}

type InvoicingConfig {
  format: InvoiceFormat!
  frequency: InvoiceFrequency!
  currency: String!
  paymentTerms: String!
  billingAddress: Address!
  emailRecipients: [String!]!
}

enum InvoiceFormat {
  PDF
  XML
  JSON
}

enum InvoiceFrequency {
  MONTHLY
  QUARTERLY
  ANNUAL
}

# Queries
type Query {
  client(id: ID!): Client
  clients(filter: ClientFilter): [Client!]!
  billing(clientId: ID!, timeRange: TimeRange!): BillingData!
  invoices(clientId: ID!, filter: InvoiceFilter): [Invoice!]!
  costCenters(clientId: ID!): [CostCenter!]!
}

# Mutations
type Mutation {
  createClient(input: CreateClientInput!): Client!
  updateClient(id: ID!, input: UpdateClientInput!): Client!
  deleteClient(id: ID!): Boolean!
  createInvoice(clientId: ID!, period: BillingPeriod!): Invoice!
  processPayment(invoiceId: ID!, payment: PaymentInput!): Payment!
  addPaymentInstrument(clientId: ID!, instrument: PaymentInstrumentInput!): PaymentInstrument!
  removePaymentInstrument(clientId: ID!, instrumentId: ID!): Boolean!
  createCostCenter(clientId: ID!, input: CreateCostCenterInput!): CostCenter!
  updateCostCenter(clientId: ID!, costCenterId: ID!, input: UpdateCostCenterInput!): CostCenter!
  deleteCostCenter(clientId: ID!, costCenterId: ID!): Boolean!
}

# Input Types
input CreateClientInput {
  name: String!
  legalEntity: LegalEntityInput!
  invoicingConfig: InvoicingConfigInput!
  contract: ContractInput
  msa: MSAInput
}

input ClientFilter {
  name: String
  jurisdiction: String
  status: ClientStatus
}

input TimeRange {
  start: DateTime!
  end: DateTime!
}

REST Endpoints

# Client Management
POST   /api/v1/commercial/clients
GET    /api/v1/commercial/clients
GET    /api/v1/commercial/clients/{id}
PUT    /api/v1/commercial/clients/{id}
DELETE /api/v1/commercial/clients/{id}

# Billing
GET    /api/v1/commercial/clients/{id}/billing
GET    /api/v1/commercial/clients/{id}/invoices
POST   /api/v1/commercial/clients/{id}/invoices
GET    /api/v1/commercial/invoices/{id}
POST   /api/v1/commercial/invoices/{id}/payments

# Cost Centers
POST   /api/v1/commercial/clients/{id}/cost-centers
GET    /api/v1/commercial/clients/{id}/cost-centers
PUT    /api/v1/commercial/clients/{id}/cost-centers/{costCenterId}
DELETE /api/v1/commercial/clients/{id}/cost-centers/{costCenterId}

Example Queries

Create Client

mutation {
  createClient(input: {
    name: "Government Agency A"
    legalEntity: {
      name: "Government Agency A"
      jurisdiction: "Nation A"
      address: {
        street: "123 Main St"
        city: "Capital City"
        country: "Nation A"
      }
    }
    invoicingConfig: {
      format: PDF
      frequency: MONTHLY
      currency: "USD"
      paymentTerms: "Net 30"
      billingAddress: {
        street: "123 Main St"
        city: "Capital City"
        country: "Nation A"
      }
      emailRecipients: ["billing@agency.gov"]
    }
  }) {
    id
    name
    legalEntity {
      name
      jurisdiction
    }
  }
}

Get Billing Data

query {
  billing(
    clientId: "client-id"
    timeRange: {
      start: "2025-01-01T00:00:00Z"
      end: "2025-01-31T23:59:59Z"
    }
  ) {
    totalCost
    currency
    bySubscription {
      subscription {
        id
        name
      }
      cost
      percentage
    }
    byCostCenter {
      costCenter {
        id
        name
      }
      cost
      percentage
    }
  }
}

II. Tenancy Plane API

GraphQL Schema

# Tenant Management
type Tenant {
  id: ID!
  name: String!
  primaryDomains: [String!]!
  identityProvider: IdentityProvider!
  rbacNamespace: String!
  dataResidencyFlags: [DataResidencyFlag!]!
  complianceProfile: ComplianceProfile!
  client: Client!
  subscriptions: [Subscription!]!
  environments: [Environment!]!
  regions: [Region!]!
  keycloakRealmId: String
  multiRegionEnabled: Boolean!
  regionalDataResidency: [RegionalDataResidency!]!
  crossBorderGovernance: CrossBorderGovernance
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type IdentityProvider {
  type: IdentityProviderType!
  config: JSON!
  ssoEnabled: Boolean!
  mfaRequired: Boolean!
}

enum IdentityProviderType {
  KEYCLOAK
  AZURE_AD
  OKTA
  GOOGLE_WORKSPACE
  CUSTOM_SAML
  CUSTOM_OIDC
}

# Queries
type Query {
  tenant(id: ID!): Tenant
  tenants(filter: TenantFilter): [Tenant!]!
  tenantByDomain(domain: String!): Tenant
  identityProvider(tenantId: ID!): IdentityProvider
  complianceProfile(tenantId: ID!): ComplianceProfile
}

# Mutations
type Mutation {
  createTenant(input: CreateTenantInput!): Tenant!
  updateTenant(id: ID!, input: UpdateTenantInput!): Tenant!
  deleteTenant(id: ID!): Boolean!
  configureIdentityProvider(tenantId: ID!, provider: IdentityProviderInput!): IdentityProvider!
  syncKeycloakRealm(tenantId: ID!): KeycloakRealm!
  updateComplianceProfile(tenantId: ID!, profile: ComplianceProfileInput!): ComplianceProfile!
  addDataResidencyFlag(tenantId: ID!, flag: DataResidencyFlagInput!): DataResidencyFlag!
  removeDataResidencyFlag(tenantId: ID!, flagId: ID!): Boolean!
}

# Input Types
input CreateTenantInput {
  name: String!
  clientId: ID!
  primaryDomains: [String!]!
  identityProvider: IdentityProviderInput!
  rbacNamespace: String!
  complianceProfile: ComplianceProfileInput!
  dataResidencyFlags: [DataResidencyFlagInput!]
  multiRegionEnabled: Boolean
}

REST Endpoints

# Tenant Management
POST   /api/v1/tenancy/tenants
GET    /api/v1/tenancy/tenants
GET    /api/v1/tenancy/tenants/{id}
PUT    /api/v1/tenancy/tenants/{id}
DELETE /api/v1/tenancy/tenants/{id}
GET    /api/v1/tenancy/tenants/by-domain/{domain}

# Identity Provider
GET    /api/v1/tenancy/tenants/{id}/identity-provider
PUT    /api/v1/tenancy/tenants/{id}/identity-provider
POST   /api/v1/tenancy/tenants/{id}/keycloak/sync

# Compliance
GET    /api/v1/tenancy/tenants/{id}/compliance
PUT    /api/v1/tenancy/tenants/{id}/compliance

Example Queries

Create Tenant

mutation {
  createTenant(input: {
    name: "agency-tenant"
    clientId: "client-id"
    primaryDomains: ["agency.gov", "agency.sankofa.nexus"]
    identityProvider: {
      type: KEYCLOAK
      config: {}
      ssoEnabled: true
      mfaRequired: true
    }
    rbacNamespace: "agency-namespace"
    complianceProfile: {
      standards: [ISO_27001, SOC_2, FEDRAMP]
    }
    dataResidencyFlags: [{
      region: "region-a"
      requirement: REQUIRED
      enforcement: HARD
    }]
    multiRegionEnabled: true
  }) {
    id
    name
    primaryDomains
    keycloakRealmId
  }
}

Configure Identity Provider

mutation {
  configureIdentityProvider(
    tenantId: "tenant-id"
    provider: {
      type: AZURE_AD
      config: {
        tenantId: "azure-tenant-id"
        clientId: "azure-client-id"
        clientSecret: "azure-client-secret"
      }
      ssoEnabled: true
      mfaRequired: true
    }
  ) {
    type
    ssoEnabled
    mfaRequired
  }
}

III. Subscription Plane API

GraphQL Schema

# Subscription Management
type Subscription {
  id: ID!
  name: String!
  tenant: Tenant!
  client: Client!
  type: SubscriptionType!
  serviceBundles: [ServiceBundle!]!
  quotas: Quotas!
  limits: Limits!
  costTracking: CostTracking!
  policyPacks: [PolicyPack!]!
  featureEntitlements: [FeatureEntitlement!]!
  environments: [Environment!]!
  regions: [Region!]!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

enum SubscriptionType {
  SHARED_PLATFORM
  PRODUCT
  SANDBOX
  INNOVATION
}

type ServiceBundle {
  service: ServiceType!
  enabled: Boolean!
  quotas: ServiceQuotas
  limits: ServiceLimits
}

enum ServiceType {
  COMPUTE
  STORAGE
  NETWORKING
  DATABASE
  AI_ML
  ANALYTICS
  SECURITY
  MONITORING
  BACKUP
}

# Queries
type Query {
  subscription(id: ID!): Subscription
  subscriptions(filter: SubscriptionFilter): [Subscription!]!
  quotas(subscriptionId: ID!): Quotas!
  checkQuota(subscriptionId: ID!, resource: ResourceType!): QuotaStatus!
}

# Mutations
type Mutation {
  createSubscription(input: CreateSubscriptionInput!): Subscription!
  updateSubscription(id: ID!, input: UpdateSubscriptionInput!): Subscription!
  deleteSubscription(id: ID!): Boolean!
  updateQuotas(subscriptionId: ID!, quotas: QuotasInput!): Quotas!
  addServiceBundle(subscriptionId: ID!, bundle: ServiceBundleInput!): ServiceBundle!
  removeServiceBundle(subscriptionId: ID!, service: ServiceType!): Boolean!
  addPolicyPack(subscriptionId: ID!, pack: PolicyPackInput!): PolicyPack!
  removePolicyPack(subscriptionId: ID!, packId: ID!): Boolean!
}

REST Endpoints

# Subscription Management
POST   /api/v1/subscription/subscriptions
GET    /api/v1/subscription/subscriptions
GET    /api/v1/subscription/subscriptions/{id}
PUT    /api/v1/subscription/subscriptions/{id}
DELETE /api/v1/subscription/subscriptions/{id}

# Quotas
GET    /api/v1/subscription/subscriptions/{id}/quotas
PUT    /api/v1/subscription/subscriptions/{id}/quotas
GET    /api/v1/subscription/subscriptions/{id}/quotas/check

# Service Bundles
POST   /api/v1/subscription/subscriptions/{id}/service-bundles
DELETE /api/v1/subscription/subscriptions/{id}/service-bundles/{service}

# Policy Packs
POST   /api/v1/subscription/subscriptions/{id}/policy-packs
DELETE /api/v1/subscription/subscriptions/{id}/policy-packs/{packId}

Example Queries

Create Subscription

mutation {
  createSubscription(input: {
    name: "production-subscription"
    tenantId: "tenant-id"
    type: PRODUCT
    serviceBundles: [{
      service: COMPUTE
      enabled: true
      quotas: {
        vcpu: 100
        memory: 512
        instances: 50
      }
    }, {
      service: STORAGE
      enabled: true
      quotas: {
        total: 10000
        perInstance: 500
      }
    }]
    quotas: {
      compute: {
        vcpu: 100
        memory: 512
        instances: 50
      }
      storage: {
        total: 10000
        perInstance: 500
      }
    }
    policyPacks: [{
      name: "security-policy"
      type: SECURITY
      policies: []
      enforcement: HARD
    }]
  }) {
    id
    name
    type
    serviceBundles {
      service
      enabled
    }
  }
}

IV. Environment Plane API

GraphQL Schema

# Environment Management
type Environment {
  id: ID!
  name: String!
  type: EnvironmentType!
  subscription: Subscription!
  networkIsolation: NetworkIsolation!
  dataIsolation: DataIsolation!
  deploymentPolicies: [DeploymentPolicy!]!
  runtimeSecrets: [Secret!]!
  complianceOverlays: [ComplianceOverlay!]!
  region: Region
  promotionFlow: PromotionFlow
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

enum EnvironmentType {
  DEV
  INT
  UAT
  STAGING
  PROD
  REGULATED
  SOVEREIGN
  AIR_GAPPED
}

# Queries
type Query {
  environment(id: ID!): Environment
  environments(filter: EnvironmentFilter): [Environment!]!
  promotionFlow(environmentId: ID!): PromotionFlow
  deploymentPolicies(environmentId: ID!): [DeploymentPolicy!]!
}

# Mutations
type Mutation {
  createEnvironment(input: CreateEnvironmentInput!): Environment!
  updateEnvironment(id: ID!, input: UpdateEnvironmentInput!): Environment!
  deleteEnvironment(id: ID!): Boolean!
  promoteArtifact(input: PromoteArtifactInput!): PromotionResult!
  approvePromotion(promotionId: ID!, approved: Boolean!): PromotionResult!
  configurePromotionFlow(environmentId: ID!, flow: PromotionFlowInput!): PromotionFlow!
  addDeploymentPolicy(environmentId: ID!, policy: DeploymentPolicyInput!): DeploymentPolicy!
  removeDeploymentPolicy(environmentId: ID!, policyId: ID!): Boolean!
}

REST Endpoints

# Environment Management
POST   /api/v1/environment/environments
GET    /api/v1/environment/environments
GET    /api/v1/environment/environments/{id}
PUT    /api/v1/environment/environments/{id}
DELETE /api/v1/environment/environments/{id}

# Promotion
POST   /api/v1/environment/promotions
GET    /api/v1/environment/promotions/{id}
POST   /api/v1/environment/promotions/{id}/approve
POST   /api/v1/environment/promotions/{id}/reject

# Deployment Policies
POST   /api/v1/environment/environments/{id}/deployment-policies
GET    /api/v1/environment/environments/{id}/deployment-policies
DELETE /api/v1/environment/environments/{id}/deployment-policies/{policyId}

Example Queries

Create Environment

mutation {
  createEnvironment(input: {
    name: "production-env"
    subscriptionId: "subscription-id"
    type: PROD
    networkIsolation: {
      vpcId: "vpc-id"
      subnetIds: ["subnet-1", "subnet-2"]
      firewallRules: []
    }
    dataIsolation: {
      encryptionAtRest: true
      encryptionInTransit: true
      accessControls: []
    }
    deploymentPolicies: [{
      name: "production-policy"
      type: POLICY_DRIVEN
      approvalRequired: true
      approvers: ["release-manager-1", "release-manager-2"]
    }]
    region: "region-a"
  }) {
    id
    name
    type
    region {
      id
      name
    }
  }
}

Promote Artifact

mutation {
  promoteArtifact(input: {
    artifactId: "artifact-id"
    fromEnvironmentId: "staging-env-id"
    toEnvironmentId: "prod-env-id"
    metadata: {
      version: "1.2.3"
      changelog: "Production release"
    }
  }) {
    id
    status
    approvalRequired
    approvers
  }
}

V. Content & DevOps Plane API

GraphQL Schema

# Content Hierarchy
type Enterprise {
  id: ID!
  name: String!
  portfolios: [Portfolio!]!
  ownership: Ownership!
  governance: Governance!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type Portfolio {
  id: ID!
  name: String!
  enterprise: Enterprise!
  products: [Product!]!
  ownership: Ownership!
  governance: Governance!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type Product {
  id: ID!
  name: String!
  portfolio: Portfolio!
  applications: [Application!]!
  ownership: Ownership!
  governance: Governance!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type Application {
  id: ID!
  name: String!
  product: Product!
  components: [Component!]!
  gitRepos: [GitRepo!]!
  ownership: Ownership!
  governance: Governance!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

type Component {
  id: ID!
  name: String!
  application: Application!
  contentType: ContentType!
  content: Content!
  ownership: Ownership!
  governance: Governance!
  version: String!
  lineage: [LineageEntry!]!
  createdAt: DateTime!
  updatedAt: DateTime!
  metadata: JSON
}

enum ContentType {
  SOURCE_CODE
  IAC
  PIPELINE
  CONFIG_TEMPLATE
  DOCUMENTATION
  DATA_SCHEMA
  AI_MODEL
  PROMPT
}

# Queries
type Query {
  enterprise(id: ID!): Enterprise
  enterprises: [Enterprise!]!
  portfolio(id: ID!): Portfolio
  product(id: ID!): Product
  application(id: ID!): Application
  component(id: ID!): Component
  gitRepo(id: ID!): GitRepo
  gitRepos(filter: GitRepoFilter): [GitRepo!]!
}

# Mutations
type Mutation {
  createEnterprise(input: CreateEnterpriseInput!): Enterprise!
  createPortfolio(input: CreatePortfolioInput!): Portfolio!
  createProduct(input: CreateProductInput!): Product!
  createApplication(input: CreateApplicationInput!): Application!
  createComponent(input: CreateComponentInput!): Component!
  createGitRepo(input: CreateGitRepoInput!): GitRepo!
  configureBranchProtection(repoId: ID!, branch: String!, protection: BranchProtectionInput!): BranchProtection!
}

REST Endpoints

# Content Hierarchy
POST   /api/v1/content/enterprises
GET    /api/v1/content/enterprises
GET    /api/v1/content/enterprises/{id}

POST   /api/v1/content/portfolios
GET    /api/v1/content/portfolios/{id}

POST   /api/v1/content/products
GET    /api/v1/content/products/{id}

POST   /api/v1/content/applications
GET    /api/v1/content/applications/{id}

POST   /api/v1/content/components
GET    /api/v1/content/components/{id}

# Git Integration
POST   /api/v1/content/git-repos
GET    /api/v1/content/git-repos
GET    /api/v1/content/git-repos/{id}
POST   /api/v1/content/git-repos/{id}/branch-protection

Example Queries

Create Application

mutation {
  createApplication(input: {
    name: "web-application"
    productId: "product-id"
    ownership: {
      owner: "dev-team"
      team: "engineering"
      contact: "dev-team@agency.gov"
    }
    governance: {
      approvalWorkflows: [{
        name: "code-review"
        steps: [{
          approver: "tech-lead"
          role: "TECH_LEAD"
        }]
        required: true
      }]
      complianceTags: ["HIPAA", "SOC_2"]
    }
  }) {
    id
    name
    product {
      id
      name
    }
  }
}

Create Git Repository

mutation {
  createGitRepo(input: {
    name: "web-application-repo"
    applicationId: "application-id"
    url: "https://git.sankofa.nexus/web-application"
    branchStrategy: {
      main: "main"
      develop: "develop"
      protectedBranches: ["main", "release/*"]
    }
  }) {
    id
    name
    url
    application {
      id
      name
    }
  }
}

VI. Authentication and Authorization

Authentication

All APIs use Keycloak for authentication:

# Authentication via Keycloak
# Token obtained from Keycloak OAuth2/OIDC flow
# Include in Authorization header: Bearer <token>

Authorization

Authorization is enforced via RBAC:

# RBAC roles per plane:
# Commercial: Finance Admin, Billing Viewer, Cost Center Owner
# Tenancy: Tenant Owner, Security Admin, Identity Admin, Compliance Officer
# Subscription: Subscription Owner, Platform Admin, Service Operator, Auditor
# Environment: Environment Owner, Release Manager, Operator, Observer
# Content: Enterprise Architect, Portfolio Lead, Product Owner, Dev Lead, Contributor, Reviewer, Release Approver

Example: Authenticated Request

curl -X POST https://api.phoenix.sankofa.nexus/graphql \
  -H "Authorization: Bearer <keycloak-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { tenant(id: \"tenant-id\") { id name } }"
  }'

VII. Error Handling

Error Response Format

{
  "errors": [
    {
      "message": "Error message",
      "extensions": {
        "code": "ERROR_CODE",
        "field": "fieldName",
        "resource": "ResourceType",
        "resourceId": "resource-id"
      }
    }
  ],
  "data": null
}

Common Error Codes

  • UNAUTHORIZED - Authentication required
  • FORBIDDEN - Insufficient permissions
  • NOT_FOUND - Resource not found
  • VALIDATION_ERROR - Input validation failed
  • QUOTA_EXCEEDED - Quota limit exceeded
  • POLICY_VIOLATION - Policy violation
  • CONFLICT - Resource conflict
  • INTERNAL_ERROR - Internal server error

VIII. Rate Limiting

Rate Limits

  • GraphQL: 1000 requests per minute per tenant
  • REST: 500 requests per minute per tenant
  • Billing Queries: 100 requests per minute per client
  • Promotion Operations: 50 requests per minute per environment

Rate Limit Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1640995200

IX. Pagination

GraphQL Pagination

type Query {
  tenants(
    filter: TenantFilter
    pagination: PaginationInput
  ): TenantConnection!
}

type TenantConnection {
  edges: [TenantEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type TenantEdge {
  node: Tenant!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

input PaginationInput {
  first: Int
  after: String
  last: Int
  before: String
}

REST Pagination

GET /api/v1/tenancy/tenants?page=1&pageSize=50

Response:

{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 50,
    "totalPages": 10,
    "totalCount": 500,
    "hasNext": true,
    "hasPrevious": false
  }
}

X. Webhooks and Subscriptions

GraphQL Subscriptions

type Subscription {
  clientUpdated(clientId: ID!): Client!
  tenantUpdated(tenantId: ID!): Tenant!
  subscriptionUpdated(subscriptionId: ID!): Subscription!
  environmentUpdated(environmentId: ID!): Environment!
  promotionStatusChanged(promotionId: ID!): PromotionResult!
  quotaExceeded(subscriptionId: ID!): QuotaAlert!
}

REST Webhooks

POST /api/v1/webhooks
GET  /api/v1/webhooks
PUT  /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}

Webhook Events:

  • client.created
  • client.updated
  • tenant.created
  • tenant.updated
  • subscription.created
  • subscription.updated
  • environment.created
  • environment.updated
  • promotion.started
  • promotion.approved
  • promotion.rejected
  • quota.exceeded

XI. Multi-Region Support

Regional API Endpoints

# Primary region
https://api.phoenix.sankofa.nexus

# Regional endpoints
https://api-region-a.phoenix.sankofa.nexus
https://api-region-b.phoenix.sankofa.nexus
https://api-region-c.phoenix.sankofa.nexus

Cross-Region Operations

mutation {
  createCrossRegionSubscription(input: {
    name: "multi-region-sub"
    tenantId: "tenant-id"
    regions: ["region-a", "region-b"]
    regionalQuotas: {
      "region-a": {
        compute: { vcpu: 50, memory: 256 }
      }
      "region-b": {
        compute: { vcpu: 50, memory: 256 }
      }
    }
  }) {
    id
    name
    regions {
      id
      name
    }
  }
}

References


Last Updated: 2025-01-09
Version: 1.0
Status: Complete API Specification