# Attached documents

List and attach parent-scoped document links for selected records.

Canonical HTML: https://developer.shelfcycle.com/guides/attached-documents

List and attach customer, supplier, and product document links after selecting one visible parent record.

Attached document routes are for workflow context around a specific product, customer, or supplier. They are not order documents, broad file export, binary upload, or a document-management API.

## Endpoint map

| Endpoint | Scope | Use it for |
| --- | --- | --- |
| `GET /products/{id}/documents` | `products:read` or `search:read` | List document links attached to one visible product. Product responses also include the product SDS pointer when present. |
| `POST /products/{id}/documents` | `products:write` | Attach one HTTPS document link to an active product. |
| `GET /customers/{id}/documents` | `customers:read` or `search:read` | List document links attached to one visible customer. |
| `POST /customers/{id}/documents` | `customers:write` | Attach one HTTPS document link to an active customer. |
| `GET /suppliers/{id}/documents` | `suppliers:read` or `search:read` | List document links attached to one visible supplier. |
| `POST /suppliers/{id}/documents` | `suppliers:write` | Attach one HTTPS document link to an active supplier. |

Document lists accept `limit` and `cursor`. Cursors are scoped to the selected parent. Read routes can return documents on archived parents, but document creates on archived parents are blocked.

## Read attached documents

Fetch attached documents only after search, a finder, or a detail response has selected the exact parent id.

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/documents" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

```json
{
  "data": {
    "documents": [
      {
        "type": "document",
        "id": "document-id",
        "fileName": "ACE Spec.pdf",
        "createdAt": "2026-07-02T18:00:00.000Z",
        "urlClass": "shelfcycle",
        "access": {
          "mode": "signed",
          "url": "https://...",
          "expiresAt": "2026-07-02T18:15:00.000Z"
        }
      }
    ],
    "truncated": false,
    "sds": {
      "fileName": "ACE SDS.pdf",
      "access": { "mode": "signed", "url": "https://...", "expiresAt": "2026-07-02T18:15:00.000Z" }
    }
  }
}
```

ShelfCycle-hosted files return short-lived signed read URLs. External HTTPS links return `access.mode: "raw"`. If an old ShelfCycle file points outside the authenticated org prefix, the row can return `access.mode: "unavailable"` without a URL.

## Attach a document link

Document create accepts only `fileName` and `url`. It stores a link to an existing HTTPS file; it does not upload binary data.

```bash
curl "$SHELFCYCLE_API_BASE_URL/customers/customer-id/documents?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: intake:customer-id:w9-link" \
  -d '{
    "fileName": "Northstar W9.pdf",
    "url": "https://example.com/documents/northstar-w9.pdf"
  }'
```

```json
{
  "data": {
    "type": "write_readiness",
    "operation": "customer_documents.create",
    "status": "ready",
    "wouldWrite": false,
    "checks": [
      { "code": "scope_authorized", "status": "passed" },
      { "code": "target_available", "status": "passed" },
      { "code": "idempotency_available", "status": "passed" },
      { "code": "duplicate_check_clear", "status": "passed" }
    ],
    "duplicateCandidates": [],
    "verification": {
      "available": true,
      "path": "/api/v1/customers/customer-id/documents",
      "fallback": "detail_get"
    },
    "requestId": "request-id"
  }
}
```

Execute the same request without `dryRun=true` when your integration policy allows it. Reusing the same route, idempotency key, and body returns the existing document row with `idempotencyStatus: "replayed"`.

## URL rules

Document URLs must use `https` and cannot contain embedded credentials. External HTTPS links are accepted. ShelfCycle bucket links are accepted only when the bucket key belongs to the authenticated org; signed URL query strings are normalized before duplicate checks.

Exact duplicate URLs on the same parent are blocked with `duplicate_document`. Requests with fields other than `fileName` and `url` are blocked with `unsupported_document_field`.

## Sensitive files

Attached files may contain tax, quality, commercial, or regulatory context. Use the narrowest parent route, keep signed URLs out of logs, and avoid echoing request bodies that contain sensitive source links.

> **Guardrail**: Attached document routes do not upload files, delete files, update orders or purchase orders, create broad file exports, mutate SDS pointers, or change accounting, inventory, pricing, cost, tax, or ledger records.
