openapi: "3.1.0"
info:
  title: "Stratum ledger HTTP API"
  version: "0.1.0"
  description: "HTTP contract for Stratum ledger operations. Partners call the public token gateway and ingest gateway with an X-Api-Key minted in the dashboard (API Keys) — do not dial node hosts. Fungible-token routes follow an account-model wallet API (issue, transfer, redeem, stats, balance, history). Public surface only — admin token operations are omitted from this document."
servers:
  - url: "https://token-gateway.blockskunk.com"
    description: "Public token gateway (create / issue / transfer / redeem / portfolio). Copy the exact URL from Dashboard → API Keys."
  - url: "https://ingest.blockskunk.com"
    description: "Public ingest gateway (generic JSON assets). Copy the exact URL from Dashboard → API Keys."
tags:
  - name: "Tokens (fungible)"
    description: "Public partner surface — call the token gateway for wallet operations (issue, transfer, redeem, balance, history). Do not dial node hosts."
  - name: "webhook-ingest"
    description: "Write arbitrary JSON to a network — SIEM, ERP, adapters, custom apps"
    x-displayName: "Webhook ingest"
  - name: "Health"
    description: "Process health"
  - name: "Metrics"
    description: "Prometheus metrics"
paths:
  /health:
    get:
      operationId: "getHealth"
      summary: "Liveness-oriented health snapshot"
      tags:
        - "Health"
      parameters: []
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "400":
          description: "Invalid request"
          content:
            application/json:
              schema: &a1
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema: *a1
        "403":
          description: "Forbidden"
          content:
            application/json:
              schema: *a1
        "500":
          description: "Server error"
          content:
            application/json:
              schema: *a1
      security: []
  /metrics:
    get:
      operationId: "getMetrics"
      summary: "Prometheus metrics"
      tags:
        - "Metrics"
      parameters: []
      responses:
        "200":
          description: "Prometheus exposition"
          content:
            text/plain:
              schema:
                $ref: "#/components/schemas/MetricsText"
        "400":
          description: "Invalid request"
          content:
            application/json:
              schema: &a2
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: "Unauthorized"
          content:
            application/json:
              schema: *a2
        "403":
          description: "Forbidden"
          content:
            application/json:
              schema: *a2
        "500":
          description: "Server error"
          content:
            application/json:
              schema: *a2
      description: "Golden-signal metrics in Prometheus text format."
      security: []
  /token-gateway/whoami:
    get:
      operationId: "tokenGatewayWhoAmI"
      summary: "Resolve networkId and URLs from an API key"
      tags:
        - "Tokens (fungible)"
      security: &a3
        - ApiKeyAuth: []
      responses:
        "200":
          description: "Key metadata"
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  networkId:
                    type: "string"
                  keyId:
                    type: "string"
                  permissions:
                    type: "array"
                    items:
                      type: "string"
                  ingestUrl:
                    type: "string"
                  tokenGatewayUrl:
                    type: "string"
                required:
                  - "networkId"
                  - "keyId"
                  - "permissions"
                  - "ingestUrl"
                  - "tokenGatewayUrl"
        "401":
          description: "Invalid key"
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  error:
                    type: "string"
                required:
                  - "error"
  /token-gateway/{networkId}/tokens:
    get:
      operationId: "tokenGatewayListTokens"
      summary: "List tokens on a network"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "Token list"
          content:
            application/json:
              schema: &a5
                type: "object"
                properties:
                  success:
                    type: "boolean"
                  result: {}
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/issue:
    post:
      operationId: "tokenGatewayIssue"
      summary: "Issue tokens to a wallet"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: "object"
              required:
                - "walletId"
                - "usdAmount"
                - "coinUnits"
              properties:
                walletId:
                  type: "string"
                usdAmount:
                  type: "number"
                coinUnits:
                  type: "number"
      responses:
        "200":
          description: "Issued"
          content:
            application/json:
              schema: &a4
                type: "object"
                properties:
                  success:
                    type: "boolean"
                  transactionId:
                    type: "string"
                  result: {}
                  alreadyInitialized:
                    type: "boolean"
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/transfer:
    post:
      operationId: "tokenGatewayTransfer"
      summary: "Transfer tokens between wallets"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: "object"
              required:
                - "fromWalletId"
                - "toWalletId"
                - "amount"
              properties:
                fromWalletId:
                  type: "string"
                toWalletId:
                  type: "string"
                amount:
                  type: "number"
      responses:
        "200":
          description: "Transferred"
          content:
            application/json:
              schema: *a4
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/redeem:
    post:
      operationId: "tokenGatewayRedeem"
      summary: "Redeem tokens from a wallet"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: "object"
              required:
                - "walletId"
                - "amount"
              properties:
                walletId:
                  type: "string"
                amount:
                  type: "number"
      responses:
        "200":
          description: "Redeemed"
          content:
            application/json:
              schema: *a4
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/redeem/quote:
    get:
      operationId: "tokenGatewayRedeemQuote"
      summary: "Quote a redemption"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "amount"
          in: "query"
          required: true
          schema:
            type: "number"
            exclusiveMinimum: 0
      responses:
        "200":
          description: "Quote"
          content:
            application/json:
              schema: *a5
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/wallets/{walletId}/balance:
    get:
      operationId: "tokenGatewayBalance"
      summary: "Get wallet balance for a token"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "walletId"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "Balance"
          content:
            application/json:
              schema: *a5
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/tokens/{tokenId}/wallets/{walletId}/history:
    get:
      operationId: "tokenGatewayHistory"
      summary: "Get wallet history for a token"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "tokenId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "walletId"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "History"
          content:
            application/json:
              schema: *a5
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /token-gateway/{networkId}/wallets/{walletId}/portfolio:
    get:
      operationId: "tokenGatewayPortfolio"
      summary: "Get wallet portfolio across tokens"
      tags:
        - "Tokens (fungible)"
      security: *a3
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "walletId"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "Portfolio"
          content:
            application/json:
              schema: *a5
      servers:
        - url: "https://token-gateway.blockskunk.com"
  /{networkId}:
    post:
      operationId: "ingestWebhookWrite"
      summary: "Ping or write JSON assets to a network"
      description: "Send `{\"type\":\"ping\"}` to verify connectivity without a ledger write. Otherwise normalize the body into one or more ledger assets. Requires ingest:ping or ingest:write on the key. Optional wire field targetChannels routes to named shared spaces."
      tags:
        - "webhook-ingest"
      security:
        - ApiKeyAuth: []
      parameters:
        - name: "networkId"
          in: "path"
          required: true
          schema:
            type: "string"
          description: "Network id the key is scoped to (from API Keys / whoami)"
        - name: "X-Api-Key"
          in: "header"
          required: true
          schema:
            type: "string"
          description: "Dashboard-minted ingest key (cd_ingest_…)"
        - name: "Idempotency-Key"
          in: "header"
          required: false
          schema:
            type: "string"
          description: "Same key + same body returns the cached response"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: "object"
                  properties:
                    type:
                      type: "string"
                      const: "ping"
                  required:
                    - "type"
                  additionalProperties: true
                - type: "object"
                  properties:
                    assets:
                      type: "array"
                      items:
                        $ref: "#/components/schemas/IngestAsset"
                      minItems: 1
                    targetChannels:
                      type: "array"
                      items:
                        type: "string"
                      description: "Optional shared-space names on that network (wire field name)"
                  required:
                    - "assets"
                - allOf:
                    - $ref: "#/components/schemas/IngestAsset"
                    - type: "object"
                      properties:
                        targetChannels:
                          type: "array"
                          items:
                            type: "string"
            examples:
              ping:
                summary: "Connectivity ping"
                value:
                  type: "ping"
              single:
                summary: "Single record"
                value:
                  type: "order"
                  owner: "wallet-a"
                  data:
                    sku: "SKU-1"
                    qty: 2
              batch:
                summary: "Batch"
                value:
                  assets:
                    - type: "order"
                      data:
                        sku: "A"
                    - type: "order"
                      data:
                        sku: "B"
      responses:
        "200":
          description: "Ping or write result"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestResponse"
        "401":
          description: "Missing or unknown ingest API key"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestError"
        "403":
          description: "Key revoked, expired, or lacking permission / allowlist"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestError"
        "404":
          description: "Unknown network id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestError"
        "405":
          description: "Method not allowed (POST only)"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestError"
        "429":
          description: "Rate limited — Retry-After may be set"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestError"
      servers:
        - url: "https://ingest.blockskunk.com"
