# Quickstart

Verify a key, resolve records, preflight, and execute a supported write.

Canonical HTML: https://developer.shelfcycle.com/guides/quickstart

Verify the key, resolve a record, inspect exact context, optionally preflight, and execute one supported write back to ShelfCycle.

This guide assumes you have a ShelfCycle API key. The key is scoped to one tenant and issued to one acting user. Store it locally and read it from the environment at runtime.

> **Guardrail**: Never paste the key into a prompt, chat message, log line, or source control. Give local tooling the environment variable name, not the secret value.

## 1. Configure the environment

Set the key and base URL in a local `.env` file.

```bash
SHELFCYCLE_API_KEY=sk_...
SHELFCYCLE_API_BASE_URL=https://app.shelfcycle.com/api/v1
```

For local ShelfCycle development, use:

```text
http://localhost:3000/api/v1
```

## 2. Verify the key

Call `GET /me` before any workflow step. It returns the route major, contract version, org, key preview, scopes, acting user, documentation links, and effective capabilities after live RBAC.

```bash
curl "$SHELFCYCLE_API_BASE_URL/me" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Email notes automation"
```

The response should include `apiContractVersion: "v1.21"` and the tenant, actor, scopes, and capabilities your workflow expects.

```json
{
  "data": {
    "apiVersion": "v1",
    "apiContractVersion": "v1.21",
    "org": { "id": "org-id", "name": "Northstar Chemical" },
    "key": {
      "id": "api-key-id",
      "name": "Email notes automation",
      "purpose": "Workflow for creating notes from source emails",
      "preview": "sk_...abcd",
      "scopes": ["company-reference:read", "search:read", "notes:read", "notes:write", "customers:read"],
      "accessMode": "restricted",
      "storedGrants": ["company-reference:read", "search:read", "notes:read", "notes:write", "customers:read"],
      "effectiveScopes": ["company-reference:read", "search:read", "notes:read", "notes:write", "customers:read"],
      "retiredScopes": [],
      "expiresAt": null
    },
    "actor": { "type": "user", "id": "user-id", "displayName": "Jordan Buyer" },
    "documentation": {
      "openapiJsonUrl": "https://app.shelfcycle.com/api/v1/openapi.json",
      "llmsTxtUrl": "https://app.shelfcycle.com/llms.txt"
    },
    "effectiveCapabilities": {
      "search": { "viewableTypes": ["customer", "contact", "product", "order", "purchase_order", "location"] },
      "notes": { "create": true, "view": true, "update": true, "viewableSubjectTypes": ["customer", "product"] },
      "contacts": {
        "create": false,
        "view": false,
        "update": false,
        "customer": { "create": false, "view": false, "update": false },
        "supplier": { "create": false, "view": false, "update": false }
      },
      "customers": {
        "create": false,
        "view": true,
        "update": false,
        "locations": { "view": true, "create": false, "update": false },
        "addresses": { "view": true, "create": false, "update": false },
        "documents": { "view": true, "create": false }
      },
      "suppliers": {
        "create": false,
        "view": false,
        "update": false,
        "shipFromLocations": { "view": false, "create": false, "update": false },
        "documents": { "view": false, "create": false }
      },
      "companyReference": { "activeDirectory": true, "customers": true, "suppliers": false },
      "products": { "view": true, "create": false, "update": false, "documents": { "view": true, "create": false } },
      "productFamilies": { "view": true, "create": false, "update": false },
      "costBookEntries": { "view": false, "create": false },
      "orders": { "view": false, "changedRecordDiscovery": false, "commercialEconomics": false },
      "purchaseOrders": { "view": false, "changedRecordDiscovery": false, "commercialEconomics": false },
      "reports": { "available": [] },
      "writeReadiness": {
        "notes": { "create": true, "update": true },
        "contacts": {
          "create": false,
          "update": false,
          "customer": { "create": false, "update": false },
          "supplier": { "create": false, "update": false }
        },
        "customers": {
          "create": false,
          "update": false,
          "locations": { "create": false, "update": false },
          "addresses": { "create": false, "update": false },
          "documents": { "create": false }
        },
        "suppliers": {
          "create": false,
          "update": false,
          "shipFromLocations": { "create": false, "update": false },
          "documents": { "create": false }
        },
        "products": { "create": false, "update": false, "documents": { "create": false } },
        "productFamilies": { "create": false, "update": false },
        "costBookEntries": { "create": false }
      }
    }
  }
}
```

