# Add Staff to Shop by OTP

> The affiliate (partner) calls Get OTP for the shop owner's phone, GHN sends an SMS OTP to that phone, and the affiliate then submits that OTP here to add itself as staff to the owner's existing shop. This is step 2 of the affiliate flow (see [Get OTP](https://developer.ghn.vn/en/docs/affiliate/get-otp.md)).

## Endpoint

| Environment | URL |
| --- | --- |
| Production | `https://online-gateway.ghn.vn/shiip/public-api/v2/shop/affiliateCreateWithShop` |
| Staging | `https://dev-online-gateway.ghn.vn/shiip/public-api/v2/shop/affiliateCreateWithShop` |

**Method**: `POST`

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `Content-Type` | Yes | `application/json` |
| `Token` | Yes | Your (the partner's) token, issued by GHN. Your account must be active — [get a token](https://developer.ghn.vn/en/docs/token/get-token.md) |
| `ShopId` | No | Selects **your own** shop context (independent of the `shop_id` in the body); when omitted, your account's default shop is used. The resolved shop must be active and belong to you, otherwise the request is rejected |

## Parameters

- **phone** (String): Phone of the shop owner who received the OTP. Must resolve to a registered GHN account **and** be the owner of `shop_id`; otherwise the request fails with `CLIENT_NOT_BELONG_OF_SHOP`
- **otp** (String): OTP obtained via [Get OTP](https://developer.ghn.vn/en/docs/affiliate/get-otp.md). Single-use: it is invalidated after a successful call. A wrong or expired OTP fails with `OTP_NOT_VALID`
- **shop_id** (Int): The owner's existing shop you will join as staff. Must exist, otherwise the request fails with `Shop khong ton tai` (shop does not exist)
- **language** (String): Language of `code_message_value` in error responses. Default `vi`.<br>`vi`: Vietnamese<br>`en`: English

## Request Example

```bash
curl -X POST https://dev-online-gateway.ghn.vn/shiip/public-api/v2/shop/affiliateCreateWithShop \
  -H "Token: 5c1d8a9e-2f4b-11ed-..." \
  -H "ShopId: 92837" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "0987654321",
    "otp": "123456",
    "shop_id": 200996
  }'
```

## Response

### Success (HTTP 200)

The staff member added is always **your own account** (the account behind the token) — you cannot use this endpoint to add an arbitrary third account. On success, your account is granted the default member permission set on the owner's shop, so you can operate it on their behalf.

```json
{
  "code": 200,
  "message": "Success",
  "data": null
}
```

### Response Fields

| Field | Type | Description |
| --- | --- | --- |
| `code` | Int | HTTP code (`200` on success) |
| `message` | String | Result message (`Success`) |
| `data` | null | Always `null` for this endpoint |

### Error — missing phone, otp, and shop_id (HTTP 400)

_Captured from a real Staging-environment call._

```json
{
  "code": 400,
  "message": "Key: 'myRequest.Phone' Error:Field validation for 'Phone' failed on the 'required' tag\nKey: 'myRequest.OTP' Error:Field validation for 'OTP' failed on the 'required' tag\nKey: 'myRequest.ShopID' Error:Field validation for 'ShopID' failed on the 'required' tag",
  "data": null,
  "code_message": "USER_ERR_COMMON",
  "code_message_value": "Sai thông tin đầu vào. Vui lòng thử lại."
}
```

> **NOTE**
> The add-staff sequence (add you as staff → grant permissions → invalidate OTP) is not atomic and has no rollback: a failure part-way can leave the steps partially applied even though you receive an error.

## Error Codes

| Condition | HTTP | code_message |
| --- | --- | --- |
| Missing `phone`, `otp`, or `shop_id`, or invalid input | 400 | `USER_ERR_COMMON` |
| Wrong or expired `otp` | 400 | `OTP_NOT_VALID` |
| Shop does not exist (`Shop khong ton tai`) | 400 | — |
| `phone` is not the owner of `shop_id` | 400 | `CLIENT_NOT_BELONG_OF_SHOP` |
| You are already staff of the shop | 400 | `CLIENT_HAVE_EXISTED` |
| Internal error | 500 | `SERVER_ERROR_COMMON` |
