|
| 1 | +# As-Associate API |
| 2 | + |
| 3 | +**Source:** https://docs.commercetools.com/api/projects/associate-roles |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +The **As-Associate API** is a special API path prefix in commercetools that enforces Business Unit–scoped permission checks on behalf of a buyer (associate). Any operation that a buyer performs within the context of a Business Unit — reading orders, creating carts, placing orders, managing quotes, approving flows — **must** go through this API path. Calling the standard API paths bypasses the BU permission layer and is only appropriate for merchant/admin contexts. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## The API Chain Pattern |
| 14 | + |
| 15 | +The TypeScript SDK exposes the As-Associate API through a fluent builder chain: |
| 16 | + |
| 17 | +```typescript |
| 18 | +apiRoot |
| 19 | + .asAssociate() |
| 20 | + .withAssociateIdValue({ associateId: "customer-id-here" }) |
| 21 | + .inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "bu-key-here" }) |
| 22 | + .<resource>() |
| 23 | + .<action>() |
| 24 | + .execute(); |
| 25 | +``` |
| 26 | + |
| 27 | +### Breaking Down the Chain |
| 28 | + |
| 29 | +| Segment | Purpose | |
| 30 | +|---|---| |
| 31 | +| `.asAssociate()` | Switches the SDK into the as-associate routing context. | |
| 32 | +| `.withAssociateIdValue({ associateId })` | Identifies which customer (associate) is performing the action. The platform validates that this customer is an associate of the target BU. | |
| 33 | +| `.inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey })` | Scopes the operation to a specific Business Unit. | |
| 34 | +| `.<resource>()` | The resource type (e.g. `.carts()`, `.orders()`, `.quoteRequests()`). | |
| 35 | + |
| 36 | +### Example: Create a Cart as an Associate |
| 37 | + |
| 38 | +```typescript |
| 39 | +const cart = await apiRoot |
| 40 | + .asAssociate() |
| 41 | + .withAssociateIdValue({ associateId: customerId }) |
| 42 | + .inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "acme-uk" }) |
| 43 | + .carts() |
| 44 | + .post({ |
| 45 | + body: { |
| 46 | + currency: "GBP", |
| 47 | + country: "GB", |
| 48 | + businessUnit: { key: "acme-uk", typeId: "business-unit" }, |
| 49 | + store: { key: "acme-uk-store", typeId: "store" }, |
| 50 | + }, |
| 51 | + }) |
| 52 | + .execute(); |
| 53 | +``` |
| 54 | + |
| 55 | +### Example: Place an Order as an Associate |
| 56 | + |
| 57 | +```typescript |
| 58 | +const order = await apiRoot |
| 59 | + .asAssociate() |
| 60 | + .withAssociateIdValue({ associateId: customerId }) |
| 61 | + .inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "acme-uk" }) |
| 62 | + .orders() |
| 63 | + .post({ |
| 64 | + body: { |
| 65 | + cart: { id: cart.body.id, typeId: "cart" }, |
| 66 | + version: cart.body.version, |
| 67 | + }, |
| 68 | + }) |
| 69 | + .execute(); |
| 70 | +``` |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +## Why the As-Associate Path Is Required |
| 75 | + |
| 76 | +### Platform Permission Enforcement |
| 77 | + |
| 78 | +commercetools enforces B2B permissions **at the API layer** using the as-associate path. When a request arrives via this path, the platform: |
| 79 | + |
| 80 | +1. Verifies the `associateId` is an active associate of the named Business Unit. |
| 81 | +2. Checks whether that associate holds the required `associateRole` permission for the requested operation (e.g. `createMyCarts`, `createOrders`, `viewOrders`, `updateApprovalFlows`). |
| 82 | +3. Rejects the request with an `AssociateMissingPermissionError` (an HTTP 403-class error) if the permission is absent. |
| 83 | + |
| 84 | +Calling `/carts`, `/orders`, etc. directly (without `asAssociate`) uses project-level OAuth scopes only and does not apply associate-role permission logic. This means: |
| 85 | +- A buyer could see or modify another BU's data if project scopes are broad. |
| 86 | +- Approval rule enforcement is bypassed. |
| 87 | +- Audit trail (who did what, in which BU) is lost. |
| 88 | + |
| 89 | +**Always use the as-associate path for buyer-facing operations.** |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## Operations Covered |
| 94 | + |
| 95 | +The as-associate path supports the following resource types (non-exhaustive): |
| 96 | + |
| 97 | +| Resource | Typical Operations | |
| 98 | +|---|---| |
| 99 | +| `carts` | Create, read, update, replicate carts scoped to the BU | |
| 100 | +| `orders` | Place orders from cart, read orders | |
| 101 | +| `orders/quotes` | Create an order from an accepted Quote | |
| 102 | +| `quote-requests` | Create and manage QuoteRequests | |
| 103 | +| `quotes` | Read, accept, decline Quotes | |
| 104 | +| `order-approval-flows` | Approve or reject pending orders | |
| 105 | +| `business-units` | Read BU details, update associates (where permitted) | |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## REST Equivalent |
| 110 | + |
| 111 | +The SDK chain maps to the following REST path structure: |
| 112 | + |
| 113 | +``` |
| 114 | +POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/carts |
| 115 | +GET /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders |
| 116 | +POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders |
| 117 | +POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/quote-requests |
| 118 | +POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders/quotes |
| 119 | +``` |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Common Mistakes |
| 124 | + |
| 125 | +| Mistake | Consequence | |
| 126 | +|---|---| |
| 127 | +| Using `/carts` instead of `asAssociate().*.carts()` | No associate permission check; any authenticated customer could access any BU's carts if scopes allow. | |
| 128 | +| Omitting `businessUnitKey` from the cart body | Cart may not be associated with the BU, breaking approval rule evaluation. | |
| 129 | +| Using a merchant/admin token for buyer flows | Bypasses all associate-role enforcement; creates audit and security gaps. | |
| 130 | +| Calling approval actions via the standard orders path | Not supported — approval transitions are only available through the as-associate path. | |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## OAuth Scope |
| 135 | + |
| 136 | +The OAuth token used in as-associate requests should carry: |
| 137 | +- `manage_my_orders:{projectKey}` or `manage_orders:{projectKey}` (depending on whether you use customer tokens or merchant tokens acting on behalf of a customer). |
| 138 | +- For customer-token flows (recommended for buyer-facing apps): use the `customer` OAuth flow so the token is bound to the specific customer, and the platform cross-checks `associateId` against the token's subject. |
0 commit comments