# Inflo Share — Agent / CLI Quick Reference

> Plain-text docs for AI agents (Claude, ChatGPT, Cursor, DeepSeek, scripts).
> The full UI docs at https://infloapp.com/app/developer/api/agents render in a SPA, so
> use this file (or the OpenAPI spec) when calling from curl/HTTP.

## Two hosts, two jobs

  - https://infloapp.com        → developer/business API (this file, PATs, app
                          registration, /api/v1/*). Does **NOT** host
                          end-user OIDC sign-in.
  - https://sso.infloapp.com      → the OIDC issuer for end-user sign-in
                          (authorization_code, id_token, userinfo).

If you are wiring up an end-user OIDC login flow with a library like
`openid-client`, point it at the **SSO issuer**, not at the API host.
A request like `GET https://infloapp.com/.well-known/openid-configuration` will
302-redirect to https://sso.infloapp.com/.well-known/openid-configuration so
misconfigured clients can self-correct.

  Discovery:    https://sso.infloapp.com/.well-known/openid-configuration
  JWKS:         https://sso.infloapp.com/.well-known/jwks.json
  Authorize:    https://sso.infloapp.com/oauth/authorize
  Token:        https://sso.infloapp.com/oauth/token
  Userinfo:     https://sso.infloapp.com/oauth/userinfo
  End session:  https://sso.infloapp.com/oauth/logout

After registering an app via the endpoint below, the response includes an
`oidc` block with all of these URLs pre-filled.

## TL;DR

You authenticate with a **Personal Access Token (PAT)** — a long-lived bearer
token your developer creates at:

  https://infloapp.com/app/developer/myapps  (section: Personal Access Tokens)

Token format: `infpat_` followed by 64 hex chars. Send it as:

  Authorization: Bearer infpat_<your_token>

Scopes:
  - apps:read    — list apps the user owns or co-manages
  - apps:create  — register a new app
  - apps:manage  — view client_secret / access_token

## Endpoint: register a new SSO/OIDC app (RFC 7591)

If you are using a standards-aware OIDC library (`openid-client`, etc.) that
expects `registration_endpoint` in the discovery document, point it at:

  POST https://infloapp.com/oidc/register
  Headers:
    Authorization: Bearer infpat_<token>   (PAT = RFC 7591 initial_access_token)
    Content-Type: application/json
  Body (RFC 7591 §2):
    {
      "client_name":   "Acme Helpdesk",
      "redirect_uris": ["https://acme.example.com/sso/callback"],
      "grant_types":   ["authorization_code", "refresh_token"],
      "response_types":["code"],
      "token_endpoint_auth_method": "client_secret_basic",
      "scope":         "openid profile email",
      "application_type": "web"          (optional; "web" or "native")
    }
  Success (201): RFC 7591 §3.2.1 response shape:
    {
      "client_id":                  "...",
      "client_secret":              "...",
      "client_id_issued_at":        1700000000,
      "client_secret_expires_at":   0,
      "registration_access_token":  "...",
      "registration_client_uri":    "https://infloapp.com/oidc/register/<client_id>",
      "client_name":                "Acme Helpdesk",
      "redirect_uris":              [...],
      "grant_types":                [...],
      "response_types":             [...],
      "token_endpoint_auth_method": "client_secret_basic",
      "scope":                      "openid profile email",
      "application_type":           "web"
    }

Manage the registration via RFC 7592 against `registration_client_uri`:

  GET    https://infloapp.com/oidc/register/<client_id>   (read)
  PUT    https://infloapp.com/oidc/register/<client_id>   (update redirect_uris / client_name)
  DELETE https://infloapp.com/oidc/register/<client_id>   (deregister)

All three require `Authorization: Bearer <registration_access_token>` —
the fresh token returned at registration time, NOT your PAT.

> **We don't do anonymous DCR.** RFC 7591 §3 allows registrations to be
> gated by an `initial_access_token`. We use your **Personal Access Token**
> for that. There is no open client registration. Pass your PAT as
> `Authorization: Bearer` on `POST /oidc/register`.

The `/oidc/register` endpoint is also mirrored under
`https://infloapp.com/api/oidc/register` for clients that prefer the `/api` prefix.

## Endpoint: register a new SSO/OIDC app (proprietary)

  POST https://infloapp.com/api/clp/register-app
  Headers:
    Authorization: Bearer infpat_<token>      (must include scope apps:create)
    Content-Type: application/json
    Idempotency-Key: <uuid>                   (optional, recommended; 24h replay)
  Body:
    {
      "appName":      "Acme Helpdesk",        (required, 2-100 chars)
      "platformType": "web",                   (one of: web, mobile, desktop, server)
      "redirectUris": ["https://acme.example.com/sso/callback"], (optional; default derived)
      "orgId":        "123",                   (optional; pick one of your orgs)
      "personal":     false                    (optional; create under personal entity)
    }
  Success (201):
    {
      "success": true,
      "app": {
        "id":           "<clientId>",
        "name":         "Acme Helpdesk",
        "clientSecret": "<one-time secret>",
        "accessToken":  "<one-time token>",
        "hash":         "<app hash>",
        "platformType": "web",
        "infloOrgId":   123,
        "redirectUris": [ ... ],
        "isActive":     true,
        "ssoEnabled":   true
      },
      "oidc": {
        "issuer":               "https://sso.infloapp.com",
        "discoveryUrl":         "https://sso.infloapp.com/.well-known/openid-configuration",
        "authorizationEndpoint":"https://sso.infloapp.com/oauth/authorize",
        "tokenEndpoint":        "https://sso.infloapp.com/oauth/token",
        "userinfoEndpoint":     "https://sso.infloapp.com/oauth/userinfo",
        "jwksUri":              "https://sso.infloapp.com/.well-known/jwks.json",
        "endSessionEndpoint":   "https://sso.infloapp.com/oauth/logout"
      },
      "message": "Application registered! Save your credentials - they will not be shown again."
    }

The Idempotency-Key replays the same response (incl. credentials) for 24h, so
the agent can safely retry on transient failures without creating duplicates.

## Duplicate protection

In addition to Idempotency-Key, the server enforces a soft duplicate guard:
if you POST register-app with the same `appName` under the same org within
a 10-minute window, you'll get:

  HTTP 409 Conflict
  {
    "error":         "duplicate_app",
    "message":       "...",
    "existingAppId": "<uuid>",
    "hint":          "Send Idempotency-Key, GET .../credentials, or rename."
  }

To avoid hitting this:
  - Always send an `Idempotency-Key` header on retries.
  - If the user genuinely wants a second app with the same name, give it
    a distinct name (e.g. "Acme Helpdesk (Staging)").
  - Redirect URIs are canonicalised on store (lowercased host, trailing
    slashes stripped, default ports removed), so cosmetic differences in
    redirect URIs do NOT count as a different app.

## Example: curl

  curl -X POST https://infloapp.com/api/clp/register-app \
    -H "Authorization: Bearer $INFLO_PAT" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "appName": "Acme Helpdesk",
      "platformType": "web",
      "redirectUris": ["https://acme.example.com/sso/callback"]
    }'

## Endpoint: list my apps

  GET  https://infloapp.com/api/clp/my-apps
  Headers: Authorization: Bearer infpat_<token>   (scope: apps:read)

## Endpoint: view credentials for an existing app

  GET  https://infloapp.com/api/clp/my-apps/<appId>/credentials
  Headers: Authorization: Bearer infpat_<token>   (scope: apps:manage)

## OpenAPI spec (machine-readable)

  https://infloapp.com/openapi.json             (canonical JSON, served fresh from source)
  https://infloapp.com/.well-known/api-catalog  (RFC 9727 discovery linkset)
  https://infloapp.com/api/clp/openapi.yaml     (legacy YAML mirror)

## Errors

  401 — missing / invalid / revoked / expired token
  403 — token lacks required scope, or is bound to entityIds and the
        target org isn't in the allowlist
  409 — duplicate_app: same name + org within 10-minute window. Inspect
        existingAppId in the response body and either reuse it or rename.
  400 — body validation failed (see "details" field)

## Common pitfalls

  - Hitting the SPA host with curl returns HTML. The API host is the same
    origin — use /api/clp/* paths, not /app/developer/api/* (those are docs).
  - clientSecret and accessToken are shown ONCE at creation. Capture both.
  - For end-user OAuth login flows, use SSO/OAuth — PATs are for headless
    developer / agent operations, not user sign-in.
  - "ClientError: invalid response encountered" from `openid-client` almost
    always means the issuer URL points at https://infloapp.com instead of
    https://sso.infloapp.com. Fix the issuer in your OIDC client config.