> **Information**: Use the key preview, org name, contract version, and effective capabilities as the wiring check. Never print the full key.

## 3. Cache active company hints when needed

For inbox triage, intake review, or duplicate checks, use the active reference directory to page safe customer and supplier matching hints into a local cache.

```bash
curl "$SHELFCYCLE_API_BASE_URL/reference/customers?limit=250" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Email notes automation"
```

Reference rows are active-only hints. They are not complete profiles, not archived exports, and not write authorization. If `meta.hasMore` is true, continue with `meta.nextCursor`.

## 4. Resolve the target record

Use `GET /search` for ambiguous cross-resource resolution. It takes a required `q`, an optional comma-separated `types` list, and an optional `limit` from 1 to 20.

```bash
curl "$SHELFCYCLE_API_BASE_URL/search?q=Northstar&types=customer,contact,purchase_order&limit=10" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Email notes automation"
```

```json
{
  "data": [
    {
      "type": "contact",
      "id": "contact-id",
      "displayName": "Jordan Buyer",
      "subtitle": "Northstar Chemical · jordan@example.com · President",
      "url": "https://app.shelfcycle.com/org-northstar/customers/customer-id/contacts",
      "archived": false,
      "matchedFields": [
        { "field": "email", "label": "Email", "value": "jordan@example.com" },
        { "field": "parent.name", "label": "Customer", "value": "Northstar Chemical" }
      ],
      "parent": { "type": "customer", "id": "customer-id", "displayName": "Northstar Chemical" }
    }
  ],
  "meta": {
    "requestId": "request-id",
    "limit": 10,
    "types": ["customer", "contact", "purchase_order"],
    "rankingIsContractual": false,
    "truncated": false,
    "durationMs": 24
  }
}
```

Use the matched fields and selected `{type,id}` to support your workflow's target-selection policy. If `meta.truncated` is true, narrow the query first.

If a request needs repair, v1.21 validation errors can include a machine-readable next step. Use fields such as `repairCategory`, `suggestedAction`, `validValues`, `acceptedFilters`, `requiredCombination`, `documentationPath`, and `example` to change the request once before retrying.

```json
{
  "error": {
    "type": "validation_error",
    "code": "validation_failed",
    "message": "Request validation failed.",
    "param": "type",
    "suggestedAction": "replace_param",
    "repairCategory": "unknown_param",
    "validValues": { "types": ["customer", "supplier", "contact", "product", "order", "purchase_order", "location"] },
    "documentationPath": "/api/v1/openapi.json",
    "doNotRetrySameRequest": true,
    "requestId": "request-id"
  }
}
```

## 5. Hydrate exact context

After selecting a resource, fetch the exact detail, child list, attached document list, or order document endpoint when the workflow needs more context.

```bash
curl "$SHELFCYCLE_API_BASE_URL/customers/customer-id" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

For typed lookup before detail, use finder endpoints such as `GET /customers?q=Northstar`, `GET /products?code=ACETONE-PAIL`, `GET /orders?orderNo=SO-10042`, or `GET /purchase-orders?orderNo=PO-4812`. Finders require narrowing predicates and are not sync/export lists.

Sales-order and purchase-order finders also support paired `updatedAtFrom` and `updatedAtTo` windows for parent-row changed-record discovery. Use those windows to discover recently changed order records, then fetch exact documents by id.

After selecting a customer or supplier, use parent-scoped child endpoints when the workflow needs exact locations or addresses: `GET /customers/{id}/locations`, `GET /customers/{id}/locations/{locationId}/addresses`, or `GET /suppliers/{id}/ship-from-locations`.

For product intake, resolve setup references before product writes: `GET /product-families`, `GET /product-families/{id}`, and `GET /reference/product-packaging`.

For customer, supplier, or product source files, use the parent-scoped attached document routes after selecting the exact record: `GET /customers/{id}/documents`, `GET /suppliers/{id}/documents`, or `GET /products/{id}/documents`. Product document responses can also include the product SDS pointer when one is available.

For report workflows, call `GET /reports` before running a report. Use `allowedForThisKey` and `effectiveCapabilities.reports.available` to choose report keys the current key and actor can run.

For product cost workflows, use `GET /cost-book-entries` to read current Cost Book truth or exact product and supplier history. Use `GET /products/{id}/cost-context?supplierId={supplierId}` to compare the current entry with prior Cost Book and purchasing evidence. See the [Cost Book guide](/guides/cost-book) before creating an entry.

## 6. Preflight the note write

For supported POST and PATCH writes, add `dryRun=true` to the same endpoint before execution when your workflow wants a readiness check. The dry run does not write. It uses the same scope, live permission, and rate-limit family as execution.

```bash
curl "$SHELFCYCLE_API_BASE_URL/notes?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gmail-thread-123:customer-id:create-note" \
  -d '{
    "primarySubject": { "type": "customer", "id": "customer-id" },
    "linkedRecords": [
      { "type": "contact", "id": "contact-id" },
      { "type": "product", "id": "product-id" }
    ],
    "noteType": "EMAIL",
    "title": "Inbound product thread",
    "body": "Jordan asked for follow-up on acetone availability.",
    "happenedAt": "2026-05-28T16:00:00.000Z"
  }'
