feat: implement uber store api v1.0.0 delivery endpoints and validations

This commit is contained in:
MOHAN 2026-03-29 18:18:38 +05:30
parent 54565d9b6d
commit d4042fb656
10 changed files with 773 additions and 4 deletions

View File

@ -0,0 +1,40 @@
# 04 Store API 1.0.0 Audit
Source checked: "Uber Eats Marketplace Store API (1.0.0)" shared by you.
## Implemented Now (Dedicated Wrapper Namespace)
- Get Stores:
- `GET /api/v1/uber/delivery-store/stores`
- upstream: `/v1/delivery/stores`
- Get Store Details:
- `GET /api/v1/uber/delivery-store/stores/{storeId}`
- upstream: `/v1/delivery/store/{store_id}`
- Update Store Information:
- `POST /api/v1/uber/delivery-store/stores/{storeId}`
- upstream: `/v1/delivery/store/{store_id}`
- supports contact/location/pickup_instructions only
- Retrieve Store Status:
- `GET /api/v1/uber/delivery-store/stores/{storeId}/status`
- upstream: `/v1/delivery/store/{store_id}/status`
- Set Store Status:
- `POST /api/v1/uber/delivery-store/stores/{storeId}/status`
- upstream: `/v1/delivery/store/{store_id}/update-store-status`
- Update Prep Time:
- `POST /api/v1/uber/delivery-store/stores/{storeId}/prep-time`
- upstream: `/v1/delivery/store/{store_id}/update-store-prep-time`
- Update Fulfillment Configuration (BYOC):
- `POST /api/v1/uber/delivery-store/stores/{storeId}/fulfillment-configuration`
- upstream: `/v1/delivery/store/{store_id}/update-fulfillment-configuration`
## Validation Added
- status: `ONLINE|OFFLINE` enum
- prep time max: 10,800 seconds
- store update restricted to contact/location/pickup fields
## Pending
- Optional stricter schemas for nested location/contact formats per country-specific variants
- Dedicated request-builder helpers for BYOC fulfillment override fields

View File

@ -15,3 +15,13 @@ Typed routes available:
- `POST /api/v1/uber/stores/{storeId}/status`
- `GET /api/v1/uber/stores/{storeId}/holiday-hours`
- `POST /api/v1/uber/stores/{storeId}/holiday-hours`
Store API 1.0.0 coverage (delivery namespace):
- `GET /api/v1/uber/delivery-store/stores`
- `GET /api/v1/uber/delivery-store/stores/{storeId}`
- `POST /api/v1/uber/delivery-store/stores/{storeId}`
- `GET /api/v1/uber/delivery-store/stores/{storeId}/status`
- `POST /api/v1/uber/delivery-store/stores/{storeId}/status`
- `POST /api/v1/uber/delivery-store/stores/{storeId}/prep-time`
- `POST /api/v1/uber/delivery-store/stores/{storeId}/fulfillment-configuration`

View File

