# /api/provisioning/instances (dashboard)

Source-derived dashboard endpoint contract, authentication, request validation, responses, and errors.

Source: https://docs.minds.sh/docs/api/platform/reference/dashboard--api-provisioning-instances



This is the **dashboard** service route. Its origin and authentication are described in [Platform API overview](/docs/api/platform) and [Authentication](/docs/api/platform/authentication). A path shared by another service is a different endpoint.

| Property                       | Contract                                                                       |
| ------------------------------ | ------------------------------------------------------------------------------ |
| Methods                        | POST, PATCH                                                                    |
| Path                           | `/api/provisioning/instances`                                                  |
| Path parameters                | None                                                                           |
| Query parameters read in route | None read directly; schema validation below may define additional fields       |
| Headers read in route          | No additional direct header reads; authentication helpers may read credentials |

## Authentication and access [#authentication-and-access]

Dashboard session cookie through @repo/auth/server. Some auth-summary routes can redirect to sign-in; inspect the status branches below.

## Request and response definitions [#request-and-response-definitions]

The following named definitions are imported or declared by this route. Optional markers, defaults, bounds, and enum values are shown exactly as implemented. A response type is a source contract, not an example populated with real account data.

### CreateProvisioningBody [#createprovisioningbody]

```typescript
export interface CreateProvisioningBody {
  name?: string;
  mode?: InstanceMode;
  region?: string;
  plan?: PlanTier;
  modules?: ModuleConfig;
  services?: ServiceName[];
  hotLoading?: boolean;
  customHostnames?: string[];
}
```

Source: `minds-ui/apps/app/lib/provisioning/service.ts:36`.

## Method behavior [#method-behavior]

The handler excerpt preserves field validation, response envelopes, cookie changes, and exception branches. Values returned by service helpers retain their named response type above; for passthrough routes, the upstream response is authoritative.

### POST [#post]

```typescript
export async function POST(request: NextRequest) {
  const session = await getSession();
  if (!session) {
    return provisioningAuthError();
  }

  const cookieStore = await cookies();
  let organizationContext;
  try {
    organizationContext = await loadMindsOrganizationContext({
      accessToken: session.accessToken,
      activeTenantId: cookieStore.get(ACTIVE_TENANT_COOKIE)?.value,
      tokenOrgId: session.user.orgId,
      tokenTenantId: session.user.tenantId,
    });
  } catch (error) {
    console.error('Minds organization lookup failed during provisioning:', error);
    return provisioningError(
      'IAM_ORG_LOOKUP_FAILED',
      error instanceof Error ? error.message : 'IAM organization lookup failed.',
      502
    );
  }
  const organizationId = organizationContext.activeOrganizationId;
  if (!organizationId) {
    return provisioningError(
      'NO_ORGANIZATION',
      'Create or select an organization before provisioning Akasha.',
      409,
      { redirectTo: '/organizations' }
    );
  }

  try {
    const body = await request.json().catch(() => ({})) as CreateProvisioningBody;
    const activeOrganization = organizationContext.organizations.find((organization) => organization.id === organizationId);

    const instance = await createProvisionedInstance({
      user: {
        id: session.user.id,
        organizationId,
        accessToken: session.accessToken,
      },
      body,
      fetchEntitlements: async ({ accessToken, organizationId }) => {
        try {
          const entitlements = await fetchGardenEntitlements({
            baseUrl:
              process.env.KEYSTONE_IAM_URL
              || process.env.L1FE_IAM_URL
              || process.env.L1FE_SSO_URL
              || process.env.L1FE_AUTH_URL
              || 'https://id.l1fe.ai',
            accessToken,
            organizationId,
          });
          return applyMindsFreeDedicatedEntitlementFallback({
            body,
            entitlements,
            organization: activeOrganization,
            tenantGroups: organizationContext.tenantGroups,
          });
        } catch (error) {
          const fallback = applyMindsFreeDedicatedEntitlementFallback({
            body,
            entitlements: null,
            organization: activeOrganization,
            tenantGroups: organizationContext.tenantGroups,
            error,
          });
          if (fallback) {
            return fallback;
          }
          throw error;
        }
      },
    });

    return NextResponse.json(
      { instance },
      {
        status: 201,
        headers: {
          'Cache-Control': 'private, no-cache, no-store, must-revalidate',
        },
      }
    );
  } catch (error) {
    if (error instanceof ProvisioningRequestError) {
      return provisioningError('INVALID_PROVISIONING_REQUEST', error.message, error.status);
    }
    if (error instanceof GardenEntitlementError) {
      if (error.status === 401) {
        return provisioningAuthError(error.message);
      }
      return provisioningError('ENTITLEMENT_DENIED', error.message, error.status);
    }
    if (error instanceof RenderProvisioningError) {
      return provisioningError('RENDER_PROVISIONING_FAILED', error.message, normalizeErrorStatus(error.status, 502));
    }
    if (error instanceof CloudflareDnsError) {
      return provisioningError('DNS_PROVISIONING_FAILED', error.message, normalizeErrorStatus(error.status, 502));
    }

    console.error('Minds Akasha provisioning failed:', error);
    return provisioningError(
      'PROVISIONING_FAILED',
      error instanceof Error ? error.message : 'Provisioning failed.',
      500
    );
  }
}
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:34`.