components:
  securitySchemes:
    ApiKey:
      type: "apiKey"
      in: "header"
      name: "X-Api-Key"
      description: "Dashboard-minted API key for the public token gateway and ingest gateway. Required on partner calls. Mint and rotate under Dashboard → Developer → API Keys."
    ApiKeyAuth:
      type: "apiKey"
      in: "header"
      name: "X-Api-Key"
  schemas:
    HealthResponse:
      type: "object"
      properties:
        status:
          type: "string"
          enum:
            - "healthy"
            - "unhealthy"
        timestamp:
          type: "string"
        version:
          type: "string"
        chaincode:
          type: "string"
          description: "Installed ledger application id (wire field name retained for compatibility)"
        channel:
          type: "string"
          description: "Network channel id"
        checks:
          type: "object"
          properties:
            credentials:
              type: "boolean"
            gateway:
              type: "boolean"
          required:
            - "credentials"
            - "gateway"
          additionalProperties: false
      required:
        - "status"
        - "timestamp"
        - "version"
        - "chaincode"
        - "channel"
        - "checks"
      additionalProperties: false
    ErrorResponse:
      type: "object"
      properties:
        error:
          type: "object"
          properties:
            code:
              type: "string"
            message:
              type: "string"
            details: {}
          required:
            - "code"
            - "message"
          additionalProperties: false
      required:
        - "error"
      additionalProperties: false
    MetricsText:
      type: "string"
      description: "Prometheus text exposition format"
    SyncResponse:
      type: "object"
      properties:
        success:
          type: "boolean"
        transactionIds:
          type: "array"
          items:
            type: "string"
        errors:
          type: "array"
          items:
            type: "object"
            properties:
              assetId:
                type: "string"
              error:
                type: "string"
            required:
              - "assetId"
              - "error"
            additionalProperties: false
        summary:
          type: "object"
          properties:
            total:
              type: "integer"
            succeeded:
              type: "integer"
            failed:
              type: "integer"
          required:
            - "total"
            - "succeeded"
            - "failed"
          additionalProperties: false
      required:
        - "success"
        - "transactionIds"
        - "summary"
      additionalProperties: false
    SyncRequest:
      type: "object"
      properties:
        assets:
          type: "array"
          items:
            type: "object"
            properties:
              id:
                type: "string"
                minLength: 1
              type:
                type: "string"
                minLength: 1
              data:
                type: "object"
                additionalProperties: {}
              owner:
                type: "string"
              chaincodeName:
                type: "string"
                description: "Target ledger application id (wire field name retained for compatibility)"
              sourceSystem:
                type: "string"
              timestamp:
                type: "string"
                format: "date-time"
            required:
              - "id"
              - "type"
              - "data"
            additionalProperties: false
          minItems: 1
        options:
          type: "object"
          properties:
            batchSize:
              type: "integer"
              minimum: 1
              maximum: 100
              default: 10
            continueOnError:
              type: "boolean"
              default: false
          additionalProperties: false
      required:
        - "assets"
      additionalProperties: false
    QueryResponse:
      type: "object"
      properties:
        success:
          type: "boolean"
          enum:
            - true
        result: {}
      required:
        - "success"
      additionalProperties: false
    QueryRequest:
      type: "object"
      properties:
        function:
          type: "string"
          minLength: 1
        args:
          type: "array"
          items:
            type: "string"
          default: []
      required:
        - "function"
      additionalProperties: false
    TransactionSuccess:
      type: "object"
      properties:
        success:
          type: "boolean"
          enum:
            - true
        transactionId:
          type: "string"
        result: {}
        alreadyInitialized:
          type: "boolean"
      required:
        - "success"
        - "transactionId"
      additionalProperties: false
    IssueRequest:
      type: "object"
      properties:
        walletId:
          type: "string"
          minLength: 1
        usdAmount:
          type: "number"
          minimum: 0
        coinUnits:
          type: "number"
          exclusiveMinimum: true
          minimum: 0
      required:
        - "walletId"
        - "usdAmount"
        - "coinUnits"
      additionalProperties: false
    TransferRequest:
      type: "object"
      properties:
        fromWalletId:
          type: "string"
          minLength: 1
        toWalletId:
          type: "string"
          minLength: 1
        amount:
          type: "number"
          exclusiveMinimum: true
          minimum: 0
      required:
        - "fromWalletId"
        - "toWalletId"
        - "amount"
      additionalProperties: false
    RedeemRequest:
      type: "object"
      properties:
        walletId:
          type: "string"
          minLength: 1
        amount:
          type: "number"
          exclusiveMinimum: true
          minimum: 0
      required:
        - "walletId"
        - "amount"
      additionalProperties: false
    IngestError:
      type: "object"
      properties:
        error:
          type: "string"
      required:
        - "error"
    IngestResponse:
      type: "object"
      properties:
        transactionIds:
          type: "array"
          items:
            type: "string"
        summary:
          type: "object"
          properties:
            total:
              type: "integer"
            succeeded:
              type: "integer"
            failed:
              type: "integer"
          required:
            - "total"
            - "succeeded"
            - "failed"
        eventId:
          type: "string"
          description: "Dashboard Sync Data deep-link id"
        ping:
          type: "boolean"
          description: "Present when body is a connectivity ping"
      required:
        - "transactionIds"
        - "summary"
    IngestAsset:
      type: "object"
      properties:
        id:
          type: "string"
          description: "Optional; UUID generated if omitted"
        type:
          type: "string"
          description: "Asset type for queries / allowlists (default unknown)"
        owner:
          type: "string"
        data:
          type: "object"
          additionalProperties: true
      additionalProperties: true
security:
  - ApiKey: []
x-tagGroups:
  - name: "Tokens"
    tags:
      - "Tokens (fungible)"
  - name: "Marketplace Addons"
    tags:
      - "webhook-ingest"
  - name: "Observability"
    tags:
      - "Health"
      - "Metrics"
