# Flat Rate Repairs

This cookbook walks you through ordering flat rate repair services via the PartsSource API. Flat rate repairs have unique constraints compared to standard part orders—most importantly, **each repair must be its own line item with a quantity of one**.

***

## Overview

Flat rate repairs are a service option where a vendor repairs your equipment at a fixed price. Unlike standard part purchases where you might order a quantity of five in a single line item, flat rate repairs require vendor-specific information about each individual unit being repaired—at minimum a **repair reason** and **equipment serial number**, and often additional fields like asset ID or work order depending on the configuration.

Because each repair needs its own set of required fields, the API enforces a strict quantity limit:

{% hint style="warning" %}
**One Repair Per Line Item**

Flat rate repair line items must have a `quantity` of **1**. Each unit being repaired requires its own `requiredFields` (repair reason, serial number, and any other vendor-required fields), and these fields are submitted per line item—not per unit.

If you need three repairs for the same product, you must submit **three separate line items**, each with `quantity: 1` and its own required field values.
{% endhint %}

### Why This Constraint Exists

Consider a hospital sending three ultrasound probes for repair. Each probe has a different serial number and may be sent in for a different reason. The vendor needs to track each repair individually for:

* **Traceability** — matching the serial number to the specific unit returned after repair
* **Diagnostics** — understanding the reported issue for each unit before it arrives
* **Compliance** — maintaining per-device repair records for regulatory purposes

A single line item with `quantity: 3` would only allow one set of required fields, making it impossible to capture per-unit details. The one-per-line-item model ensures every repair is fully documented.

***

## Prerequisites