### PATCH [#patch]

```typescript
export async function PATCH(request: NextRequest) {
  const session = await getSession();
  if (!session) {
    return provisioningAuthError();
  }

  const cookieStore = await cookies();
  const organizationContext = await loadMindsOrganizationContext({
    accessToken: session.accessToken,
    activeTenantId: cookieStore.get(ACTIVE_TENANT_COOKIE)?.value,
    tokenOrgId: session.user.orgId,
    tokenTenantId: session.user.tenantId,
  });
  const organizationId = organizationContext.activeOrganizationId;
  if (!organizationId) {
    return provisioningError(
      'NO_ORGANIZATION',
      'Create or select an organization before upgrading a Mind.',
      409,
      { redirectTo: '/organizations' }
    );
  }

  try {
    const body = (await request.json().catch(() => ({}))) as {
      instanceId?: string;
      plan?: string;
      renderServiceId?: string;
      renderServiceName?: string;
      hostname?: string;
      deploymentSlug?: string;
      region?: string;
      services?: string[];
    };

    if (body.plan !== 'pro') {
      return provisioningError(
        'INVALID_UPGRADE',
        'Only Free→Pro in-place upgrades are supported.',
        400
      );
    }
    if (!body.instanceId?.trim()) {
      return provisioningError('INVALID_UPGRADE', 'instanceId is required.', 400);
    }

    // Resolve renderServiceName from org registry when only instanceId is known.
    let renderServiceId = body.renderServiceId?.trim();
    let renderServiceName = body.renderServiceName?.trim();
    let hostname = body.hostname?.trim();
    let deploymentSlug = body.deploymentSlug?.trim();
    let region = body.region?.trim();

    if (!renderServiceId && !renderServiceName) {
      const match = organizationContext.tenantGroups
        .flatMap((group) => group.services)
        .find((service) => service.id === body.instanceId);
      if (match) {
        const cfg = match.config || {};
        renderServiceName =
          typeof cfg.renderServiceName === 'string'
            ? cfg.renderServiceName
            : undefined;
        hostname = hostname || match.endpoint?.replace(/^https?:\/\//, '');
        deploymentSlug =
          deploymentSlug ||
          (typeof cfg.deploymentSlug === 'string' ? cfg.deploymentSlug : undefined);
        region = region || match.region;
        if (typeof cfg.renderServiceId === 'string') {
          renderServiceId = cfg.renderServiceId;
        }
      }
    }

    const result = await upgradeInstanceToPro({
      instanceId: body.instanceId.trim(),
      organizationId,
      plan: 'pro',
      renderServiceId,
      renderServiceName,
      hostname,
      deploymentSlug,
      region,
    });

    return NextResponse.json(
      { instance: result },
      {
        status: 200,
        headers: {
          'Cache-Control': 'private, no-cache, no-store, must-revalidate',
        },
      }
    );
  } catch (error) {
    if (error instanceof UpgradeRequestError) {
      return provisioningError('INVALID_UPGRADE', error.message, error.status);
    }
    if (error instanceof RenderProvisioningError) {
      return provisioningError(
        'RENDER_UPGRADE_FAILED',
        error.message,
        normalizeErrorStatus(error.status, 502)
      );
    }
    console.error('Minds Free→Pro upgrade failed:', error);
    return provisioningError(
      'UPGRADE_FAILED',
      error instanceof Error ? error.message : 'Upgrade failed.',
      500
    );
  }
}
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:152`.