```

```json
{
  "data": {
    "type": "write_readiness",
    "operation": "notes.create",
    "status": "ready",
    "wouldWrite": false,
    "checks": [
      { "code": "scope_authorized", "status": "passed", "message": "The key can create notes." },
      { "code": "target_available", "status": "passed", "message": "The subject is visible." },
      { "code": "idempotency_available", "status": "passed", "message": "The idempotency key is available." }
    ],
    "duplicateCandidates": [],
    "submit": { "method": "POST", "path": "/api/v1/notes", "query": {} },
    "verification": {
      "available": true,
      "method": "GET",
      "path": null,
      "pathTemplate": "/api/v1/notes/{id}",
      "requiresScopes": ["notes:read"],
      "fallback": "detail_get"
    },
    "requestId": "request-id"
  }
}
```

`status: "blocked"` can still return HTTP 200. Inspect `checks` and `duplicateCandidates` before deciding whether your policy allows execution.

## 7. Execute the note write

Remove `dryRun=true` when your workflow's policy allows execution. Send a stable `Idempotency-Key` built from source facts, not a random value.

```bash
curl "$SHELFCYCLE_API_BASE_URL/notes" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gmail-thread-123:customer-id:create-note" \
  -d '{
    "primarySubject": { "type": "customer", "id": "customer-id" },
    "linkedRecords": [
      { "type": "contact", "id": "contact-id" },
      { "type": "product", "id": "product-id" }
    ],
    "noteType": "EMAIL",
    "title": "Inbound product thread",
    "body": "Jordan asked for follow-up on acetone availability.",
    "happenedAt": "2026-05-28T16:00:00.000Z"
  }'
```

```json
{
  "data": {
    "id": "note-id",
    "type": "note",
    "url": "https://app.shelfcycle.com/org-northstar/notes/note-id",
    "createdAt": "2026-05-29T14:35:00.000Z",
    "updatedAt": "2026-05-29T14:35:00.000Z",
    "createdBy": { "type": "user", "id": "user-id", "displayName": "Jordan Buyer" },
    "idempotencyStatus": "created"
  }
}
```

Use the returned `url` or the readiness response's verification guidance for readback. If detail verification is not available for the key, treat the execute response as the readback.

> **Warning**: Do not generate a random idempotency key for each retry. The same source event must reuse the same key. The same key with a different body returns `409 idempotency_key_reused`.

## 8. Build with the v1.21 capabilities

v1.21 supports active customer/supplier reference directory paging, search, narrowed finders, governed report rollups with flexible period comparisons, Cost Book reads and verified direct or constructed cost updates, exact product and supplier cost context, product setup references, safe detail reads, subject-scoped note reads, parent-scoped contact and child-location reads, parent-scoped attached document reads and link creates, optional write-readiness checks, note/contact writes, bounded customer/supplier profile writes, parent-scoped customer location/address and supplier ship-from writes, bounded product and product-family writes, read-only sales/purchase order documents, scope-gated profit, margin, and cost details, and repair-aware validation responses.

Choose the narrowest endpoint for the job, verify effective capabilities through `GET /me`, and use the live OpenAPI reference for exact request and response shapes.
