CI HUBCI HUB SDK
AuthenticationDAM Auth

DAM login

POST and GET /auth/login, initiate and poll a DAM provider login.

DAM login is a two-call flow: initiate a login for a chosen provider, then poll until the end user finishes signing in at the DAM. The completed poll returns the tokens the partner sends on every content call. The flow is summarized on the DAM connection concept page.

Initiate

POST
/auth/login

Starts a login for the provider named in the provider query parameter (an id from the providers listing). DAM login is a two-call flow: the partner platform starts a login for a chosen provider, opens the returned URL in the end user's browser, then polls until the end user finishes signing in at the DAM.

Open redirect_uri for the end user (popup or full-page redirect). Keep state for polling.

Failures past the token check (an unknown provider, a provider the user is not licensed for, or a rejected sign-in) have not moved to the error envelope yet. They currently redirect the browser to a CI HUB URL carrying an error query parameter with a plain-text message. Treat a redirect or non-JSON response from initiate as a failure and read the error value.

Provider parameters

Several providers are multi-tenant, so CI HUB asks the end user which instance of the DAM to sign in to on a page of its own before the provider's login. A partner platform that already knows the instance sends it as serverUrl on the initiate call, and that page drops out of the flow. bynder, dash, fotoware, frontify, picturepark, and purered read it.

It is optional. Omit it and the end user answers the prompt as before. A value the provider rejects also falls back to the prompt, so a stale instance URL degrades instead of failing the login.

Authorization

CIHubAuth
AuthorizationBearer <token>

The CI-HUB JWT token obtained through authentication. Needs to be sent in the Authorization header.

In: header

Query Parameters

provider*string

Provider identifier from the providers listing.

polling*boolean

Send true on every initiate and poll call; it selects the JSON response shape.

serverUrl?string

Provider parameter. The DAM instance the end user signs in to, as a full origin. Read by bynder, dash, fotoware, frontify, picturepark, and purered.

Response Body

application/json

application/json

curl -X POST "https://example.com/auth/login?provider=bynder&polling=true"
{
  "redirect_uri": "https://provider.example.com/oauth/authorize?...&state=Pf3a9c...",
  "state": "Pf3a9c..."
}
Empty
{
  "message": "Error",
  "details": "POST /api/v1/auth/exchangeToken failed: SDK authentication token is invalid",
  "errorCode": "cihub-sdk-token-invalid",
  "error": {
    "code": "integration-forbidden",
    "source": "cihub",
    "status": 400,
    "message": "Access denied by the integration",
    "details": "403 Forbidden - insufficient_permissions",
    "provider": "bynder"
  }
}

Example

import { CiHubAccessClient, TokenManager } from '@ci-hub/access-sdk'

const client = new CiHubAccessClient({ baseUrl: 'https://stage.ci-hub.com/api/v1' })
const tokens = new TokenManager(client) // seeded during authentication

const login = await tokens.withCihubAuth((accessToken) =>
  client.beginDamLogin({ provider: 'bynder', accessToken }),
)
// Open login.redirect_uri for the end user, then poll with login.state.
curl -X POST "https://stage.ci-hub.com/api/v1/auth/login?provider=bynder&polling=true" \
  -H "Authorization: Bearer $CI_HUB_ACCESS_TOKEN"
const response = await fetch(
  'https://stage.ci-hub.com/api/v1/auth/login?provider=bynder&polling=true',
  {
    method: 'POST',
    headers: { Authorization: `Bearer ${ciHubAccessToken}` },
    redirect: 'manual',
  }
)

// Failures past the token check redirect to a CI HUB URL with an `error` param instead of returning JSON.
if (response.status >= 300 && response.status < 400) {
  const location = response.headers.get('location') ?? ''
  const error = new URL(location, 'https://stage.ci-hub.com').searchParams.get('error')
  throw new Error(error ?? 'DAM login initiate failed')
}
if (!response.ok) {
  throw new Error(`DAM login initiate failed (${response.status})`)
}

