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
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 The CI-HUB JWT token obtained through authentication. Needs to be sent in the Authorization header.
In: header
Query Parameters
Provider identifier from the providers listing.
Send true on every initiate and poll call; it selects the JSON response shape.
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..."
}{
"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 stateProvider 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
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
The state returned by initiate. Identifies and authorizes the call.
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')
}