> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shftd2.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Publishers integrate one API: GET https://api.shftd2.com/v1/fragment?partner_id=<account id>&page_url=<page URL>, called from their edge for AI-agent requests only, with the API key in the X-API-Key header, never in the query string.
> A 200 carries the card. A 204 means no card: serve the page unchanged.
> The management API (https://api.shftd2.com/v1/me, /v1/sites, /v1/cards, /v1/keys) is optional tooling for scripting setup. It takes its own management token (Authorization: Bearer ds_mgmt_…). Its limits (120 requests and 60 writes per minute per token) do not apply to the delivery API.
> Delivery logs reach doubleshift only through direct delivery logs (POST https://logs.shftd2.com/v1/beacon with a logs token; the edge library with a logsToken is its provided client, and a custom sender may use the same endpoint) or a Cloudflare Logpush job configured with doubleshift. Otherwise doubleshift sees only its own API activity.

# Get the card for a page

> Returns a card for the page named by page_url. The server finds the account's site for the page host, then the fragment (a routing rule of the site, else its default fragment), then an eligible card. Authentication: the X-API-Key header only. An api_key query parameter is ignored, so a call without the header answers 401 once it reaches authentication (HEAD, parameter and page_url checks come first). A parameter that would override the resolved fragment is refused (400). HEAD answers 405. A POST with a non-empty Origin header whose host or port differs from the request's answers 403; other methods answer 405.

X-ABC-Resolution values, the rate limit and the maximum page_url length may change.

> [!NOTE]
>
> How the server goes from `page_url` to a fragment, and from a fragment to a card: [resolution](https://docs.shftd2.com/how-it-works.md#resolution) and [card selection](https://docs.shftd2.com/how-it-works.md#selection).


## OpenAPI

````yaml /openapi.json get /v1/fragment
openapi: 3.1.0
info:
  title: doubleshift fragment API
  version: 0.16.1
  description: >-
    Serves a publisher's Agentic Branded Content card to its edge. GET
    /v1/fragment is the call for every integration: partner_id and page_url, the
    API key in the X-API-Key header. Every response, whatever its status,
    carries Cache-Control: private, no-store (no Vary header) and its delivery
    number in X-Request-Id. Nothing is cached on doubleshift's side: a paused
    fragment or a revoked key takes effect on the next call. The agent
    User-Agent is used for reporting only and never changes the answer.
servers:
  - url: https://api.shftd2.com
    description: >-
      Delivery host. The console host https://app.shftd2.com answers the same
      path, without redirecting, for integrations set up before the move to
      api.shftd2.com; it is not a second server.
security: []
paths:
  /v1/fragment:
    get:
      summary: Get the card for a page
      description: >-
        Returns a card for the page named by page_url. The server finds the
        account's site for the page host, then the fragment (a routing rule of
        the site, else its default fragment), then an eligible card.
        Authentication: the X-API-Key header only. An api_key query parameter is
        ignored, so a call without the header answers 401 once it reaches
        authentication (HEAD, parameter and page_url checks come first). A
        parameter that would override the resolved fragment is refused (400).
        HEAD answers 405. A POST with a non-empty Origin header whose host or
        port differs from the request's answers 403; other methods answer 405.


        X-ABC-Resolution values, the rate limit and the maximum page_url length
        may change.
      operationId: getFragmentV1
      parameters:
        - name: partner_id
          in: query
          required: true
          description: >-
            Publisher account id. The API key must belong to this account (403
            otherwise). An absent or empty value answers 400.
          schema:
            type: string
            minLength: 1
          example: <your account id>
        - name: page_url
          in: query
          required: true
          description: >-
            The page being served. Its host finds the account's site; its path
            (the query string and the hash are not used) finds the fragment; its
            page key (normalised host + path) seeds the card choice. A value
            without a scheme (host/path) is read as https. An absent or empty
            value answers 400. Once the HEAD and parameter checks pass, a value
            over 2,048 characters, or one that is not an http(s) URL with a
            dotted host, answers 204 before authentication, without any database
            read.
          schema:
            type: string
          example: https://your-domain.example/some-article
        - name: format
          in: query
          required: false
          description: >-
            Response representation. The exact value json returns the card
            payload as JSON; both returns JSON containing the card payload and
            rendered HTML. Omission, html, and every other value return HTML, so
            the request value is intentionally not modeled as a closed enum.
          schema:
            type: string
            default: html
          examples:
            html:
              value: html
            json:
              value: json
            both:
              value: both
        - name: agent_ua
          in: query
          required: false
          description: >-
            Agent User-Agent for reporting only. A trimmed, non-empty X-Agent-UA
            header takes precedence; otherwise this value is trimmed, and an
            empty result falls through to User-Agent.
          schema:
            type: string
          example: GPTBot/1.0
        - name: X-Agent-UA
          in: header
          required: false
          description: >-
            Preferred agent User-Agent reporting channel. A trimmed non-empty
            value takes precedence over agent_ua and User-Agent. Reporting only:
            it does not affect card selection.
          schema:
            type: string
          example: GPTBot/1.0
        - name: X-DS-Record-Id
          in: header
          required: false
          description: >-
            The caller's own id for the page being served (the edge library
            sends its page record id, a UUIDv7). Kept with the delivery,
            lowercased, when it has the shape of a UUID (any version); any other
            value is ignored, never an error. It does not affect authentication,
            selection or the response. Caller-supplied: it is not unique.
          schema:
            type: string
            format: uuid
          example: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
        - name: User-Agent
          in: header
          required: false
          description: >-
            Fallback reporting value when neither X-Agent-UA nor agent_ua yields
            a non-empty value. It does not gate serving, affect card selection,
            or cause a Vary response header.
          schema:
            type: string
          example: abc-edge-adapter
      responses:
        '200':
          description: >-
            A card is returned when the API key is valid for partner_id,
            page_url resolves to a fragment of the account (routing rule or site
            default) that is not paused, selection finds an eligible card, and
            the selected stored payload parses and passes validation. Body per
            format: format=json returns CardPayload; format=both returns
            CardAndHtml; every other format value returns rendered HTML.
            X-ABC-Resolution says how the fragment was resolved.
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            X-ABC-Resolution:
              $ref: '#/components/headers/ResolutionV1'
            Content-Type:
              description: >-
                text/html; charset=UTF-8 for the HTML representation, or
                application/json; charset=UTF-8 for json and both.
              schema:
                type: string
                enum:
                  - text/html; charset=UTF-8
                  - application/json; charset=UTF-8
          content:
            text/html:
              schema:
                type: string
              example: >-
                <article class="abc-card"
                data-ds-delivery="0192f3a4-5b6c-7d8e-9f01-23456789abcd"
                style="font-family:-apple-system,BlinkMacSystemFont,'Segoe
                UI',Roboto,Helvetica,Arial,sans-serif;max-width:680px;margin:16px
                0;padding:14px 16px;border:1px solid
                #d9d9d9;border-radius:8px;background:#fff;color:#1a1a1a;font-size:14px;line-height:1.45"><div
                style="font-size:12px;color:#666;margin-bottom:4px">Brand ·
                Example brand</div><div
                style="font-weight:600;margin-bottom:6px">Example brand
                article</div><p style="margin:0 0 8px">Example summary.</p><pre
                style="font-family:-apple-system,BlinkMacSystemFont,'Segoe
                UI',Roboto,Helvetica,Arial,sans-serif;font-size:12.5px;color:#333;background:#f6f6f6;border-radius:6px;padding:8px
                10px;margin:0 0 8px;white-space:pre-wrap">Example context for an
                agent.</pre><div style="font-size:12px;color:#666">Source: <a
                href="https://your-domain.example/brand/article"
                style="color:#2865aa">Example source article</a> · <a
                href="https://your-domain.example/brand"
                style="color:#2865aa">newsroom</a></div></article>
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/CardPayload'
                  - $ref: '#/components/schemas/CardAndHtml'
              examples:
                json:
                  summary: format=json
                  value:
                    advertiser_id: <advertiser id>
                    brand_surface: Example brand
                    card:
                      translated_title: Example brand article
                      translated_summary: Example summary.
                      llm_friendly_context: Example context for an agent.
                      keywords:
                        - example
                    source:
                      article_title: Example source article
                      article_url: https://your-domain.example/brand/article
                      newsroom_url: https://your-domain.example/brand
                both:
                  summary: format=both
                  value:
                    card:
                      advertiser_id: <advertiser id>
                      brand_surface: Example brand
                      card:
                        translated_title: Example brand article
                        translated_summary: Example summary.
                        llm_friendly_context: Example context for an agent.
                        keywords:
                          - example
                      source:
                        article_title: Example source article
                        article_url: https://your-domain.example/brand/article
                        newsroom_url: https://your-domain.example/brand
                    html: >-
                      <article class="abc-card"
                      data-ds-delivery="0192f3a4-5b6c-7d8e-9f01-23456789abcd"
                      style="font-family:-apple-system,BlinkMacSystemFont,'Segoe
                      UI',Roboto,Helvetica,Arial,sans-serif;max-width:680px;margin:16px
                      0;padding:14px 16px;border:1px solid
                      #d9d9d9;border-radius:8px;background:#fff;color:#1a1a1a;font-size:14px;line-height:1.45"><div
                      style="font-size:12px;color:#666;margin-bottom:4px">Brand
                      · Example brand</div><div
                      style="font-weight:600;margin-bottom:6px">Example brand
                      article</div><p style="margin:0 0 8px">Example
                      summary.</p><pre
                      style="font-family:-apple-system,BlinkMacSystemFont,'Segoe
                      UI',Roboto,Helvetica,Arial,sans-serif;font-size:12.5px;color:#333;background:#f6f6f6;border-radius:6px;padding:8px
                      10px;margin:0 0 8px;white-space:pre-wrap">Example context
                      for an agent.</pre><div
                      style="font-size:12px;color:#666">Source: <a
                      href="https://your-domain.example/brand/article"
                      style="color:#2865aa">Example source article</a> · <a
                      href="https://your-domain.example/brand"
                      style="color:#2865aa">newsroom</a></div></article>
        '204':
          description: >-
            No card, with an empty body and no Content-Type header. Before
            authentication and without any database read: page_url is over 2,048
            characters or is not an http(s) URL with a dotted host (once the
            HEAD and parameter checks pass). After authentication: no site of
            the account matches the host, no rule matched and the site has no
            default fragment, the resolved fragment is paused (no fallback to
            another rule or to the default), no active card is eligible for the
            fragment, or the selected card's stored payload is invalid. At any
            step, an unexpected error caught by the route's fail-safe also
            answers 204. No delivery event is recorded; the reason is logged
            server-side.
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
        '400':
          description: >-
            Returned before authentication when partner_id or page_url is absent
            or empty, or when a parameter that would override the resolved
            fragment is present (with any value, including empty).
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            Content-Type:
              $ref: '#/components/headers/JsonContentType'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestError'
              examples:
                missing:
                  value:
                    ok: false
                    error: partner_id and page_url are required
        '401':
          description: >-
            X-API-Key is absent or empty after trimming, or the key is unknown
            or revoked.
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            Content-Type:
              $ref: '#/components/headers/JsonContentType'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedError'
              examples:
                missing:
                  value:
                    ok: false
                    error: missing API key
                invalid:
                  value:
                    ok: false
                    error: invalid API key
        '403':
          description: >-
            Returned when the API key is valid but its account differs from
            partner_id.
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            Content-Type:
              $ref: '#/components/headers/JsonContentType'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ForbiddenError'
              example:
                ok: false
                error: API key not authorized for this partner
        '405':
          description: >-
            Only GET is served: HEAD and the other methods answer 405 with
            Allow: GET. A POST with a non-empty Origin header whose host or port
            differs from the request's answers 403 instead (the check compares
            hosts, not schemes, and does not detect browsers as such).
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            Content-Type:
              $ref: '#/components/headers/JsonContentType'
            Allow:
              $ref: '#/components/headers/Allow'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MethodNotAllowedError'
              example:
                ok: false
                error: method not allowed
        '429':
          description: >-
            Too many calls for this API key (per-key ceiling, evaluated after
            authentication). The call is logged, but no served event is
            recorded, and last_used_at is not updated.
          headers:
            Cache-Control:
              $ref: '#/components/headers/NoStore'
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            Content-Type:
              $ref: '#/components/headers/JsonContentType'
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitedError'
              example:
                ok: false
                error: rate limited
      security:
        - ApiKeyHeader: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request GET \
              --url 'https://api.shftd2.com/v1/fragment?partner_id=<your account id>&page_url=https%3A%2F%2Fyour-domain.example%2Fsome-article' \
              --header 'X-API-Key: <your key>' \
              --header 'X-Agent-UA: GPTBot/1.0'