* Valid OAuth 2.0 access token (see [Authentication](https://docs.partssource.com/documentation/authentication/overview))
* Familiarity with the standard [Search to Order](https://docs.partssource.com/documentation/cookbooks/search-to-order) workflow
* Understanding of [Required Fields](https://docs.partssource.com/documentation/domain-concepts/required-fields)

***

## Step 1: Get Catalog Detail for a Flat Rate Repair

After searching the catalog and finding a product that offers flat rate repair (see [Search to Order](https://docs.partssource.com/documentation/cookbooks/search-to-order) Steps 1–2), call `/catalog/detail` to retrieve pricing options.

A flat rate repair option is identified by its `condition` value containing `"Flat Rate Repair"` (e.g., `"Aftermarket Flat Rate Repair"`). Note that the `purchaseChoice` will be `"Outright"` — it is the **condition**, not the purchase choice, that distinguishes flat rate repairs from standard part purchases.

### Request

```bash
curl -X POST "https://api.partssource.com/customer/api/catalog/detail" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "CAT-789",
    "facilityId": 2001,
    "requesterId": 12345
  }'
```

### Response

```json
{
  "success": true,
  "data": {
    "product": {
      "id": "CAT-789",
      "partNumber": "PROBE-US-001",
      "title": "Ultrasound Transducer Repair Service",
      "description": "Flat rate repair for GE ultrasound transducers",
      "manufacturer": "GE Healthcare"
    },
    "options": [
      {
        "priceOptionId": "opt_900000000001",
        "price": 450.00,
        "condition": "Aftermarket Flat Rate Repair",
        "warranty": "6 Month",
        "purchaseChoice": "Outright",
        "unitOfMeasurement": "(Each)",
        "estimatedShipDate": "",
        "estimatedShipDateMessage": "Ship date provided after order confirmation",
        "customFields": [
          {
            "fieldId": "77777777-7777-7777-7777-777777777777",
            "prompt": "Repair Reason",
            "description": "Repair Reason",
            "isRequired": true,
            "formatRegex": "",
            "errorMessage": "",
            "placeholder": ""
          },
          {
            "fieldId": "44444444-4444-4444-4444-444444444444",
            "prompt": "Equipment Serial #",
            "description": "Equipment Serial Number Default Field",
            "isRequired": true,
            "formatRegex": "",
            "errorMessage": "",
            "placeholder": ""
          }
        ]
      }
    ]
  }
}
```

{% hint style="info" %}
**Response trimmed for clarity.** The actual response may include additional `customFields` (such as Asset ID, Work Order, Cost Center, Model, etc.) depending on the vendor. Always check `isRequired` on each field — the specific set of required fields varies by vendor and product.
{% endhint %}

### Key Indicators of a Flat Rate Repair

| Field            | Value                                 | Significance                                                                                |
| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------- |
| `condition`      | `"Aftermarket Flat Rate Repair"`      | Identifies this as a flat rate repair — look for "Flat Rate Repair" in the condition string |
| `purchaseChoice` | `"Outright"`                          | The purchase choice is Outright, same as standard purchases — **not** "Flat Rate Repair"    |
| `customFields`   | Non-empty array with required entries | Required fields must be captured for each unit                                              |

### Required vs. Optional Custom Fields

Flat rate repair options typically include a mix of required and optional custom fields. You **must** provide values for all fields where `isRequired` is `true`. Repair Reason and Equipment Serial # are always required for flat rate repairs. Vendors may also require additional fields such as Asset ID, Work Order, or others — always check `isRequired` on each field in the `customFields` response.

{% hint style="info" %}
**Tip: Inspect customFields Before Building Your Order**

The specific required fields can vary by vendor and product. Always read the `customFields` array from the catalog detail response rather than hardcoding field IDs. The fields shown above are typical, but vendors may include additional required or optional fields. Note that field IDs are GUIDs — do not attempt to guess them.
{% endhint %}

***

## Step 2: Build the Order — One Line Item Per Repair

This is where flat rate repairs diverge from standard orders. Instead of setting `quantity` to the number of units you need repaired, you create a **separate line item for each repair**, each with its own `requiredFields`.

### Ordering a Single Repair

For a single repair, the order request looks similar to a standard order — but with the required repair reason and serial number populated. The `shippingMethod` field is required — see [Shipping Methods](https://docs.partssource.com/documentation/domain-concepts/shipping-methods) for valid values.

```bash
curl -X POST "https://api.partssource.com/customer/api/orders" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "requesterId": 12345,
    "userId": 12345,
    "facilityId": 2001,
    "shippingAddressId": 3001,
    "billingAddressId": 4001,
    "shippingMethod": "GROUND",
    "quoteItems": [
      {
        "priceOptionId": "opt_900000000001",
        "productId": "CAT-789",
        "quantity": 1,
        "price": 450.00,
        "requiredFields": [
          {
            "fieldId": "77777777-7777-7777-7777-777777777777",
            "value": "Intermittent signal loss on channels 1-3"
          },
          {
            "fieldId": "44444444-4444-4444-4444-444444444444",
            "value": "GE-US-20145"
          }
        ]
      }
    ],
    "poNumber": "PO-2025-042",
    "notes": "Fragile — pack with foam inserts"
  }'
```

### Ordering Multiple Repairs on the Same Order

When you need to repair multiple units, create a separate entry in the `quoteItems` array for each one. Every line item uses the **same** `priceOptionId` and `productId` but has **different** `requiredFields` values:

```bash
curl -X POST "https://api.partssource.com/customer/api/orders" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 661f9500-f30c-52e5-b827-557766551111" \
  -d '{
    "requesterId": 12345,
    "userId": 12345,
    "facilityId": 2001,
    "shippingAddressId": 3001,
    "billingAddressId": 4001,
    "shippingMethod": "GROUND",
    "quoteItems": [
      {
        "priceOptionId": "opt_900000000001",
        "productId": "CAT-789",
        "quantity": 1,
        "price": 450.00,
        "requiredFields": [
          {
            "fieldId": "77777777-7777-7777-7777-777777777777",
            "value": "Intermittent signal loss on channels 1-3"
          },
          {
            "fieldId": "44444444-4444-4444-4444-444444444444",
            "value": "GE-US-20145"
          }
        ]
      },
      {
        "priceOptionId": "opt_900000000001",
        "productId": "CAT-789",
        "quantity": 1,
        "price": 450.00,
        "requiredFields": [
          {
            "fieldId": "77777777-7777-7777-7777-777777777777",
            "value": "No power — unit does not turn on"
          },
          {
            "fieldId": "44444444-4444-4444-4444-444444444444",
            "value": "GE-US-20198"
          }
        ]
      },
      {
        "priceOptionId": "opt_900000000001",
        "productId": "CAT-789",
        "quantity": 1,
        "price": 450.00,
        "requiredFields": [
          {
            "fieldId": "77777777-7777-7777-7777-777777777777",
            "value": "Cracked housing near connector — still functional"
          },
          {
            "fieldId": "44444444-4444-4444-4444-444444444444",
            "value": "GE-US-20203"
          }
        ]
      }
    ],
    "poNumber": "PO-2025-042",
    "notes": "Three probes shipping together in one box"
  }'
```

{% hint style="info" %}
**Same priceOptionId, Different requiredFields**

Notice that all three line items share the same `priceOptionId` and `productId`. The `priceOptionId` quote applies per unit, so reusing it across line items is expected. What must be unique is the `requiredFields` on each line item — each repair needs its own serial number, repair reason, and values for any other required fields.
{% endhint %}

***

## What NOT to Do

### Do Not Set Quantity Greater Than One

Setting `quantity: 2` (or higher) on a flat rate repair line item will result in a validation error because the API cannot associate a single set of required fields with multiple units:

```json
{
  "quoteItems": [
    {
      "priceOptionId": "opt_900000000001",
      "productId": "CAT-789",
      "quantity": 2,
      "price": 450.00,
      "requiredFields": [
        {
          "fieldId": "77777777-7777-7777-7777-777777777777",
          "value": "Signal loss"
        },
        {
          "fieldId": "44444444-4444-4444-4444-444444444444",
          "value": "GE-US-20145"
        }
      ]
    }
  ]
}
```

This will fail with a `422 Unprocessable Entity`:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Flat rate repair items must have a quantity of 1",
    "details": [
      {
        "field": "quoteItems[0].quantity",
        "message": "Quantity must be 1 for flat rate repair items. Submit separate line items for each unit requiring repair."
      }
    ]
  }
}
```

### Do Not Omit Required Fields

Submitting a flat rate repair line item without the mandatory `requiredFields` will also fail validation:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Required field validation failed",
    "details": [
      {
        "field": "quoteItems[0].requiredFields.77777777-7777-7777-7777-777777777777",
        "message": "Repair Reason is required"
      },
      {
        "field": "quoteItems[0].requiredFields.44444444-4444-4444-4444-444444444444",
        "message": "Equipment Serial # is required"
      }
    ]
  }
}
```

***

## Complete Flow Example

{% stepper %}
{% step %}
**Search the catalog for a repair service**

Use `/catalog/search` to find products that offer flat rate repair options.
{% endstep %}

{% step %}
**Get catalog detail and identify the flat rate repair option**

Call `/catalog/detail` with the `productId`. Look for an option where `condition` contains `"Flat Rate Repair"` (e.g., `"Aftermarket Flat Rate Repair"`). Note its `priceOptionId` and `customFields`.
{% endstep %}

{% step %}
**Collect required fields for each unit**

For every unit being sent for repair, gather the repair reason and serial number, plus any other vendor-required fields (such as asset ID or work order). Each unit will become its own line item.
{% endstep %}

{% step %}
**Build the quoteItems array — one entry per repair**

Create a separate object in the `quoteItems` array for each unit. Every entry should have `quantity: 1`, the same `priceOptionId`, and its own `requiredFields`.
{% endstep %}

{% step %}
**Submit the order with an Idempotency-Key**

POST to `/orders` with a unique `Idempotency-Key` header. Verify the response returns `success: true` and an `orderNumber`.
{% endstep %}
{% endstepper %}

***

## Implementation Tips

### Programmatically Building Multi-Repair Orders

When your integration needs to create orders for multiple repairs, build the `quoteItems` array dynamically. Read the `customFields` from the catalog detail response to get the correct field IDs:

```typescript
interface RepairUnit {
  repairReason: string;
  serialNumber: string;
}

function buildFlatRateRepairItems(
  priceOptionId: string,
  productId: string,
  price: number,
  units: RepairUnit[],
  repairReasonFieldId: string,
  serialNumberFieldId: string
): OrderItem[] {
  return units.map(unit => ({
    priceOptionId,
    productId,
    quantity: 1,
    price,
    requiredFields: [
      { fieldId: repairReasonFieldId, value: unit.repairReason },
      { fieldId: serialNumberFieldId, value: unit.serialNumber }
    ]
  }));
}

// Usage — field IDs come from the catalog detail customFields response
const repairs: RepairUnit[] = [
  { repairReason: "Signal loss on channels 1-3", serialNumber: "GE-US-20145" },
  { repairReason: "No power", serialNumber: "GE-US-20198" },
  { repairReason: "Cracked housing", serialNumber: "GE-US-20203" }
];

const quoteItems = buildFlatRateRepairItems(
  "opt_900000000001",
  "CAT-789",
  450.00,
  repairs,
  "77777777-7777-7777-7777-777777777777", // repairReasonFieldId from customFields
  "44444444-4444-4444-4444-444444444444"  // serialNumberFieldId from customFields
);
```

### Validating Before Submission

Validate each repair unit against the `customFields` definitions from catalog detail before submitting the order. This avoids round-trip validation failures:

```typescript
function validateRepairUnits(
  units: RepairUnit[],
  customFields: CustomField[]
): string[] {
  const errors: string[] = [];
  const requiredFields = customFields.filter(f => f.isRequired);

  units.forEach((unit, index) => {
    for (const field of requiredFields) {
      // Map the field prompt to the corresponding unit property
      let value = "";
      switch (field.prompt) {
        case "Repair Reason": value = unit.repairReason; break;
        case "Equipment Serial #": value = unit.serialNumber; break;
      }

      if (!value.trim()) {
        errors.push(`Repair ${index + 1}: ${field.prompt} is required`);
      } else if (field.formatRegex) {
        const regex = new RegExp(field.formatRegex);
        if (!regex.test(value)) {
          errors.push(
            `Repair ${index + 1}: ${field.errorMessage || `Invalid format for ${field.prompt}`}`
          );
        }
      }
    }
  });

  // Check for duplicate serial numbers
  const serials = units.map(u => u.serialNumber);
  const duplicates = serials.filter((s, i) => serials.indexOf(s) !== i);
  if (duplicates.length > 0) {
    errors.push(
      `Duplicate serial numbers found: ${[...new Set(duplicates)].join(", ")}`
    );
  }

  return errors;
}
```

{% hint style="info" %}
**Watch for Duplicate Serial Numbers**

Since each repair must target a distinct unit, submitting two line items with the same serial number is almost always a mistake. Consider adding a duplicate check in your integration logic as shown above.
{% endhint %}

***

## Error Handling

| Error                      | Cause                                                                                    | Solution                                                                       |
| -------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `422 Unprocessable Entity` | `quantity` greater than 1 on a flat rate repair item                                     | Set `quantity: 1` and create separate line items per unit                      |
| `422 Unprocessable Entity` | Missing a required field (repair reason, serial number, or other vendor-required fields) | Provide all fields where `isRequired` is `true` in the `customFields` response |
| `422 Unprocessable Entity` | Field value fails format validation                                                      | Check value against the `formatRegex` from catalog detail                      |
| `400 Bad Request`          | Unknown `fieldId` in `requiredFields`                                                    | Use exact `fieldId` GUIDs from the catalog detail response                     |
| `404 Not Found`            | Expired `priceOptionId`                                                                  | Fetch fresh catalog detail — quotes expire after 30 days                       |

***

## Related Concepts

* [Required Fields](https://docs.partssource.com/documentation/domain-concepts/required-fields) — Full reference for vendor-defined custom fields
* [Price Option ID](https://docs.partssource.com/documentation/domain-concepts/price-option-id) — How quotes work and expiration behavior
* [Search to Order](https://docs.partssource.com/documentation/cookbooks/search-to-order) — The standard ordering workflow that this cookbook builds on
* [Idempotency](https://docs.partssource.com/documentation/core-concepts/idempotency) — Preventing duplicate orders with idempotency keys
