CI HUB Access SDK API

The HTTP API a partner platform calls to embed CI HUB's DAM connectivity in its own product. A partner backend signs an RS256 JWT and exchanges it at POST /auth/exchangeToken for a CI HUB access token (1 hour) and refresh token (30 days); the end user then connects a DAM provider through POST /auth/login and the partner polls GET /auth/login with the returned state. Every content call afterwards carries two tokens — the CI HUB access token in Authorization and the DAM connection token in provider-authorization. Content is read-only in this release: folder browse, keyword search, similarity search from a reference image, asset detail and asset version history, all over one contract no matter which DAM is behind it.

OpenAPI Specification

ci-hub-access-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: CI HUB Access SDK API
  version: v1
  summary: 'Embed CI HUB asset connectivity into a partner platform: exchange a partner-signed JWT for
    a CI HUB session, connect an end user to a DAM provider, then browse, search and read assets from
    that DAM over one uniform contract.'
  description: 'The CI HUB Access SDK API is the HTTP surface a partner platform calls to reach any DAM,
    MAM, PIM, cloud-storage or work-management system CI HUB connects to, without integrating each one
    separately.


    Authentication is a token exchange: the partner backend signs an RS256 JWT for the user and exchanges
    it at `POST /auth/exchangeToken` for a CI HUB access token (1 hour) and refresh token (30 days). The
    end user then connects a DAM provider through `POST /auth/login`, which returns a redirect URI plus
    a `state` the partner polls at `GET /auth/login`. From that point every content call carries two tokens:
    the CI HUB access token in `Authorization` and the DAM connection token in `provider-authorization`.


    Content is read-only in this release: folder browse, keyword search, similarity search by reference
    image, asset detail and asset version history. Every failure returns one error envelope whose `error.source`
    separates CI HUB platform faults (`cihub`) from DAM provider faults (`integration`).


    The API is served under the `/api/v1` prefix and is not versioned beyond it; additive changes ship
    in place, breaking changes are announced on the changelog before they ship.'
  termsOfService: https://ci-hub.com/legal/terms
  contact:
    name: CI HUB GmbH
    url: https://developer.ci-hub.com/access
externalDocs:
  description: CI HUB Access SDK reference
  url: https://developer.ci-hub.com/access
servers:
- url: https://live.ci-hub.com/api/v1
  description: Production
- url: https://stage.ci-hub.com/api/v1
  description: Staging / integration environment used throughout the published examples