@ -693,6 +693,153 @@
}
}
},
"/api/v1/uber/delivery-store/stores": {
"get": {
"summary": "Store API 1.0.0 - Get stores (/v1/delivery/stores)",
"tags": [
"Uber Delivery Store v1"
],
"responses": {
"200": {
"description": "Stores retrieved"
}
}
}
},
"/api/v1/uber/delivery-store/stores/{storeId}": {
"get": {
"summary": "Store API 1.0.0 - Get store details",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Store details retrieved"
}
}
},
"post": {
"summary": "Store API 1.0.0 - Update store information",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Store information updated"
}
}
}
},
"/api/v1/uber/delivery-store/stores/{storeId}/status": {
"get": {
"summary": "Store API 1.0.0 - Retrieve store status",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Store status retrieved"
}
}
},
"post": {
"summary": "Store API 1.0.0 - Set store status",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Store status updated"
}
}
}
},
"/api/v1/uber/delivery-store/stores/{storeId}/prep-time": {
"post": {
"summary": "Store API 1.0.0 - Update prep time",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Prep time updated"
}
}
}
},
"/api/v1/uber/delivery-store/stores/{storeId}/fulfillment-configuration": {
"post": {
"summary": "Store API 1.0.0 - Update BYOC fulfillment configuration",
"tags": [
"Uber Delivery Store v1"
],
"parameters": [
{
"in": "path",
"name": "storeId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Fulfillment configuration updated"
}
}
}
},
"/api/v1/uber/reporting/fetch": {
"post": {
"summary": "Fetch Uber reporting CSV with retries and header-based parsing",

View File

@ -287,6 +287,235 @@
}
}
},
{
"name": "Delivery Store API - Get Stores",
"request": {
"method": "GET",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores?page_size=50",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores"
],
"query": [
{
"key": "page_size",
"value": "50"
}
]
}
}
},
{
"name": "Delivery Store API - Get Store Details",
"request": {
"method": "GET",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}?expand=holiday_hours,internal_contact_emails",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}"
],
"query": [
{
"key": "expand",
"value": "holiday_hours,internal_contact_emails"
}
]
}
}
},
{
"name": "Delivery Store API - Update Store",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"contact\": {\n \"email\": \"abc@restaurant.com\",\n \"name\": \"Jane Doe\",\n \"phone_number\": \"+1-800-999-9999\"\n },\n \"pickup_instructions\": \"Enter from the north side\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}"
]
}
}
},
{
"name": "Delivery Store API - Get Status",
"request": {
"method": "GET",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}/status",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}",
"status"
]
}
}
},
{
"name": "Delivery Store API - Set Status",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"status\": \"OFFLINE\",\n \"is_offline_until\": \"2026-04-01T10:00:00.000Z\",\n \"reason\": \"Scheduled maintenance\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}/status",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}",
"status"
]
}
}
},
{
"name": "Delivery Store API - Update Prep Time",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"default_prep_time\": 500\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}/prep-time",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}",
"prep-time"
]
}
}
},
{
"name": "Delivery Store API - Update Fulfillment Config",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"override_config\": {\n \"custom_min_etd_minutes\": 30\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/delivery-store/stores/{{storeId}}/fulfillment-configuration",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"delivery-store",
"stores",
"{{storeId}}",
"fulfillment-configuration"
]
}
}
},
{
"name": "Get Store By ID",
"request": {

View File

@ -22,6 +22,15 @@ module.exports = {
inventory: "/v1/eats/stores/{storeId}/inventory",
posData: "/v1/eats/stores/{storeId}/pos_data"
},
deliveryStore: {
list: "/v1/delivery/stores",
getById: "/v1/delivery/store/{storeId}",
update: "/v1/delivery/store/{storeId}",
getStatus: "/v1/delivery/store/{storeId}/status",
setStatus: "/v1/delivery/store/{storeId}/update-store-status",
updatePrepTime: "/v1/delivery/store/{storeId}/update-store-prep-time",
updateFulfillmentConfig: "/v1/delivery/store/{storeId}/update-fulfillment-configuration"
},
webhooks: {
events: "/v1/eats/stores/{storeId}/event_feed"
}

View File

@ -130,7 +130,8 @@ async function getAuthCapabilities(req, res) {
AUTH_SCOPES.STORE_STATUS_WRITE,
AUTH_SCOPES.ORDER,
AUTH_SCOPES.STORE_ORDERS_READ,
AUTH_SCOPES.REPORT
AUTH_SCOPES.REPORT,
AUTH_SCOPES.BYOC_FULFILLMENT_CONFIG
],
authorization_code: [AUTH_SCOPES.POS_PROVISIONING]
},

View File

@ -11,11 +11,13 @@ const AUTH_GRANT_TYPES = {
const AUTH_SCOPES = {
STORE: "eats.store",
STORE_WRITE: "eats.store",
STORE_STATUS_WRITE: "eats.store.status.write",
ORDER: "eats.order",
STORE_ORDERS_READ: "eats.store.orders.read",
REPORT: "eats.report",
POS_PROVISIONING: "eats.pos_provisioning"
POS_PROVISIONING: "eats.pos_provisioning",
BYOC_FULFILLMENT_CONFIG: "eats.byoc.fulfillment.config"
};
const uberAuthClient = axios.create({

View File

@ -338,6 +338,112 @@ async function deletePosData(req, res) {
return res.json({ success: true, data });
}
async function deliveryListStores(req, res) {
const schema = z.object({
next_page_token: z.string().optional(),
page_size: z.coerce.number().int().min(1).max(200).optional()
});
const query = schema.parse(req.query || {});
const data = await proxyService.deliveryListStores({ query });
return res.json({ success: true, data });
}
async function deliveryGetStoreDetails(req, res) {
const schema = z.object({
expand: z.string().optional()
});
const query = schema.parse(req.query || {});
const data = await proxyService.deliveryGetStoreDetails({
storeId: req.params.storeId,
query
});
return res.json({ success: true, data });
}
async function deliveryUpdateStore(req, res) {
const contactSchema = z
.object({
email: z.string().optional(),
name: z.string().optional(),
phone_number: z.string().optional()
})
.optional();
const locationSchema = z
.object({
latitude: z.number().optional(),
longitude: z.number().optional(),
street_address_line_one: z.string().optional(),
street_address_line_two: z.string().optional(),
city: z.string().optional(),
country: z.string().optional(),
postal_code: z.union([z.string(), z.number()]).optional(),
unit_number: z.union([z.string(), z.number()]).optional(),
business_name: z.string().optional()
})
.optional();
const schema = z.object({
contact: contactSchema,
location: locationSchema,
pickup_instructions: z.string().optional()
});
const payload = schema
.refine((value) => Object.keys(value).length > 0, {
message: "At least one of contact/location/pickup_instructions is required"
})
.parse(req.body || {});
const data = await proxyService.deliveryUpdateStore({
storeId: req.params.storeId,
payload
});
return res.json({ success: true, data });
}
async function deliveryGetStoreStatus(req, res) {
const data = await proxyService.deliveryGetStoreStatus({
storeId: req.params.storeId
});
return res.json({ success: true, data });
}
async function deliverySetStoreStatus(req, res) {
const schema = z.object({
status: z.enum(["ONLINE", "OFFLINE"]),
is_offline_until: z.string().optional(),
reason: z.string().optional()
});
const payload = schema.parse(req.body || {});
const data = await proxyService.deliverySetStoreStatus({
storeId: req.params.storeId,
payload
});
return res.json({ success: true, data });
}
async function deliveryUpdatePrepTime(req, res) {
const schema = z.object({
default_prep_time: z.coerce.number().int().min(0).max(10800)
});
const payload = schema.parse(req.body || {});
const data = await proxyService.deliveryUpdatePrepTime({
storeId: req.params.storeId,
payload
});
return res.json({ success: true, data });
}
async function deliveryUpdateFulfillmentConfig(req, res) {
const schema = z.object({
override_config: z.record(z.string(), z.any())
});
const payload = schema.parse(req.body || {});
const data = await proxyService.deliveryUpdateFulfillmentConfig({
storeId: req.params.storeId,
payload
});
return res.json({ success: true, data });
}
module.exports = {
genericProxy,
upsertMenu,
@ -360,5 +466,12 @@ module.exports = {
createPosData,
getPosData,
patchPosData,
deletePosData
deletePosData,
deliveryListStores,
deliveryGetStoreDetails,
deliveryUpdateStore,
deliveryGetStoreStatus,
deliverySetStoreStatus,
deliveryUpdatePrepTime,
deliveryUpdateFulfillmentConfig
};

View File

@ -388,6 +388,88 @@ async function deletePosData({ storeId }) {
});
}
async function deliveryListStores({ query }) {
return callUberApi({
method: "GET",
uberPath: uberEndpoints.deliveryStore.list,
query,
wrapperRoute: "/api/v1/uber/delivery-store/stores",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliveryGetStoreDetails({ storeId, query }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.getById, { storeId });
return callUberApi({
method: "GET",
uberPath,
query,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliveryUpdateStore({ storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.update, { storeId });
return callUberApi({
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId",
authMode: "app",
scopes: AUTH_SCOPES.STORE_WRITE
});
}
async function deliveryGetStoreStatus({ storeId }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.getStatus, { storeId });
return callUberApi({
method: "GET",
uberPath,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId/status",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function deliverySetStoreStatus({ storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.setStatus, { storeId });
return callUberApi({
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId/status",
authMode: "app",
scopes: AUTH_SCOPES.STORE_STATUS_WRITE
});
}
async function deliveryUpdatePrepTime({ storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.updatePrepTime, { storeId });
return callUberApi({
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId/prep-time",
authMode: "app",
scopes: AUTH_SCOPES.STORE_WRITE
});
}
async function deliveryUpdateFulfillmentConfig({ storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.deliveryStore.updateFulfillmentConfig, { storeId });
return callUberApi({
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/delivery-store/stores/:storeId/fulfillment-configuration",
authMode: "app",
scopes: AUTH_SCOPES.BYOC_FULFILLMENT_CONFIG
});
}
module.exports = {
genericProxy,
menuUpsert,
@ -410,5 +492,12 @@ module.exports = {
createPosData,
getPosData,
patchPosData,
deletePosData
deletePosData,
deliveryListStores,
deliveryGetStoreDetails,
deliveryUpdateStore,
deliveryGetStoreStatus,
deliverySetStoreStatus,
deliveryUpdatePrepTime,
deliveryUpdateFulfillmentConfig
};

View File

@ -352,4 +352,133 @@ router.get("/uber/stores/:storeId/pos-data", asyncHandler(controller.getPosData)
router.patch("/uber/stores/:storeId/pos-data", asyncHandler(controller.patchPosData));
router.delete("/uber/stores/:storeId/pos-data", asyncHandler(controller.deletePosData));
/**
* @openapi
* /api/v1/uber/delivery-store/stores:
* get:
* summary: Store API 1.0.0 - Get stores (/v1/delivery/stores)
* tags:
* - Uber Delivery Store v1
* responses:
* 200:
* description: Stores retrieved
*/
router.get("/uber/delivery-store/stores", asyncHandler(controller.deliveryListStores));
/**
* @openapi
* /api/v1/uber/delivery-store/stores/{storeId}:
* get:
* summary: Store API 1.0.0 - Get store details
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Store details retrieved
* post:
* summary: Store API 1.0.0 - Update store information
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Store information updated
*/
router.get("/uber/delivery-store/stores/:storeId", asyncHandler(controller.deliveryGetStoreDetails));
router.post("/uber/delivery-store/stores/:storeId", asyncHandler(controller.deliveryUpdateStore));
/**
* @openapi
* /api/v1/uber/delivery-store/stores/{storeId}/status:
* get:
* summary: Store API 1.0.0 - Retrieve store status
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Store status retrieved
* post:
* summary: Store API 1.0.0 - Set store status
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Store status updated
*/
router.get(
"/uber/delivery-store/stores/:storeId/status",
asyncHandler(controller.deliveryGetStoreStatus)
);
router.post(
"/uber/delivery-store/stores/:storeId/status",
asyncHandler(controller.deliverySetStoreStatus)
);
/**
* @openapi
* /api/v1/uber/delivery-store/stores/{storeId}/prep-time:
* post:
* summary: Store API 1.0.0 - Update prep time
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Prep time updated
*/
router.post(
"/uber/delivery-store/stores/:storeId/prep-time",
asyncHandler(controller.deliveryUpdatePrepTime)
);
/**
* @openapi
* /api/v1/uber/delivery-store/stores/{storeId}/fulfillment-configuration:
* post:
* summary: Store API 1.0.0 - Update BYOC fulfillment configuration
* tags:
* - Uber Delivery Store v1
* parameters:
* - in: path
* name: storeId
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Fulfillment configuration updated
*/
router.post(
"/uber/delivery-store/stores/:storeId/fulfillment-configuration",
asyncHandler(controller.deliveryUpdateFulfillmentConfig)
);
module.exports = router;