components:
  headers:
    NoStore:
      description: >-
        Cache policy set on every response of both operations, whatever the
        status (200, 204, 400, 401, 403, 405, 429). No Vary header is set.
      schema:
        type: string
        const: private, no-store
    RequestId:
      description: >-
        The delivery number of the call: a UUIDv7 (RFC 9562) created by
        doubleshift on receipt, on every response of both operations whatever
        the status (200, 204, 400, 401, 403, 405, 429), before authentication
        too. Quote it to support to point at one precise answer. In the HTML
        representations of a 200 (format html or both) the same value is on the
        card root as the data-ds-delivery attribute. An X-Request-Id sent in the
        request is ignored.
      schema:
        type: string
        format: uuid
      example: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
    ResolutionV1:
      description: >-
        How the served fragment was resolved from page_url: rule when a routing
        rule of the site matched the path, default when the site default
        fragment was used. The set of values is provisional.
      schema:
        type: string
        enum:
          - rule
          - default
    JsonContentType:
      description: JSON response media type set by the route's JSON response helper.
      schema:
        type: string
        const: application/json; charset=UTF-8
    Allow:
      description: The only method the operation accepts.
      schema:
        type: string
        const: GET
    RetryAfter:
      description: >-
        Seconds the caller should wait before retrying. The per-key ceiling (600
        calls per minute per API key) and this value are provisional.
      schema:
        type: string
        const: '60'
  schemas:
    CardPayload:
      type: object
      additionalProperties: false
      required:
        - advertiser_id
        - brand_surface
        - card
        - source
      properties:
        advertiser_id:
          type: string
          pattern: \S
        brand_surface:
          type: string
          pattern: \S
        card:
          type: object
          additionalProperties: false
          required:
            - translated_title
            - translated_summary
            - llm_friendly_context
            - keywords
          properties:
            translated_title:
              type: string
              pattern: \S
            translated_summary:
              type: string
              pattern: \S
            llm_friendly_context:
              type: string
              pattern: \S
            keywords:
              type: array
              minItems: 1
              items:
                type: string
                pattern: \S
        source:
          type: object
          additionalProperties: false
          required:
            - article_title
            - article_url
            - newsroom_url
          properties:
            article_title:
              type: string
              pattern: \S
            article_url:
              type: string
              format: uri
              description: >-
                The code accepts only values parsed as absolute URLs whose
                protocol is http: or https:.
            newsroom_url:
              type: string
              format: uri
              description: >-
                The code accepts only values parsed as absolute URLs whose
                protocol is http: or https:.
    CardAndHtml:
      type: object
      additionalProperties: false
      required:
        - card
        - html
      properties:
        card:
          $ref: '#/components/schemas/CardPayload'
        html:
          type: string
          description: >-
            The same payload rendered by renderCardHtml as a self-contained
            article element.
    BadRequestError:
      type: object
      additionalProperties: false
      required:
        - ok
        - error
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
          description: >-
            Which required parameters are missing, or which refused parameters
            were present (<names> not accepted on /v1/fragment).
    UnauthorizedError:
      type: object
      additionalProperties: false
      required:
        - ok
        - error
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
          enum:
            - missing API key
            - invalid API key
    ForbiddenError:
      type: object
      additionalProperties: false
      required:
        - ok
        - error
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
          enum:
            - API key not authorized for this partner
            - fragment not authorized for this API key
    MethodNotAllowedError:
      type: object
      additionalProperties: false
      required:
        - ok
        - error
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
          const: method not allowed
    RateLimitedError:
      type: object
      additionalProperties: false
      required:
        - ok
        - error
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
          const: rate limited
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        The publisher's API key, kept out of the request URL. Whitespace around
        the value is trimmed.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.