security:
- cihubAccessToken: []
paths:
  /auth/exchangeToken:
    post:
      operationId: exchangeToken
      summary: Exchange token
      description: 'Exchanges a partner-signed JWT for CI HUB access and refresh tokens. Required as the

        first call of every Access SDK session.


        ## Partner JWT


        Send the partner-signed JWT as `Authorization: Bearer <partner-signed-jwt>`.


        ### Header


        | Field | Required | Value |

        |---|---|---|

        | `alg` | yes | `RS256` |

        | `kid` | yes | Must match a key published in the partner JWKS |

        | `typ` | optional | `JWT` |


        `HS256` is not accepted.


        ### Payload


        | Claim | Type | Required | Notes |

        |---|---|---|---|

        | `iss` | string | yes | Partner issuer URL. Must match the registered value exactly, including
        trailing slash. |

        | `aud` | string | yes | Registered audience. Default `https://api.ci-hub.com`. |

        | `sub` | string | yes | Stable identifier for the user in the partner system. |

        | `iat` | number | yes | Unix seconds. Tolerance: up to 30 seconds in the future. The token is
        rejected once it is older than `maxTokenAge` (now minus `iat`). |

        | `exp` | number | yes | Unix seconds. Must be in the future. |

        | `email` | string | yes (in JWT or body) | Used for just-in-time (JIT) user resolution: CI HUB
        finds the matching user or creates one on first exchange. Must parse as an email. JWT value takes
        precedence over body. |

        | `given_name` | string | optional | First name. Falls back to splitting `name`. |

        | `family_name` | string | optional | Last name. Falls back to splitting `name`. |

        | `name` | string | optional | Display name. Used when `given_name` and `family_name` are absent.
        |


        The token must be no older than `maxTokenAge` seconds (default 3600), measured from

        `iat` to the current time. Sign a fresh JWT for each exchange. Stale tokens are

        rejected with `cihub-sdk-token-invalid`.


        Send `Content-Type: application/json` so the JSON body parser picks up the request.

        Only an `application/json` body is parsed for the `email` field; other content types

        leave it unread. Name fields are not read from the body. They come from the partner

        JWT claims (`given_name`, `family_name`, `name`).'
      requestBody:
        description: 'Optional. Used only as a fallback for partners that cannot include the `email`

          claim in the partner JWT. If the JWT contains `email`, the body value is ignored.

          The request must include `email` somewhere (JWT or body). If your JWT already

          carries `email`, send `{}`.'
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  description: Fallback email for JIT user resolution when the JWT has no `email` claim.
                  format: email
                  examples:
                  - jane@customer.example.com
      responses:
        '200':
          description: '@description Token exchange successful.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                    description: 'CI HUB access token. Sent on subsequent calls as `Authorization: Bearer
                      <access_token>`. Valid for 1 hour.'
                    examples:
                    - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  refresh_token:
                    type: string
                    description: CI HUB refresh token. 30-day lifetime. Used to mint new access tokens.
                    examples:
                    - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  expires_in:
                    type: number
                    description: Access token lifetime in seconds. Always 3600.
                    examples:
                    - 3600
                  token_type:
                    type: string
                    const: Bearer
                    description: Always `Bearer`.
                required:
                - access_token
                - refresh_token
                - expires_in
                - token_type
        '400':
          description: '`cihub-sdk-email-missing`: no `email` claim and no body fallback, or the value

            fails format validation.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: '| Code | When |

            |---|---|

            | `cihub-sdk-token-missing` | No `Authorization` header. |

            | `cihub-sdk-token-invalid` | Malformed JWT, wrong algorithm, missing required field, signature
            failed, `iat` more than 30s in the future, token older than `maxTokenAge` (now minus `iat`).
            |

            | `cihub-sdk-token-expired` | JWT past `exp`. |


            `cihub-sdk-token-invalid` is rarely also returned when CI HUB fails to persist

            the user during the exchange (transient backend fault). If a known-good JWT

            suddenly fails, retry once before treating the token as the problem.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '402':
          description: '`cihub-sdk-no-subscription`: the partner company has no active SDK subscription.

            Contact CI HUB before retrying.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: '| Code | When |

            |---|---|

            | `cihub-sdk-partner-unknown` | `iss` claim not registered as an SDK partner. |

            | `cihub-sdk-audience-invalid` | `aud` claim does not match the registered audience. |'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: '`cihub-rate-limited`: the partner exceeded its exchange rate limit. Standard

            `RateLimit-*` headers describe the window; back off and retry after it resets.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
      - cihubAccessToken: []
  /auth/checkToken:
    get:
      operationId: checkTokenSdk
      summary: Check token
      description: 'Verifies that a CI HUB access token is still valid and returns the user profile

        envelope the partner platform needs to display in its UI. Typical use: confirm an

        active session before rendering a CI HUB-backed view, or revalidate after a long

        idle period.


        `licenseState`, `licenseExpires`, and `isTrialLicense` describe the SDK

        subscription, not the DAM provider. They are informational; the SDK subscription

        itself is re-checked on every call.


        A 401 here is the cue to start a new exchange. A 402 means the subscription state

        changed: contact CI HUB before retrying.'
      responses:
        '200':
          description: '@description Token is valid.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  adapter:
                    type: string
                    description: Always "CI HUB".
                    examples:
                    - CI HUB
                  source:
                    type: string
                    description: CI HUB host that served the response.
                    examples:
                    - api.ci-hub.com
                  user:
                    type: string
                    description: Display name composed from the user record.
                    examples:
                    - Jane Doe
                  account:
                    type: string
                    description: Email address tied to the user record.
                    examples:
                    - jane@customer.example.com
                  licenseState:
                    type: string
                    description: Always "Subscription" for SDK partners.
                    examples:
                    - Subscription
                  licenseExpires:
                    type: number
                    description: 'Unix milliseconds. End of the active SDK subscription''s last day in
                      UTC.

                      Perpetual subscriptions emit a far-future sentinel; treat any value greater

                      than `Date.now()` as valid.'
                    examples:
                    - 1798761599999
                  isTrialLicense:
                    type: boolean
                    description: Always false for SDK partners.
                    examples:
                    - false
                  userHash:
                    type: string
                    description: Stable, opaque per-user identifier suitable for partner-side analytics.
                    examples:
                    - a1b2c3d4e5f6g7h8i9j0k1l2
        '401':
          description: '| Code | When |

            |---|---|

            | `cihub-access-token-missing` | No `Authorization` header. |

            | `cihub-access-token-invalid` | Token signature failed, token expired, or the user record
            was removed. |'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '402':
          description: '@description `cihub-sdk-no-subscription`: the partner''s SDK subscription is no
            longer active.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: '`cihub-sdk-partner-unknown`: the partner registration was removed since the

            access token was issued.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: '@description `cihub-internal-error`: the subscription re-check failed transiently.
            Safe to retry.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
      - cihubAccessToken: []
  /auth/refreshToken:
    get:
      operationId: refreshTokenSdk
      summary: Refresh token
      description: 'Renews a session. This is a shared endpoint: the token in `provider-authorization`

        decides which session is renewed.


        ## CI HUB session


        Send the CI HUB refresh token returned at exchange in `provider-authorization` to

        mint a new access token and a new refresh token. `Authorization` carries the current

        access token, which may already be expired (only its signature is checked here).

        The refresh token''s `sub` must match the access token''s `sub`; cross-user refresh

        attempts are rejected.


        The new access token is valid for 1 hour, the new refresh token for 30 days.

        Replace both cached tokens with the values returned here. The previous access token

        is superseded and should be discarded by the client, but stays valid until its

        `exp`; the previous refresh token remains valid until its 30-day clock runs out, so

        a slow client switch-over is safe.


        Refresh proactively a few minutes before `expires_in`, or reactively after

        receiving `cihub-access-token-invalid` from any endpoint. Once a refresh token

        expires the partner must perform a new exchange.


        ## DAM connection


        Send the DAM `refresh_token` from the login poll in `provider-authorization` to

        renew a DAM connection token. The token also identifies the provider. Some

        providers only return a new `access_token`; in that case keep the prior

        `refresh_token` and reuse it on the next refresh. A provider with no refresh path

        returns 404: run a fresh DAM login. Handle every provider the same way: try to

        refresh, and fall back to a fresh login if the refresh fails.


        The CI HUB SDK subscription is re-checked on every refresh; partners whose

        subscription lapsed receive 402 here and must contact CI HUB before continuing.'
      responses:
        '200':
          description: '@description Token refreshed successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                    description: New access token (CI HUB session) or new DAM connection token, matching
                      the refreshed session.
                    examples:
                    - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  refresh_token:
                    type: string
                    description: New refresh token. Some DAM providers omit it; keep and reuse the prior
                      refresh token then.
                    examples:
                    - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  expires_in:
                    type: number
                    description: Access token lifetime in seconds. Omitted when the DAM provider does
                      not supply a token lifetime.
                    examples:
                    - 3600
                  token_type:
                    type: string
                    description: Token type for the Authorization header.
                    examples:
                    - Bearer
                required:
                - access_token
                - token_type
        '401':
          description: '| Code | When |

            |---|---|

            | `cihub-access-token-missing` | No `Authorization` header. |

            | `cihub-access-token-invalid` | Access token signature failed or was malformed. An expired
            access token is accepted here; only signature and format are checked. |

            | `cihub-refresh-token-invalid` | The `provider-authorization` token is not a refresh token
            (for example an access token sent in its place), or its `sub` does not match the access token''s
            `sub`. |


            A `cihub-refresh-token-invalid`, and any failure once the 30-day refresh window

            has lapsed, is the cue to start a new exchange.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '402':
          description: '@description `cihub-sdk-no-subscription`: the partner''s SDK subscription is no
            longer active.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: '| Code | When |

            |---|---|

            | `provider-access-token-missing` | No `provider-authorization` header. |

            | `provider-access-token-invalid` | Refresh token signature failed, was malformed, or has
            expired. |

            | `cihub-sdk-partner-unknown` | The partner registration was removed since the tokens were
            issued. |'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: 'The DAM provider has no refresh path. No structured `error` envelope. Run a

            fresh DAM login.'
        '503':
          description: '@description `cihub-internal-error`: the subscription re-check failed transiently.
            Safe to retry.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
      - cihubAccessToken: []
        damToken: []
  /auth/logout:
    get:
      operationId: logoutSdk
      summary: Logout
      description: 'Signals the end of a partner-side session. For Access SDK partners this endpoint is

        advisory: CI HUB does not maintain server-side session state for SDK tokens, so

        logout returns 200 without revoking the refresh token. The partner platform is

        responsible for discarding the cached access and refresh tokens locally.


        CI HUB does not currently maintain a refresh-token blocklist for Access SDK

        partners. A refresh token remains valid until its 30-day clock runs out, even after

        a logout call. Partners that need stronger revocation guarantees should:


        - Drop both the access token and the refresh token from local storage on logout.

        - Not persist refresh tokens beyond the active session.

        - Detect compromise on the partner side and avoid reusing a leaked refresh token.


        Server-side revocation is in progress.'
      responses:
        '200':
          description: '@description Logout acknowledged. The response body is the literal string `OK`.'
        '401':
          description: '| Code | When |

            |---|---|

            | `cihub-access-token-missing` | No `Authorization` header. |

            | `cihub-access-token-invalid` | Access token signature failed or was malformed. |


            A 401 here is harmless from a logout perspective: the token was not valid to

            begin with. The partner should still discard local copies.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
      - cihubAccessToken: []
  /auth/providers:
    get:
      operationId: getProvidersSdk
      summary: Get providers
      description: 'Returns the list of DAM providers available to the authenticated CI HUB user. The

        partner platform uses the result to render a connection picker and to pass each

        provider''s `id` on subsequent login and content calls.


        This endpoint is intentionally lenient on authentication so the partner platform

        can render its connection picker before any DAM session exists. A missing or

        invalid `Authorization` header returns 200 with a single-entry list containing

        only the CI HUB platform connection. When the list contains only `cihub`, refresh

        the access token or start a new exchange.


        The provider list is stable for the lifetime of one access token. Cache it for

        that long and refetch after a new exchange.'
      responses:
        '200':
          description: '@description List of available providers'
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      description: '@description Provider identifier'
                    name:
                      type: string
                      description: '@description Provider display name'
                    version:
                      type: string
                      description: '@description Provider version'
                    logo:
                      type: object
                      properties:
                        data:
                          type: string
                          description: Base64 encoded image data
                          examples:
                          - data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
                        width:
                          type: number
                          description: Logo width in pixels
                          examples:
                          - 249
                        height:
                          type: number
                          description: Logo height in pixels
                          examples:
                          - 77
                        backgroundColor:
                          type: string
                          description: Background color in hex format
                          examples:
                          - '#FFFFFF'
                      description: '@description Provider logo with metadata.'
                    glyph:
                      type: object
                      properties:
                        data:
                          type: string
                          description: Base64 encoded image data
                          examples:
                          - data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
                        width:
                          type: number
                          description: Glyph width in pixels
                          examples:
                          - 249
                        height:
                          type: number
                          description: Glyph height in pixels
                          examples:
                          - 77
                        backgroundColor:
                          type: string
                          description: Background color in hex format
                          examples:
                          - '#FFFFFF'
                      description: '@description Provider glyph/icon with metadata'
                    capabilities:
                      $ref: '#/components/schemas/ProviderCapabilities'
                    loginUrl:
                      type: string
                      description: '@description Login URL for this provider. POST it with `?polling=true`
                        to run the JSON polling login flow.'
                    isDefault:
                      type: boolean
                      description: '@description Whether this is a default provider'
                    isOptional:
                      type: boolean
                      description: '@description Whether this is an optional provider'
                    isLicensed:
                      type: boolean
                      description: '@description Whether the provider requires a paid tier. This does
                        not indicate whether the current end user holds that license.'
                    isUnsupported:
                      type: boolean
                      description: Present with value `true` only when the provider is wired but no longer
                        maintained. Absent otherwise; `false` is never sent.
                      examples:
                      - true
                    unsupportedAdapterTitle:
                      type: string
                      description: Title to display when the provider is unsupported. May be absent even
                        when `isUnsupported` is `true`; null-guard before reading.
                      examples:
                      - Ask your DAM Vendor
                    unsupportedAdapterDescription:
                      type: string
                      description: Description to display when the provider is unsupported. May be absent
                        even when `isUnsupported` is `true`; null-guard before reading.
                      examples:
                      - This Solution is not supported today, please ask the DAM Vendor for a status.
                    isDisabledForHost:
                      type: boolean
                      description: '@description Whether this provider is disabled for the current host.'
        '500':
          description: '@description Transient backend failure. Safe to retry.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
      - cihubAccessToken: []
  /auth/login:
    get:
      operationId: damLoginPoll
      summary: DAM login (poll)
      description: '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.'
      parameters:
      - name: state
        in: query
        required: true
        description: '@description The `state` returned by initiate. Identifies and authorizes the call.'
        schema:
          type: string
      - name: polling
        in: query
        required: true
        description: '@description Send `true` on every initiate and poll call; it selects the JSON response
          shape.'
        schema:
          type: boolean
          const: true
      responses:
        '200':
          description: 'While the state is alive and the user has not finished signing in, the poll

            returns an empty object. Once the login completes, it returns the tokens. A

            dead `state` (unknown, expired, consumed, or failed login) returns an `error`

            property.'
          content:
            application/json:
              schema:
                anyOf:
                - type: object
                - type: object
                  properties:
                    access_token:
                      type: string
                      description: '@description DAM connection token. Send it as the `provider-authorization`
                        header on content calls.'
                    refresh_token:
                      type: string
                      description: '@description DAM refresh token. Use it to renew the connection.'
                    userHash:
                      type: string
                      description: '@description Stable, opaque per-user identifier for the connected
                        DAM account. Optional for partners to use.'
                  required:
                  - access_token
                  - refresh_token
                - type: object
                  properties:
                    error:
                      type: string
                      description: Plain-text failure marker.
                      examples:
                      - Not found
                  required:
                  - error
      security:
      - cihubAccessToken: []
    post:
      operationId: damLoginInitiate
      summary: DAM login (initiate)
      description: '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.'
      parameters:
      - name: provider
        in: query
        required: true
        description: '@description Provider identifier from the providers listing.'
        schema:
          type: string
      - name: polling
        in: query
        required: true
        description: '@description Send `true` on every initiate and poll call; it selects the JSON response
          shape.'
        schema:
          type: boolean
          const: true
      - name: serverUrl
        in: query
        description: 'Provider parameter. The DAM instance the end user signs in to, as a full origin.

          Read by `bynder`, `dash`, `fotoware`, `frontify`, `picturepark`, and `purered`.'
        schema:
          type: string
      responses:
        '200':
          description: '@description Login initiated.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  redirect_uri:
                    type: string
                    description: One-time URL to open in the end user's browser so they can authenticate
                      at the DAM.
                    examples:
                    - https://provider.example.com/oauth/authorize?...&state=Pf3a9c...
                  state:
                    type: string
                    description: Opaque token that identifies this login. Pass it to the poll call.
                    examples:
                    - Pf3a9c...
                required:
                - redirect_uri
                - state
        '302':
          description: 'Legacy failure path: an unknown provider, a provider the user is not licensed

   

# --- truncated at 32 KB (102 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/ci-hub/refs/heads/main/openapi/ci-hub-access-openapi.yml