## Response and error branches [#response-and-error-branches]

Literal HTTP statuses in the route: 200, 201. Shared management error classes are defined in [Errors and responses](/docs/api/platform/errors).

```typescript
provisioningAuthError()
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:37`.

```typescript
provisioningError(
      'IAM_ORG_LOOKUP_FAILED',
      error instanceof Error ? error.message : 'IAM organization lookup failed.',
      502
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:51`.

```typescript
provisioningError(
      'NO_ORGANIZATION',
      'Create or select an organization before provisioning Akasha.',
      409,
      { redirectTo: '/organizations' }
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:59`.

```typescript
request.json()
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:68`.

```typescript
NextResponse.json(
      { instance },
      {
        status: 201,
        headers: {
          'Cache-Control': 'private, no-cache, no-store, must-revalidate',
        },
      }
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:112`.

```typescript
provisioningError('INVALID_PROVISIONING_REQUEST', error.message, error.status)
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:123`.

```typescript
provisioningAuthError(error.message)
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:127`.

```typescript
provisioningError('ENTITLEMENT_DENIED', error.message, error.status)
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:129`.

```typescript
provisioningError('RENDER_PROVISIONING_FAILED', error.message, normalizeErrorStatus(error.status, 502))
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:132`.

```typescript
provisioningError('DNS_PROVISIONING_FAILED', error.message, normalizeErrorStatus(error.status, 502))
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:135`.

```typescript
provisioningError(
      'PROVISIONING_FAILED',
      error instanceof Error ? error.message : 'Provisioning failed.',
      500
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:139`.

```typescript
provisioningAuthError()
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:155`.

```typescript
provisioningError(
      'NO_ORGANIZATION',
      'Create or select an organization before upgrading a Mind.',
      409,
      { redirectTo: '/organizations' }
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:167`.

```typescript
request.json()
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:176`.

```typescript
provisioningError(
        'INVALID_UPGRADE',
        'Only Free→Pro in-place upgrades are supported.',
        400
      )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:188`.

```typescript
provisioningError('INVALID_UPGRADE', 'instanceId is required.', 400)
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:195`.

```typescript
NextResponse.json(
      { instance: result },
      {
        status: 200,
        headers: {
          'Cache-Control': 'private, no-cache, no-store, must-revalidate',
        },
      }
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:237`.

```typescript
provisioningError('INVALID_UPGRADE', error.message, error.status)
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:248`.

```typescript
provisioningError(
        'RENDER_UPGRADE_FAILED',
        error.message,
        normalizeErrorStatus(error.status, 502)
      )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:251`.

```typescript
provisioningError(
      'UPGRADE_FAILED',
      error instanceof Error ? error.message : 'Upgrade failed.',
      500
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:258`.

```typescript
provisioningError(
    'UNAUTHENTICATED',
    message,
    401,
    { redirectTo: `/sign-in?returnTo=${encodeURIComponent(PROVISIONING_RETURN_TO)}` }
  )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:267`.

```typescript
NextResponse.json(
    {
      error: {
        code,
        message,
        ...extra,
      },
    },
    { status }
  )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:281`.

```typescript
new ProvisioningRequestError(
      'Shared pool provisioning is retired. Provision a dedicated Mind (mode=dedicated).',
      400
    )
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:312`.

```typescript
new GardenEntitlementError('Garden entitlement check failed.')
```

Source: `minds-ui/apps/app/app/api/provisioning/instances/route.ts:351`.

## Service dependencies [#service-dependencies]

* `minds-ui/apps/app/lib/provisioning/render.ts`
* `minds-ui/apps/app/lib/provisioning/garden.ts`
* `minds-ui/apps/app/lib/provisioning/cloudflare.ts`
* `minds-ui/apps/app/lib/organizations.ts`
* `minds-ui/apps/app/lib/provisioning/service.ts`
* `minds-ui/apps/app/lib/provisioning/upgrade.ts`
* `minds-ui/apps/app/lib/provisioning/types.ts`

## Evidence [#evidence]

Generated from the local source snapshot on 2026-09-12. This page documents implemented code and does not certify a deployed service, permissions configuration, or successful provider operation.

Source file: `minds-ui/apps/app/app/api/provisioning/instances/route.ts`. SHA-256: `dbc8b1ff63383b155e1d277559d5a7d0a01dc38907566a13eb8832c43b10a984`.
