True Fit Recommendations API

Size recommendations and general fit guidance for retailer products.

OpenAPI Specification

true-fit-recommendations-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: True Fit Partner Recommendations API
  version: '2026-08-05'
  description: "These APIs can be used to manage True Fit profiles on behalf of your own users and to\nrequest size recommendations for retailer products.\n\n# Getting Started\n\nHere are the general concepts - please see further below for the specific endpoint\ndocumentation.\n\n## Base URL\n\nEvery path in this reference is relative to the base URL, which already includes the\n`/api` prefix:\n\n```\nhttps://partner.truefitcorp.com/api\n```\n\n## Authentication\n\nAll `/partner/{partnerId}/**` endpoints use HTTP Basic authentication. The **username is\nempty** and the **password is your partner API key**:\n\n```\nAuthorization: Basic <base64(\":\" + apiKey)>\n```\n\nNote the leading colon. `base64(apiKey)` without it has no separator and is rejected\nwith `401`.\n\nAn unknown or disabled `partnerId` returns `404` **before** credentials are checked, so a\n`404` on your first call points at the `partnerId` or the environment, not at the key.\n\n`GET /id-sync` is authenticated differently - it is signed with HMAC-SHA256 rather than\nBasic auth. See that endpoint for the signing scheme.\n\n## Identifying a user\n\nPartner endpoints operate on a True Fit user, not on your identifier directly.\n`GET /id-sync` creates that user and maps one of your identifiers to it. Call it once per\nuser, store the `tfPartnerUserId` it returns, and drive every later request from that.\n\nExcept for `POST /general-guidance/bulk`, every endpoint needs a user identifier as a\nquery parameter:\n\n* `tfPartnerUserId` - the identifier returned by `GET /id-sync`. Preferred: it is a\n  durable handle on one specific True Fit user and its data.\n* `partnerUserId` - your own identifier for the user, as passed to `GET /id-sync`.\n\nSupply at least one; omitting both returns `400`. If you send both, `tfPartnerUserId`\ntakes precedence. An identifier that was never synced returns `401 Unknown partner user`,\nwhich means \"call `GET /id-sync` first\" rather than \"bad credentials\".\n\n## Profiles\n\nA profile is the entity that holds information about the person being shopped for -\ndepartment, alias, measurements, and closet items. When a profile is created it is\nassigned a `profileId`, which is then used in the path of the measurements, closet, and\nrecommendation endpoints.\n\nA user may have several profiles, one per person they shop for. List them with\n`GET /profile` and address them explicitly by `profileId`; there is no implicit \"current\"\nprofile in this API.\n\nProfiles you create record your `partnerId` as their `originStore`.\n\n## Getting a size recommendation\n\nA profile needs measurements, closet items, or both before it can be sized: a profile\ncarrying neither has nothing to size against and returns `incompleteProfile`. Add\nmeasurements with `PUT /profile/{profileId}/measurements` and closet items with\n`POST /profile/{profileId}/closet`, then call\n`POST /profile/{profileId}/recommendation/bulk`.\n\nProfile writes trigger asynchronous body estimation, so a recommendation requested\nimmediately after one may return `userEstimationInProgress`. Retry after a short delay.\n\nWhen no profile exists - anonymous visitors, or a product page before registration -\nuse `POST /general-guidance/bulk` instead. It returns how a product ran for shoppers\ngenerally, needs no user identifier, and is the natural fallback when a recommendation\nreturns `incompleteProfile` or `noProfile`.\n\n## Example Flow - Product Page\n\n1. Once per user, call `GET /id-sync` and persist the returned `tfPartnerUserId`.\n2. Call `GET /profile` to list the user's profiles.\n3. If the list is empty, give the user an opportunity to register, then create a profile\n   with `POST /profile`.\n4. Add what you know about the user: `PUT /profile/{profileId}/measurements`, and\n   `POST /profile/{profileId}/closet` for garments they already own.\n5. Call `POST /profile/{profileId}/recommendation/bulk` with the products on the page and\n   present `recommendedSize` for each successful result.\n6. For products that return `success: false`, or when no profile exists, fall back to\n   `POST /general-guidance/bulk`.\n\n## Bulk requests\n\nBoth bulk endpoints take a JSON **array** of 1 to 100 items and return an array of the\n**same length and in the same order**, so results can be matched to requests by position.\nEach result also echoes the `retailerDomain`, `productId`, and `locale` you sent.\n\nFailures are per item: each result carries `success: true` with its data, or\n`success: false` with an `error` string. One unrecognised product does not fail the batch,\nand the HTTP status is still a success status.\n\n`retailerDomain` accepts a hostname or a full URL, from which only the hostname is used -\nprotocol, port, path, and query string are discarded, and matching is case-insensitive.\n`www.` is not stripped, so confirm with True Fit which exact hostnames are registered for\nyour retailers.\n\n## Request bodies\n\nEnum values are documented in their canonical lowercase form but matched\ncase-insensitively, so `Womens` and `womens` are equally valid - the stored and returned\nvalue is always lowercase. Fields not listed in a schema are ignored rather than rejected.\n\n## General API Behavior\n\n* Success statuses vary by endpoint. Empty-bodied writes return `200` in some places and\n  `204` in others, and both bulk endpoints return `201`. Treat any 2xx as success rather\n  than matching an exact code.\n* A `400 - Bad Request` is returned when a body or query string fails validation, or when\n  no user identifier is supplied.\n* A `401 - Unauthorized` is returned for a missing or wrong API key, and for a user\n  identifier that has no True Fit mapping.\n* A `404 - Not Found` is returned for an unknown or disabled `partnerId`, and for a\n  `profileId` that does not exist or does not belong to the identified user. Another\n  user's profile returns `404` rather than `403` so that the API cannot be used to detect\n  whether a profile exists.\n* It is possible for any endpoint to return a `500 - Internal Server Error` if an\n  unexpected error is encountered.\n* Validation failures carry a field-keyed error map and, unlike other errors, no\n  `statusCode` field:\n\n```json\n{ \"message\": \"Validation failed\", \"error\": { \"0.retailerDomain\": \"Invalid input: expected string, received undefined\" } }\n```\n\nArray indices appear as the first path segment, so `0.retailerDomain` is the\n`retailerDomain` field of the first item in a bulk request. All other errors use the\nstandard shape:\n\n```json\n{ \"statusCode\": 404, \"message\": \"Profile not found\", \"error\": \"Not Found\" }\n```\n\n## Rate limits\n\nNo rate limits are enforced today. Expected request volume should be agreed during\nonboarding.\n"
  contact:
    name: True Fit
    email: tech@truefit.com
  license:
    name: Proprietary - (c) True Fit Corporation
