77 · SaaS Registry

Register a CAP app in the SaaS Registry, configure onSubscription URLs, and secure with the mtcallback scope.

Episode 77 — SaaS Registry

What you'll learn

Why this matters

The SaaS Registry is BTP's central directory of multi-tenant SaaS applications. It connects the "subscribe" button in a customer's BTP Cockpit to your provisioning endpoint. Without registering your app in the SaaS Registry, customers cannot subscribe — you would have to manage tenant onboarding manually. The registry also controls which XSUAA scopes are granted to subscribers and what the consumer app looks like in their cockpit.

How the SaaS Registry works

Customer BTP Cockpit
  │ Click "Subscribe"
  ▼
SaaS Registry
  │ PUT /mtx/v1/provisioning/tenant/{tenantId}
  ▼
Your CAP App (mtxs provisioning endpoint)
  │ Create HDI container, deploy schema
  │ Return 200 OK
  ▼
SaaS Registry
  │ Store subscription record
  │ Redirect customer to app URL
  ▼
Customer opens your app at:
  https://your-app-<customer-subaccount>.cfapps.eu10.hana.ondemand.com

Binding the SaaS Registry

cf create-service saas-registry application my-saas-registry \
  -c '{
    "appName": "my-cap-saas",
    "appUrls": {
      "getDependencies": "https://my-cap-app.cfapps.eu10.hana.ondemand.com/mtx/v1/provisioning/dependencies",
      "onSubscription": "https://my-cap-app.cfapps.eu10.hana.ondemand.com/mtx/v1/provisioning/tenant/{tenantId}",
      "onSubscriptionAsync": false
    },
    "displayName": "My CAP Learning App",
    "description": "Learn SAP BTP development interactively",
    "category": "Education",
    "xsappname": "my-cap-saas"
  }'

The onSubscription URL is what the registry calls — {tenantId} is a literal placeholder that the registry replaces with the actual tenant GUID at call time.

MTA descriptor resource

resources:
  - name: my-saas-registry
    type: org.cloudfoundry.managed-service
    parameters:
      service: saas-registry
      service-plan: application
      config:
        appName: my-cap-saas
        displayName: My CAP Learning App
        description: Interactive SAP BTP course platform
        category: Education
        appUrls:
          getDependencies: ~{srv-api/srv-url}/mtx/v1/provisioning/dependencies
          onSubscription: ~{srv-api/srv-url}/mtx/v1/provisioning/tenant/{tenantId}
          onSubscriptionAsync: false
    requires:
      - name: srv-api

Using ~{srv-api/srv-url} means the URL is resolved from the CAP module's published URL — no hardcoding.

The getDependencies endpoint

The SaaS Registry calls this endpoint to discover which XSUAA and Destination services the app depends on. @sap/cds-mtxs implements it automatically:

GET /mtx/v1/provisioning/dependencies
Response:
[
  { "xsappname": "my-cap-saas!t1234" },
  { "xsappname": "destination!b12" }
]

Securing the provisioning endpoint

Only the SaaS Registry should call your provisioning endpoint. @sap/cds-mtxs enforces this by requiring the mtcallback scope, which is only granted to the SaaS Registry service principal:

// xs-security.json
{
  "scopes": [
    {
      "name": "$XSAPPNAME.mtcallback",
      "description": "SaaS Registry callback scope",
      "grant-as-authority-to-apps": ["$XSAPPNAME!b*"]
    }
  ]
}

Managing subscriptions in the BTP Cockpit

As the provider, you can see all subscriptions:
1. Navigate to SaaS Registry → your application → Subscriptions
2. Each subscription shows: tenant ID, subaccount name, status, subscribed date
3. You can manually trigger getDependencies to test the endpoint
4. For offboarding: the customer unsubscribes in their cockpit → registry calls DELETE /mtx/v1/provisioning/tenant/{tenantId}

Multi-tenant URL pattern

When a tenant subscribes, their dedicated URL is:

https://<tenant-subdomain>-<your-app-name>.cfapps.eu10.hana.ondemand.com

AppRouter resolves the subdomain → XSUAA extracts the zone ID → CAP sets req.tenant. The routing is automatic.

Common mistakes

Mistake Fix
Hardcoding the callback URL Use ~{srv-api/srv-url} to resolve at deploy time
Not granting mtcallback scope to the registry Provisioning calls will be rejected as unauthorized
onSubscriptionAsync: true without polling Async mode requires a Job ID response; start with sync
Not implementing getDependencies Subscriber subaccounts won't have the correct XSUAA scopes
Missing saas-registry entitlement Provision will fail — check Global Account entitlements first

Checkpoint ✓

You can create a SaaS Registry service instance with the correct onSubscription and getDependencies URLs, bind it in the MTA descriptor using dynamic URL references, secure the provisioning endpoint with the mtcallback scope, and manage subscriptions from the BTP Cockpit.

Sign in for progress tracking & Q&A →