# ResellerIO Mobile API Guide

This is the compressed mobile integration guide for the current `/api/v1` surface.

## 1. Authentication

Register:

- `POST /api/v1/auth/register`

Log in:

- `POST /api/v1/auth/login`

Log out:

- `DELETE /api/v1/auth/logout`

Resend confirmation email:

- `POST /api/v1/auth/resend_confirmation`

Both register and login return a bearer token. Send it on every protected call:

```text
Authorization: Bearer <token>
```

Recommended mobile flow:

1. Register or log in.
2. Persist the bearer token securely.
3. Fetch `GET /api/v1/me`.
4. If `user.confirmed_at` is `null`, prompt the user to confirm their email. Offer a "Resend" action that calls `POST /api/v1/auth/resend_confirmation`.
5. Fetch `GET /api/v1/me/usage` if you display quotas.

Browser-based clients:

- preflight `OPTIONS` is supported for `/api/v1/*`
- only allowlisted origins receive CORS headers

Identifier note:

- the mobile surface uses ULID-based public identifiers now
- route params keep names like `:id`, `:page_id`, and `:image_id`, but their values are ULID strings
- public `id` and public `*_id` values are ULID strings while internal integer ids remain server-only

## 2. User and Marketplace Settings

Current-user endpoints:

- `GET /api/v1/me`
- `PATCH /api/v1/me`
- `GET /api/v1/me/usage`

Important fields returned by `GET /api/v1/me`:

- `selected_marketplaces`
- `plan`, `plan_status`, `plan_period`
- `plan_expires_at`, `trial_ends_at`
- `addon_credits`
- `supported_marketplaces`

## 3. Product Flow

Confirmed-email gate:

- `POST /api/v1/products`
- `POST /api/v1/products/:id/prepare_uploads`
- `POST /api/v1/products/:id/finalize_uploads`
- `POST /api/v1/products/:id/reprocess`
- `POST /api/v1/products/:id/generate_lifestyle_images`
- `POST /api/v1/products/:id/generate_marketplace_video`
- `POST /api/v1/imports`

If the account is still unconfirmed, these write actions return `403 email_not_confirmed`. Call `POST /api/v1/auth/resend_confirmation`, then wait until `GET /api/v1/me` shows a non-null `user.confirmed_at`.

### Create a product

Use:

- `POST /api/v1/products`

You may send only seller fields, or seller fields plus `uploads`.

If `uploads` is present, the response includes:

- `product.images`
- `upload_instructions`

Each upload instruction contains:

- `image_id`
- `method`
- `upload_url`
- `headers`
- `expires_at`

Upload note:

- `image_id` is a ULID string
- `upload_url` should be treated as an opaque signed URL

### Upload originals

Perform a direct `PUT` to `upload_url` with the returned headers.

### Add photos later

Use:

- `POST /api/v1/products/:id/prepare_uploads`

This returns the same `upload_instructions` shape as create-time uploads and is the API path for attaching more originals after the product already exists.

### Finalize uploads

Use:

- `POST /api/v1/products/:id/finalize_uploads`

The backend validates the uploaded image ids and starts processing.

The finalize response includes:

- `product`
- `finalized_images`
- `processing_run`

### Poll product state

Use:

- `GET /api/v1/products/:id`

Watch:

- `status`
- `latest_processing_run`
- `description_draft`
- `price_research`
- `marketplace_listings`
- `latest_marketplace_video_generation_run`
- `images`
- `videos`

### Mutable product actions

- `PATCH /api/v1/products/:id`
- `DELETE /api/v1/products/:id`
- `POST /api/v1/products/:id/reprocess`
- `POST /api/v1/products/:id/generate_marketplace_video`
- `POST /api/v1/products/:id/mark_sold`
- `POST /api/v1/products/:id/archive`
- `POST /api/v1/products/:id/unarchive`

## 4. Product Payload Notes

Current mobile-relevant fields:

- `product_tab_id`, `product_tab`
- `storefront_enabled`, `storefront_published_at`
- `description_draft`
- `price_research`
- `marketplace_listings[*].external_url`
- `latest_processing_run`
- `latest_lifestyle_generation_run`
- `latest_marketplace_video_generation_run`
- `latest_marketplace_video_generation_run.error_code`
- `latest_marketplace_video_generation_run.error_message`
- `latest_marketplace_video_generation_run.started_at`, `finished_at`, `updated_at`
- `latest_marketplace_video_generation_run.payload.summary`
- `latest_marketplace_video_generation_run.payload.progress`
- `latest_marketplace_video_generation_run.payload.reference_image_count`
- `latest_marketplace_video_generation_run.payload.usage`
- `image_urls`
- `images[*].url`
- `images[*].storefront_visible`
- `images[*].storefront_position`
- `videos[*].url`
- `videos[*].generation_run_id`
- `videos[*].marketplaces`
- `videos[*].content_type`
- `videos[*].original_filename`
- `videos[*].width`, `height`, `byte_size`
- `videos[*].duration_ms`, `frame_rate`, `codec`
- `videos[*].source_image_ids`

Media URL rule:

- use `image_urls` when the app only needs ordered URLs
- use `images` when the app needs metadata plus `url`

## 5. Product Tabs

Endpoints:

- `GET /api/v1/product_tabs`
- `POST /api/v1/product_tabs`
- `PATCH /api/v1/product_tabs/:id`
- `DELETE /api/v1/product_tabs/:id`

Use tabs for seller workspace organization only. They do not affect public storefront routing.

## 6. Storefront

Endpoints:

- `GET /api/v1/storefront`
- `PUT /api/v1/storefront`
- `GET /api/v1/storefront/pages`
- `POST /api/v1/storefront/pages`
- `PATCH /api/v1/storefront/pages/:page_id`
- `DELETE /api/v1/storefront/pages/:page_id`
- `PUT /api/v1/storefront/pages/order`
- `POST /api/v1/storefront/assets/:kind/prepare_upload`
- `DELETE /api/v1/storefront/assets/:kind`

Storefront payload notes:

- `assets[*].url` is the public branding asset URL
- `image_urls` is the ordered list of branding asset URLs
- `themes` returns the full preset list for the theme picker
- each `themes[*]` entry includes `id`, `label`, and `colors`
- send `storefront.theme_id` using one of the returned `themes[*].id` values
- `pages` is the seller-managed content set

Branding asset upload flow:

1. call `prepare_upload`
2. upload to returned signed URL
3. refetch storefront config

## 7. Lifestyle Images & Marketplace Video

Endpoints:

- `POST /api/v1/products/:id/generate_lifestyle_images`
- `GET /api/v1/products/:id/lifestyle_generation_runs`
- `POST /api/v1/products/:id/generate_marketplace_video`
- `GET /api/v1/products/:id/marketplace_video_generation_runs`
- `POST /api/v1/products/:id/generated_images/:image_id/approve`
- `DELETE /api/v1/products/:id/generated_images/:image_id`

Generated images are normal `product_images` with `kind: "lifestyle_generated"`.
Generated marketplace videos are separate `product_videos` with `kind: "marketplace_generated"`.

Relevant fields:

- `lifestyle_generation_run_id`
- `scene_key`
- `variant_index`
- `source_image_ids`
- `seller_approved`
- `approved_at`
- `generation_run_id`
- `marketplaces`
- `duration_ms`
- `width`, `height`, `byte_size`
- `frame_rate`
- `codec`
- `latest_marketplace_video_generation_run.payload.prompt_version`
- `latest_marketplace_video_generation_run.payload.reference_image_count`
- `latest_marketplace_video_generation_run.payload.usage`

Marketplace video notes:

- `POST /api/v1/products/:id/generate_marketplace_video` uses the authenticated user's `selected_marketplaces`
- the generator downloads up to 3 reference images, sends them to Gemini Veo Fast as `bytesBase64Encoded` image inputs plus seller description context, and retries once on Veo 3.1 Lite with a primary image if Gemini audio-filters the first response
- the generated master targets `MP4 / H.264 / 1080x1920 / 9:16 / 30 fps / 8-12 sec`
- the initial `202 Accepted` response is only a kickoff; mobile should expect `video_generation_run.status` to be `queued` or `running` and `videos` to stay empty until later polling
- `error_code` can be `ai_content_filtered` when Gemini returns no downloadable video after safety filtering
- `payload.reference_image_count` can drop from `3` to `1` after the Fast -> Lite fallback, and `payload.usage` may be `null` on successful fallback runs
- poll either `GET /api/v1/products/:id` or `GET /api/v1/products/:id/marketplace_video_generation_runs` for completion

## 8. Storefront Image Curation

Endpoints:

- `PATCH /api/v1/products/:id/images/:image_id/storefront`
- `PUT /api/v1/products/:id/images/storefront_order`

These control public storefront gallery visibility and ordering.

Identifier handling summary:

- send ULID strings for `product_tab_id`, `page_id`, `image_id`, and reorder arrays like `image_ids`
- expect ULID strings back in public `id`, `product_tab_id`, `lifestyle_generation_run_id`, `generation_run_id`, and `source_image_ids` fields

## 9. Inquiries

Endpoints:

- `GET /api/v1/inquiries`
- `DELETE /api/v1/inquiries/:id`

Supported query params:

- `page`
- `page_size`
- `q`

## 10. Exports and Imports

Export:

- `POST /api/v1/exports`
- `GET /api/v1/exports/:id`

Import:

- `POST /api/v1/imports`
- `GET /api/v1/imports/:id`

Mobile expectations:

- both are asynchronous
- poll the `:id` endpoint after the initial `202 Accepted`
- export records expose `download_url` when ready
- import records expose aggregate counts, failure details, and ULID-based `payload.created_product_ids` values when present
- `download_url` should be treated as an opaque signed URL
- import archives are capped in decoded size and rejected if they contain unsafe entry paths

## 11. Error Handling

Treat these as first-class cases:

- `401` — missing or invalid token
- `403 email_not_confirmed` — email not yet confirmed (blocks product creation, uploads, reprocessing, lifestyle generation, and imports)
- `404` — unknown resource or foreign-owned resource
- `429 rate_limited` — too many public auth attempts (`register` / `login`)
- `402` — usage limit exceeded
- `422 validation_failed` — bad input payload
- `422` with other codes — invalid resource state
- `502 storage_unavailable` — object storage upload signing is not configured
- `502 upload_signing_failed` — a signed upload URL could not be generated

For `402`, show the returned `upgrade_url` directly.

For `403 email_not_confirmed`, prompt the user to check their inbox or call `POST /api/v1/auth/resend_confirmation` to request a new link.

## 12. Suggested Sync Strategy

Recommended mobile sequence:

1. `GET /api/v1/me`
2. `GET /api/v1/me/usage`
3. `GET /api/v1/product_tabs`
4. `GET /api/v1/storefront`
5. `GET /api/v1/products`

On product detail:

1. `GET /api/v1/products/:id`
2. poll while `status` is `uploading` or `processing`
3. refresh after finalize, reprocess, or lifestyle actions

## 13. Source of Truth

- route list: `lib/reseller_web/router.ex`
- JSON shapes: `lib/reseller_web/controllers/api/v1/*`
- generated spec: `/api/v1/openapi.json`
- compressed reference: `docs/API.md`
