---
title: Self-service callback (redirect) management
path: tutorials/self-service-callbacks
status: published
---

# Self-service callback (redirect) management

An application that runs many instances (one host per tenant, vanity domains,
ephemeral previews) can manage **its own** OAuth redirect/logout URIs — add on
provision, remove on decommission — without a platform-team request each time.
Callbacks are confined to domains the platform approved or the app has proven it
controls; auth-time matching stays exact (no wildcards).

## Enabling it (super-admin, once per app)

In the admin console: **Applications → (app) → Edit → Settings → Self-service
callbacks** — turn on *"Allow this app to self-manage its redirect URIs"* and
list the **approved callback domains** you own (e.g. `scaigit.scailabs.ai`).
Equivalently via API:

```
PATCH /api/v1/admin/applications/{id}
{ "self_service_redirects": true,
  "approved_redirect_domains": ["scaigit.scailabs.ai"] }
```

Approved domains need no proof (you're asserting the platform owns them); the app
may register any https callback under them or their subdomains immediately.

## Using it (the application, with its own token)

Mint a `client_credentials` token with **`scope=application:self`** (the scope
must be in the app's `allowed_scopes`). All calls act on the calling app only —
there is no app id in the path.

```bash
# See current state
GET  /api/v1/apps/self

# Add / remove a callback (must be https and under an approved/verified domain)
POST   /api/v1/apps/self/redirect-uris   {"uri":"https://acme.scaigit.scailabs.ai/user/oauth2/scaikey/callback"}
DELETE /api/v1/apps/self/redirect-uris   {"uri":"https://acme.scaigit.scailabs.ai/user/oauth2/scaikey/callback"}
# Same for logout URIs: POST/DELETE /api/v1/apps/self/logout-uris
```

## Vanity / customer domains — prove ownership, then self-serve

A domain the platform doesn't own (e.g. `git.mosa.cloud`) must be verified first:

```bash
POST /api/v1/apps/self/domains               {"domain":"git.mosa.cloud"}
# → returns a challenge (satisfy EITHER):
#   DNS TXT   _scaikey-challenge.git.mosa.cloud  =  scaikey-verify=<token>
#   HTTPS     GET https://git.mosa.cloud/.well-known/scaikey-challenge/<token>  → <token>
POST /api/v1/apps/self/domains/git.mosa.cloud/verify     # → VERIFIED
# now register callbacks under it:
POST /api/v1/apps/self/redirect-uris  {"uri":"https://git.mosa.cloud/user/oauth2/scaikey/callback"}
```

Because a vanity host must already have DNS pointing at the instance for the
OAuth flow to work, whoever provisions the instance can drop the TXT record (or
serve the well-known path) in the same automation — no human approval.
`DELETE /api/v1/apps/self/domains/{domain}` removes a domain and strips any
callbacks registered under it.

Full endpoint list: [Reference → App Self-Service](/docs/scaikey/reference/api/app-self-service).

## Notes

- **Exact match is unchanged.** Self-service only writes exact `redirect_uris`;
  ScaiKey never matches redirect URIs by wildcard.
- **One client, many instances.** All instances share the app's `client_id`, so
  any registered callback is valid for that client — restrict *which tenants'
  users* may log in with the per-tenant restriction (separate feature), not with
  the callback list.
- **Read-only, own-app-only.** The `application:self` token can't read or modify
  any other application, and can't perform any non-callback write.