const { redirect_uri, state } = await response.json()
// open redirect_uri for the end user, then poll with state

Provider parameters

Several providers are multi-tenant, so CI HUB asks the end user which instance of the DAM to sign in to on a page of its own before the provider's login. A partner platform that already knows the instance sends it as serverUrl on the initiate call, and that page drops out of the flow. bynder, dash, fotoware, frontify, picturepark, and purered read it.

It is optional. Omit it and the end user answers the prompt as before. A value the provider rejects falls back to the prompt instead of failing the login, so a stale instance URL degrades rather than breaks. Parameters a provider does not read are ignored.

const login = await tokens.withCihubAuth((accessToken) =>
  client.beginDamLogin({
    provider: 'frontify',
    accessToken,
    options: { providerParams: { serverUrl: 'https://acme.frontify.com' } },
  }),
)
curl -X POST "https://stage.ci-hub.com/api/v1/auth/login?provider=frontify&polling=true&serverUrl=https%3A%2F%2Facme.frontify.com" \
  -H "Authorization: Bearer $CI_HUB_ACCESS_TOKEN"
const url = new URL('https://stage.ci-hub.com/api/v1/auth/login')
url.searchParams.set('provider', 'frontify')
url.searchParams.set('polling', 'true')
url.searchParams.set('serverUrl', 'https://acme.frontify.com')

const response = await fetch(url, {
  method: 'POST',
  headers: { Authorization: `Bearer ${ciHubAccessToken}` },
  redirect: 'manual',
})

Poll

GET
/auth/login

Returns the current state of a login started by initiate. The state value is the credential for this call, so no Authorization header is needed. Poll on an interval until the response carries tokens or an error.

The returned access_token is the DAM connection token and is separate from the CI HUB access token in your Authorization header. Content calls carry both: the CI HUB token in Authorization and this token in provider-authorization.

A successful poll consumes the state. A state lives for 5 minutes from initiate; once it expires (or after a successful poll), further polls take the failure path. Cap your loop at the 5-minute horizon and fall back to a fresh login on failure.

The poll always answers 200 with a JSON body. An unknown, expired, or consumed state, and a login the end user failed or canceled at the DAM, all return a body carrying an error property instead of tokens. Treat any body with error as final and start a new DAM login.

Query Parameters

state*string

The state returned by initiate. Identifies and authorizes the call.

polling*boolean

Send true on every initiate and poll call; it selects the JSON response shape.

Response Body

application/json

curl -X GET "https://example.com/auth/login?state=string&polling=true"
{}

Example

The poll always answers 200 with a JSON body. A pending login returns {}, a completed login returns the tokens, and a dead state (unknown, expired, consumed) or a failed sign-in returns { error }. Branch on the body, not on the HTTP status.

// Polls until the login completes, the sign-in fails, or the state expires.
const damTokens = await client.waitForDamLogin({ state: login.state })
await tokens.setDamSession('bynder', damTokens)
curl "https://stage.ci-hub.com/api/v1/auth/login?state=$STATE&polling=true"
async function pollLogin(state: string) {
  const deadline = Date.now() + 5 * 60 * 1000 // state lives 5 minutes

  while (Date.now() < deadline) {
    const response = await fetch(
      `https://stage.ci-hub.com/api/v1/auth/login?state=${encodeURIComponent(state)}&polling=true`
    )

    const body = (await response.json()) as {
      access_token?: string
      refresh_token?: string
      userHash?: string
      error?: string
    }

    // Failed: a dead state or a sign-in the end user did not complete.
    if (body.error) throw new Error(`DAM login failed: ${body.error}`)

    // Completed: the DAM connection tokens are present.
    if (body.access_token) return body

    // Pending: empty object. Keep polling.
    await new Promise((r) => setTimeout(r, 1500))
  }

  throw new Error('DAM login timed out')
}

Next

On this page