openapi: 3.1.0
info:
  title: eSecured ERP Ecommerce API
  version: 1.0.0
  description: >-
    Tenant-scoped, read-only product catalog API. Only `GET /ecommerce/v1/products` is public and
    reachable at https://api.erp.co.tz. The credential-management operations are internal: they
    are called by the ERP admin UI (Settings → Integrations → Ecommerce API) with a signed-in
    Tenant Admin's session, are served only by the ERP backend, and are not forwarded by the
    public hostname.
servers:
  - url: https://api.erp.co.tz
    description: Public catalog hostname. Forwards only GET /ecommerce/v1/products.
tags:
  - name: Catalog
    description: Public, server-to-server product catalog.
  - name: Credentials (internal)
    description: Internal credential management used by the ERP admin UI only. Not available at api.erp.co.tz.
paths:
  /ecommerce/v1/products:
    get:
      tags: [Catalog]
      operationId: listPublishedProducts
      summary: List published active products
      description: Returns only active Product items explicitly marked Publish to ecommerce for the tenant bound to the API key.
      security:
        - ecommerceApiKey: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          description: Cursor returned by the previous response.
          schema:
            type: string
            minLength: 1
            maxLength: 128
      responses:
        "200":
          description: Product page
          content:
            application/json:
              schema:
                type: object
                required: [products, page]
                properties:
                  products:
                    type: array
                    items:
                      $ref: "#/components/schemas/Product"
                  page:
                    type: object
                    required: [limit, hasMore, nextCursor]
                    properties:
                      limit:
                        type: integer
                      hasMore:
                        type: boolean
                      nextCursor:
                        type: [string, "null"]
        "400":
          description: Invalid query parameter (`limit_must_be_between_1_and_100` or `invalid_cursor`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing, malformed, unknown, or revoked credential (`unauthorized`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Ecommerce API disabled for the tenant (`ecommerce_api_disabled`) or request originated in a browser (`server_to_server_only`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Per-key rate limit exceeded (`rate_limit_exceeded`)
          headers:
            Retry-After:
              description: Seconds until the current one-minute rate-limit window resets.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Catalog read failed (`internal_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: Credential verification temporarily unavailable (`service_unavailable`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /ecommerce/v1/credentials/{tenantId}:
    servers:
      - url: https://{erpBackend}
        description: ERP backend. Internal, called by the ERP admin UI only; not reachable on api.erp.co.tz.
        variables:
          erpBackend:
            default: erp-backend.internal
            description: Not published. Credentials are managed in Settings → Integrations.
    parameters:
      - name: tenantId
        in: path
        required: true
        schema:
          type: string
    get:
      tags: [Credentials (internal)]
      x-internal: true
      operationId: listEcommerceCredentials
      summary: List tenant ecommerce credentials (internal)
      description: Tenant Admin or SuperAdmin only. Returns up to 50 credentials, newest first. Secret values are never returned.
      security:
        - firebaseTenantAdmin: []
      responses:
        "200":
          description: Credential metadata
          content:
            application/json:
              schema:
                type: object
                required: [credentials]
                properties:
                  credentials:
                    type: array
                    items:
                      $ref: "#/components/schemas/Credential"
        "401":
          description: No ERP session token was sent
        "403":
          description: Invalid session token, caller is not authorized (`forbidden`), or the feature is disabled (`ecommerce_api_disabled`)
        "500":
          description: Credential list failed (`internal_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags: [Credentials (internal)]
      x-internal: true
      operationId: createEcommerceCredential
      summary: Create a read-only ecommerce credential (internal)
      description: Tenant Admin or SuperAdmin only. The full key is returned once and cannot be retrieved later.
      security:
        - firebaseTenantAdmin: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 80
      responses:
        "201":
          description: Credential created; store the returned key securely.
          content:
            application/json:
              schema:
                type: object
                required: [credential]
                properties:
                  credential:
                    $ref: "#/components/schemas/CreatedCredential"
        "400":
          description: Invalid credential name (`name_must_be_between_2_and_80_characters`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: No ERP session token was sent
        "403":
          description: Invalid session token, caller is not authorized (`forbidden`), or the feature is disabled (`ecommerce_api_disabled`)
        "409":
          description: Tenant has reached the limit of 10 active credentials (`credential_limit_reached`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Credential creation failed (`internal_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /ecommerce/v1/credentials/{tenantId}/{credentialId}/revoke:
    servers:
      - url: https://{erpBackend}
        description: ERP backend. Internal, called by the ERP admin UI only; not reachable on api.erp.co.tz.
        variables:
          erpBackend:
            default: erp-backend.internal
            description: Not published. Credentials are managed in Settings → Integrations.
    post:
      tags: [Credentials (internal)]
      x-internal: true
      operationId: revokeEcommerceCredential
      summary: Revoke a tenant ecommerce credential (internal)
      description: Tenant Admin or SuperAdmin only. Revocation takes effect immediately. Revoking an already revoked credential succeeds without changing it.
      security:
        - firebaseTenantAdmin: []
      parameters:
        - name: tenantId
          in: path
          required: true
          schema:
            type: string
        - name: credentialId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Credential revoked
          content:
            application/json:
              schema:
                type: object
                required: [success]
                properties:
                  success:
                    type: boolean
        "401":
          description: No ERP session token was sent
        "403":
          description: Invalid session token, caller is not authorized (`forbidden`), or the feature is disabled (`ecommerce_api_disabled`)
        "404":
          description: Credential not found for this tenant (`credential_not_found`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Credential revocation failed (`internal_error`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  securitySchemes:
    ecommerceApiKey:
      type: http
      scheme: bearer
      bearerFormat: erp_ec_<credential-id>.<secret>
    firebaseTenantAdmin:
      type: http
      scheme: bearer
      bearerFormat: Firebase ID token for an authorized Tenant Admin or SuperAdmin
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Machine-readable error code, for example `unauthorized` or `internal_error`.
    Product:
      type: object
      required: [id, sku, name, unit, price, currency]
      properties:
        id:
          type: string
        sku:
          type: string
          description: The item's Item Code.
        name:
          type: string
        description:
          type: [string, "null"]
        unit:
          type: string
        price:
          type: number
          minimum: 0
          description: >-
            The item's Selling Price as entered in ERP. VAT-inclusive or VAT-exclusive according
            to the tenant's price-entry VAT setting; not converted by the API.
        currency:
          type: string
          description: The tenant's base currency (TZS when not set).
        brand:
          type: [string, "null"]
        barcode:
          type: [string, "null"]
        category:
          type: [string, "null"]
          description: The item's Item Category, falling back to its Food / Beverage tag; null when neither is set.
        updatedAt:
          type: [string, "null"]
    Credential:
      type: object
      required: [id, name, keyPrefix, scope, createdAt, createdBy, lastUsedAt, revokedAt]
      properties:
        id:
          type: string
        name:
          type: string
        keyPrefix:
          type: string
        scope:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
        createdBy:
          type: string
        lastUsedAt:
          type: [string, "null"]
          format: date-time
        revokedAt:
          type: [string, "null"]
          format: date-time
    CreatedCredential:
      type: object
      required: [id, name, key, scope, createdAt]
      properties:
        id:
          type: string
        name:
          type: string
        key:
          type: string
          writeOnly: true
        scope:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