servers:
- url: https://partner.truefitcorp.com/api
  description: Partner API
security:
- partnerApiKey: []
tags:
- name: Recommendations
  description: Size recommendations and general fit guidance for retailer products.
paths:
  /partner/{partnerId}/profile/{profileId}/recommendation/bulk:
    parameters:
    - $ref: '#/components/parameters/partnerId'
    - $ref: '#/components/parameters/profileId'
    - $ref: '#/components/parameters/partnerUserId'
    - $ref: '#/components/parameters/tfPartnerUserId'
    post:
      operationId: getBulkRecommendation
      tags:
      - Recommendations
      summary: Recommend sizes for up to 100 products
      description: 'Returns a recommended size per product for the given profile.


        The response array has the **same length and order as the request array**, so results

        can be matched positionally. Each result also echoes the `retailerDomain`, `productId`,

        and `locale` you sent. Failures are per item: a product that cannot be recommended

        returns `success: false` with an `error`, and does not fail the other items.


        This is a `POST` because the product list is sent in the body; it has no side effects

        and returns `201`.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkRequest'
            examples:
              mixed:
                value:
                - retailerDomain: https://NYDJ.com/collections/jeans
                  productId: '7093242789933'
                  locale: en_US
                - retailerDomain: landsend.com
                  productId: '532582'
                  locale: en_US
                - retailerDomain: unknown.example.org
                  productId: abc
                  locale: en_US
      responses:
        '201':
          description: One result per requested item, in request order.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BulkRecommendationResult'
              examples:
                mixed:
                  value:
                  - retailerDomain: https://NYDJ.com/collections/jeans
                    productId: '7093242789933'
                    locale: en_US
                    success: true
                    recommendedSize: '10'
                  - retailerDomain: landsend.com
                    productId: '532582'
                    locale: en_US
                    success: false
                    error: noSizesFitTheUser
                  - retailerDomain: unknown.example.org
                    productId: abc
                    locale: en_US
                    success: false
                    error: Unsupported retailer domain
        '400':
          $ref: '#/components/responses/ValidationFailed'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ProfileNotFound'
  /partner/{partnerId}/general-guidance/bulk:
    parameters:
    - $ref: '#/components/parameters/partnerId'
    post:
      operationId: getBulkGeneralGuidance
      tags:
      - Recommendations
      summary: Get aggregate fit guidance for up to 100 products
      description: 'Returns crowd-sourced fit guidance for products - how the product ran for other

        shoppers - without reference to any individual user.


        Unlike every other endpoint, this one needs **no user identifier**: the partner API key

        alone authorizes it, and no `partnerUserId` or `tfPartnerUserId` query parameter is

        required. Use it for anonymous visitors and for product pages where no profile exists.


        The response array has the same length and order as the request array, and failures are

        per item.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkRequest'
            examples:
              mixed:
                value:
                - retailerDomain: nydj.com
                  productId: '7093242789933'
                  locale: en_US
                - retailerDomain: unknown.example.org
                  productId: abc
                  locale: en_US
      responses:
        '201':
          description: One result per requested item, in request order.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BulkGeneralGuidanceResult'
              examples:
                mixed:
                  value:
                  - retailerDomain: nydj.com
                    productId: '7093242789933'
                    locale: en_US
                    success: true
                    recommendationSummary: Runs small
                    recommendationMessage: Most shoppers found this ran small - consider sizing up.
                  - retailerDomain: unknown.example.org
                    productId: abc
                    locale: en_US
                    success: false
                    error: Unsupported retailer domain
        '400':
          $ref: '#/components/responses/ValidationFailed'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/UnsupportedPartner'
components:
  schemas:
    Error:
      type: object
      description: Standard error body.
      required:
      - statusCode
      - message
      properties:
        statusCode:
          type: integer
          example: 404
        message:
          type: string
          example: Profile not found
        error:
          type: string
          example: Not Found
    BulkGeneralGuidanceResult:
      description: 'One result, echoing the request item''s fields. Match results to requests by position.

        '
      oneOf:
      - title: Guidance available
        allOf:
        - $ref: '#/components/schemas/BulkRequestItem'
        - type: object
          required:
          - success
          - recommendationSummary
          - recommendationMessage
          properties:
            success:
              type: boolean
              const: true
            recommendationSummary:
              type: string
              description: Short headline describing how the product runs, localized to `locale`.
              example: Runs small
            recommendationMessage:
              type: string
              description: Longer explanation suitable for display, localized to `locale`.
              example: Most shoppers found this ran small - consider sizing up.
      - title: No guidance
        allOf:
        - $ref: '#/components/schemas/BulkRequestItem'
        - type: object
          required:
          - success
          - error
          properties:
            success:
              type: boolean
              const: false
            error:
              type: string
              description: "Why no guidance was returned.\n  * `Invalid retailerDomain` - the value could not be parsed as a hostname.\n  * `Unsupported retailer domain` - the hostname is not mapped to a retailer.\n  * `No general guidance returned for product` - the product is unknown, or\n    too few shoppers have rated it to produce guidance.\n"
              examples:
              - Unsupported retailer domain
              - No general guidance returned for product
    BulkRecommendationResult:
      description: 'One result, echoing the request item''s fields. Match results to requests by position.

        '
      oneOf:
      - title: Recommended
        allOf:
        - $ref: '#/components/schemas/BulkRequestItem'
        - type: object
          required:
          - success
          - recommendedSize
          properties:
            success:
              type: boolean
              const: true
            recommendedSize:
              type: string
              description: The recommended size label, as the retailer prints it.
              example: '10'
      - title: Not recommended
        allOf:
        - $ref: '#/components/schemas/BulkRequestItem'
        - type: object
          required:
          - success
          - error
          properties:
            success:
              type: boolean
              const: false
            error:
              type: string
              description: "Why no size was returned.\n\nDomain resolution:\n  * `Invalid retailerDomain` - the value could not be parsed as a hostname.\n  * `Unsupported retailer domain` - the hostname is not mapped to a retailer.\n  * `No recommendation returned for product` - the product is unknown to True Fit.\n\nFit outcomes:\n  * `incompleteProfile` - the profile lacks the measurements needed for this garment.\n  * `noProfile` - the profile no longer exists.\n  * `userEstimationInProgress` - body estimation is still running after a\n    profile, measurement, or closet write. Retry after a short delay.\n  * `noSizesFitTheUser` - no stocked size fits.\n  * `invalidGender` - the product's department does not apply to this profile.\n  * `unrecommendableDepartment` - the product is in a department True Fit does\n    not size, such as accessories.\n  * `productMissingData` - the product lacks the garment data needed to size it.\n  * `recommendationDisabled` - recommendations are turned off for that retailer.\n  * `error` - an unexpected failure. Safe to retry.\n"
              examples:
              - Unsupported retailer domain
              - incompleteProfile
              - userEstimationInProgress
    ValidationError:
      type: object
      description: Schema validation failure. Note this body carries no `statusCode`.
      required:
      - message
      - error
      properties:
        message:
          type: string
          example: Validation failed
        error:
          type: object
          description: Failing field path to error message.
          additionalProperties:
            type: string
    BulkRequestItem:
      type: object
      required:
      - retailerDomain
      - productId
      properties:
        retailerDomain:
          type: string
          minLength: 1
          description: 'The retailer the product belongs to, as a hostname. A full URL is also accepted -

            only the hostname is used, and protocol, port, path, and query string are discarded.

            `www.` is **not** stripped, so the hostname must match the mapping True Fit holds for

            that retailer. Matching is case-insensitive. Ask True Fit to register any retailer

            domain you intend to send.

            '
          examples:
          - nydj.com
          - https://NYDJ.com:443/collections/jeans?utm=x
        productId:
          type: string
          minLength: 1
          description: The retailer's own product identifier.
          example: '7093242789933'
        locale:
          type: string
          description: 'Locale for the product data and any returned copy, as `language_COUNTRY`. Defaults

            to the retailer''s primary locale when omitted.

            '
          example: en_US
    BulkRequest:
      type: array
      description: Between 1 and 100 items. Duplicate products are permitted and each gets its own result.
      minItems: 1
      maxItems: 100
      items:
        $ref: '#/components/schemas/BulkRequestItem'
  responses:
    Unauthorized:
      description: 'Missing or invalid Basic credentials, or a user identifier that does not match a known

        partner user.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missingCredentials:
              value:
                statusCode: 401
                message: Missing Basic auth credentials
                error: Unauthorized
            invalidCredentials:
              value:
                statusCode: 401
                message: Invalid credentials
                error: Unauthorized
            unknownUser:
              summary: The identifier has no True Fit mapping - call GET /id-sync first
              value:
                statusCode: 401
                message: Unknown partner user
                error: Unauthorized
    ProfileNotFound:
      description: 'The `profileId` does not exist or does not belong to the identified user. Also returned

        when the `partnerId` is unknown or disabled.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            profileNotFound:
              value:
                statusCode: 404
                message: Profile not found
                error: Not Found
    UnsupportedPartner:
      description: 'The `partnerId` is unknown or disabled. Returned before credentials are checked, so an

        invalid API key against an unknown partner is a `404`, not a `401`.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unsupportedPartner:
              value:
                statusCode: 404
                message: Unsupported partner
                error: Not Found
    ValidationFailed:
      description: 'The request body or query string failed schema validation. `error` maps each failing

        field path to its message; array indices appear as the first path segment.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
          examples:
            missingField:
              value:
                message: Validation failed
                error:
                  0.retailerDomain: 'Invalid input: expected string, received undefined'
            badEnum:
              value:
                message: Validation failed
                error:
                  category: 'Invalid option: expected one of "belts"|"bottoms"|"bras"'
  parameters:
    partnerUserId:
      name: partnerUserId
      in: query
      required: false
      description: 'Your own identifier for the user, as passed to `GET /id-sync`. Send either this or

        `tfPartnerUserId`; omitting both returns `400`. If both are sent, `tfPartnerUserId` wins.

        '
      schema:
        type: string
      example: acme-user-42817
    tfPartnerUserId:
      name: tfPartnerUserId
      in: query
      required: false
      description: 'True Fit''s identifier for the user, returned by `GET /id-sync`. Send either this or

        `partnerUserId`; omitting both returns `400`. Takes precedence when both are sent.

        '
      schema:
        type: string
      example: p-3f9c1a80-5d2e-4a17-9b64-8e0c2d7f1a55
    partnerId:
      name: partnerId
      in: path
      required: true
      description: Your partner identifier, issued during onboarding.
      schema:
        type: string
      example: acme
    profileId:
      name: profileId
      in: path
      required: true
      description: 'A profile belonging to the identified user, from `GET /profile`. Profiles belonging to

        another user return `404` rather than `403`, so the endpoint cannot be used to probe

        for the existence of other users'' profiles.

        '
      schema:
        type: string
        format: uuid
      example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
  securitySchemes:
    partnerApiKey:
      type: http
      scheme: basic
      description: 'Empty username, partner API key as the password:

        `Authorization: Basic <base64(":" + apiKey)>`.

        '