This commit is contained in:
2026-08-17 21:56:09 +02:00
parent 6fcdc74843
commit a7e53ca0a3
6 changed files with 162 additions and 56 deletions
+85 -21
View File
@@ -123,6 +123,27 @@ info:
processed_product_id / raw_product_id). Use GET /products when a UUID is needed. processed_product_id / raw_product_id). Use GET /products when a UUID is needed.
Display name is items[].name (title omitted when identical). Display name is items[].name (title omitted when identical).
### AI descriptions (HTML body, not a short blurb)
Default processing_type full runs normalize, parse specs, fill fields, EPREL
(when enabled), categorize, then AI enhance. Enhance writes the display name
and a full product body.
Completed items[].description is that body: one HTML string (tags matching
section types h1/h2/h3/h4, p, ul) — never a JSON array and never a one- or
two-sentence slogan. Length and structure come from the assigned category
description_template (description formula): ordered sections with their own
instructions. A formula can ask for a short paragraph, 100-word blocks,
several heading/list sections, or a very large multi-section article. There
is no request-body size enum; configure length on the category. When a
category has no formula, the default is still 1-3 factual paragraphs with
limited HTML, not a headline-only stub.
items[].description on the start body is optional source text the model may
rewrite. Poll GET /products/process/{id} for the generated HTML.
meta_title / meta_description are separate short SEO fields and may be
omitted depending on company configuration.
Note: Dashboard JSON under /api/* uses session cookies + CSRF and is separate Note: Dashboard JSON under /api/* uses session cookies + CSRF and is separate
from this public API-key surface. Other path aliases from this public API-key surface. Other path aliases
(/categories/create, /attributes/create, /campaigns) appear next to canonical paths. (/categories/create, /attributes/create, /campaigns) appear next to canonical paths.
@@ -674,7 +695,10 @@ paths:
description: Enriched display title description: Enriched display title
processed_description: processed_description:
type: string type: string
description: Enriched long description text description: |
Enriched product body as one HTML string from the category
description formula. Length follows that formula (including very
large multi-section copy), not a short summary.
status: status:
type: string type: string
enum: enum:
@@ -788,14 +812,23 @@ paths:
- Processing - Processing
summary: Start processing by EAN summary: Start processing by EAN
description: | description: |
Public process API (handleV1StartProcess). Upserts raw products from Public process API. Upserts raw products from items[].ean (GTIN), enqueues
items[].ean (GTIN), enqueues one processing job, returns HTTP 200 with a data envelope. one processing job, returns HTTP 200 with a data envelope.
Default processing_type full runs the full pipeline including AI enhance:
the model writes a retail title (name) and a full HTML product description
driven by the category description formula. That body can be long —
multiple headings, paragraphs, and lists, including very large
multi-section articles — not a one- or two-sentence summary.
Primary body: items[].ean. Alternate body on the same handler: raw_product_ids Primary body: items[].ean. Alternate body on the same handler: raw_product_ids
(raw_products.id UUID list — not GET /products data[].id). Prefer items for public (raw_products.id UUID list — not GET /products data[].id). Prefer items for public
integrations. On items[].ean, assertV1ProcessGates runs before EnsureRaw so plan/credit integrations. On items[].ean, assertV1ProcessGates runs before EnsureRaw so plan/credit
failures cannot spam catalog writes (HTTP 402 coded envelope). failures cannot spam catalog writes (HTTP 402 coded envelope).
Poll GET /products/process/{id} until status is COMPLETED, then read
items[].description for the generated HTML.
Not an alias of POST /process (flat ProcessingJob / 202). Do not mix envelopes. Not an alias of POST /process (flat ProcessingJob / 202). Do not mix envelopes.
requestBody: requestBody:
$ref: "#/components/requestBodies/StartProcessByEAN" $ref: "#/components/requestBodies/StartProcessByEAN"
@@ -903,9 +936,13 @@ paths:
jobs omit items. Not the same shape as GET /process/{id} (flat ProcessingJob). jobs omit items. Not the same shape as GET /process/{id} (flat ProcessingJob).
On COMPLETED items, name is the product display name (title is omitted when On COMPLETED items, name is the product display name (title is omitted when
identical). Internal UUIDs (id / processed_product_id / raw_product_id) are identical). description is the generated product body: one HTML string
omitted — use GET /products for those. Optional meta_title and following the category description formula (h1/h2/h3/h4, p, ul). Expect
meta_description may be omitted depending on company configuration. real catalog copy — often several paragraphs and lists, sometimes a very
large article — not a one- or two-sentence blurb. Internal UUIDs (id /
processed_product_id / raw_product_id) are omitted — use GET /products for
those. Optional meta_title and meta_description are short SEO fields and
may be omitted depending on company configuration.
parameters: parameters:
- name: id - name: id
in: path in: path
@@ -938,12 +975,13 @@ paths:
category_id: '50' category_id: '50'
category_name: Cookers category_name: Cookers
name: VOX electric cooker EHT 6020 WG name: VOX electric cooker EHT 6020 WG
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven." description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
attributes: attributes:
brand: Vox brand: Vox
product_model: EHT6020WG product_model: EHT6020WG
main_image: https://images.example.com/products/vox-eht6020wg.jpg main_image: https://images.example.com/products/vox-eht6020wg.jpg
more_images: null more_images:
- https://images.example.com/products/vox-eht6020wg-side.jpg
eprel: eprel:
id: "1234567" id: "1234567"
label: https://eprel.ec.europa.eu/label/Example label: https://eprel.ec.europa.eu/label/Example
@@ -4389,7 +4427,9 @@ paths:
summary: Processing job status (raw_product_ids surface) summary: Processing job status (raw_product_ids surface)
description: | description: |
Public processing job JSON: status, counts, timestamps, processing_type. Public processing job JSON: status, counts, timestamps, processing_type.
Omits current_step, step_progress, and company_id. Completed jobs still include items[]. Omits current_step, step_progress, and company_id. Completed jobs still include
items[] with the same AI HTML description contract as GET /products/process/{id}
(category formula body, not a one- or two-sentence blurb).
parameters: parameters:
- $ref: "#/components/parameters/ID" - $ref: "#/components/parameters/ID"
responses: responses:
@@ -4421,6 +4461,7 @@ paths:
status: processed status: processed
name: Acme Wireless Earbuds ANC Black name: Acme Wireless Earbuds ANC Black
category: Headphones category: Headphones
description: "<h1>Acme Wireless Earbuds ANC Black</h1><p>Acme Wireless Earbuds ANC Black are in-ear headphones for commuting and desk work. Active noise cancellation, a secure fit, and a charging case cover a full workday of listening. The compact buds sit flush enough for glasses and hats, and the case fits a jacket pocket.</p><p>Pair over Bluetooth and switch between a phone and a laptop with the onboard controls. Transparency mode lets nearby voices through without removing the buds. The default ear-tip set covers common sizes; swap tips if bass feels weak or the seal slips during a walk.</p><h2>In the box</h2><ul><li>Wireless earbuds (left and right)</li><li>Charging case with USB-C cable</li><li>Three ear-tip sizes</li><li>Quick-start guide</li></ul><p>Charge the case before first use. Keep the charging contacts dry and store the buds in the case so they sleep and recharge between sessions. For replacement tips or a spare case, quote the product name and the EAN on the box.</p>"
total_items: 1 total_items: 1
'400': '400':
description: Path id is not a UUID. description: Path id is not a UUID.
@@ -5133,7 +5174,10 @@ components:
description: Optional product title seed for enrichment description: Optional product title seed for enrichment
description: description:
type: string type: string
description: Optional long description seed for enrichment description: |
Optional source/seed text. AI enhance rewrites this into the
category description-formula HTML body. It is not the final
poll payload and does not cap length to a short summary.
specifications: specifications:
type: array type: array
description: Optional key/value specification pairs for attribute hints description: Optional key/value specification pairs for attribute hints
@@ -5193,10 +5237,13 @@ components:
Not PresentProduct.id. Use GET /products data[].raw_product_id. Not PresentProduct.id. Use GET /products data[].raw_product_id.
processing_type: processing_type:
description: | description: |
full (default); named steps category|title|description|attributes (string or full (default) runs the whole pipeline including AI enhance, which
writes formula-driven HTML descriptions (not a one- or two-sentence
blurb). Named steps category|title|description|attributes (string or
array of those steps); dual-mode also accepts v2 dashboard types such as array of those steps); dual-mode also accepts v2 dashboard types such as
normalize_only, enhance_only, attributes_only, eprel_only, categorize_only normalize_only, enhance_only, attributes_only, eprel_only, categorize_only
(ParseV1ProcessingType). Alias processingType accepted when values match. (ParseV1ProcessingType). Alias processingType accepted when values match.
The description step still emits the category formula HTML body.
oneOf: oneOf:
- type: string - type: string
enum: enum:
@@ -5550,8 +5597,13 @@ components:
type: string type: string
description: description:
type: string type: string
description: |
Product body. After processing this is the AI HTML description
(category formula), not a one- or two-sentence summary.
processed_description: processed_description:
type: string type: string
description: |
Enriched HTML body from AI enhance. Same contract as ProcessItem.description.
status: status:
type: string type: string
example: completed example: completed
@@ -5664,8 +5716,16 @@ components:
nullable: true nullable: true
title_template: title_template:
nullable: true nullable: true
description: |
Title formula for AI enhance. Controls how processed name is built
(type, brand, model, and other slots).
description_template: description_template:
nullable: true nullable: true
description: |
Description formula for AI enhance. Ordered sections (h1/h2/h3/h4, p, ul)
with per-section instructions. Controls HTML structure and length of
processed items[].description — short, long, or very large copy.
Not a one- or two-sentence cap.
created_at: created_at:
type: string type: string
format: date-time format: date-time
@@ -6297,10 +6357,11 @@ components:
description: | description: |
One COMPLETED process line (public contract). Successful items One COMPLETED process line (public contract). Successful items
expose category as the human-readable display name (category_id holds expose category as the human-readable display name (category_id holds
categories.unique_id), a description string that may include section categories.unique_id), a description string that is generated HTML
HTML (h1/h2/h3/h4, p, ul — never a JSON array), optional SEO meta_title / product copy (h1/h2/h3/h4, p, ul — never a JSON array and never a one-
meta_description (plain text; some companies omit these fields), or two-sentence slogan), optional SEO meta_title / meta_description
optional eprel object or null, clean attributes, and images. (plain text; some companies omit these fields), optional eprel object
or null, clean attributes, and images.
Product display name is name (primary). title is omitted when identical to name. Product display name is name (primary). title is omitted when identical to name.
Internal UUIDs (id, processed_product_id, raw_product_id) are omitted from this Internal UUIDs (id, processed_product_id, raw_product_id) are omitted from this
public shape — use catalog APIs when a product UUID is required. public shape — use catalog APIs when a product UUID is required.
@@ -6317,8 +6378,6 @@ components:
description: | description: |
Human-readable category display name (same value as category_name when Human-readable category display name (same value as category_name when
resolved). Prefer this for UI. Machine unique_id is category_id. resolved). Prefer this for UI. Machine unique_id is category_id.
ASSUMPTION: historically this field held categories.unique_id; clients
that need the opaque id must read category_id (additive).
category_id: category_id:
type: string type: string
nullable: true nullable: true
@@ -6356,9 +6415,14 @@ components:
type: string type: string
nullable: true nullable: true
description: | description: |
Product body description as a string — never a one-element JSON array. Generated product body as one HTML string — never a JSON array and
May include section HTML tags (h1/h2/h3/h4, p, ul). Plain-text never a one- or two-sentence slogan. AI enhance follows the assigned
descriptions remain plain. Optional meta_description stays plain SEO text. category description_template (description formula): ordered sections
of type h1, h2, h3, h4, p, and ul, each with its own instructions.
Output can be a short paragraph, several 100-word blocks, or a very
large multi-section article. When no formula is set, expect 1-3
factual paragraphs with limited HTML. Optional meta_description stays
plain SEO text and is not a substitute for this field.
attributes: attributes:
type: object type: object
additionalProperties: true additionalProperties: true
@@ -6477,7 +6541,7 @@ components:
category_id: '50' category_id: '50'
category_name: Cookers category_name: Cookers
name: VOX electric cooker EHT 6020 WG name: VOX electric cooker EHT 6020 WG
description: "Vox Electronics electric cooker EHT 6020 WG offers strong value with four electric hobs and a 65 L fan oven. Energy class A with practical everyday capacity." description: "<h1>VOX electric cooker EHT 6020 WG</h1><p>The VOX EHT 6020 WG is a free-standing electric cooker for everyday household cooking. Four ceramic hobs and a 65 L fan oven let you boil, fry, and bake at the same time, while energy class A helps keep running costs down. The 60 cm width fits a standard kitchen niche. An enamel oven cavity and removable inner door glass are designed for cleaning after heavy use.</p><p>Run sauces or pasta on the hob while the oven handles a roast or tray bake. Heat is even across the ceramic zones, and residual-heat indicators reduce the chance of accidental contact after cooking. Fan mode suits multi-level baking; conventional heat remains available for dishes that prefer still air. A full-width grill finishes cheese, gratins, and toast.</p><h2>Key features</h2><ul><li>Four electric ceramic hobs with residual-heat indicators</li><li>65 L fan oven plus grill, energy class A</li><li>60 cm free-standing body (0.6 m wide, 0.85 m high, 0.6 m deep)</li><li>Enamel cavity and removable inner door glass</li><li>60-month manufacturer warranty</li></ul><p>Install in a standard 60 cm cooker slot with the dedicated electric supply shown on the rating plate. Leave rear clearance for ventilation and use a heat-resistant worktop next to the hob. This product is for household kitchens, not commercial catering loads.</p><p>Use cookware that matches each zone so energy is not wasted around the pan. Wipe the ceramic surface regularly and empty crumbs from the oven floor to preserve appearance and efficiency. For spare parts, quote model EHT 6020 WG and the EAN on the rating plate.</p>"
attributes: attributes:
brand: Vox brand: Vox
product_model: EHT6020WG product_model: EHT6020WG
@@ -467,6 +467,44 @@ func TestV1OpenAPIYAMLOmitsInternalPromptWording(t *testing.T) {
} }
} }
func TestV1OpenAPIYAMLDocumentsAIDescriptionHTML(t *testing.T) {
t.Parallel()
doc := string(v1OpenAPIYAML)
for _, needle := range []string{
"AI descriptions (HTML body, not a short blurb)",
"description_template (description formula)",
"one- or two-sentence",
"<h1>VOX electric cooker EHT 6020 WG</h1>",
"<h2>Key features</h2>",
} {
if !strings.Contains(doc, needle) {
t.Fatalf("openapi missing AI description contract text %q", needle)
}
}
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
comps, _ := root["components"].(map[string]any)
examples, _ := comps["examples"].(map[string]any)
completed, _ := examples["ProcessCompletedExample"].(map[string]any)
val, _ := completed["value"].(map[string]any)
data, _ := val["data"].(map[string]any)
items, _ := data["items"].([]any)
if len(items) == 0 {
t.Fatal("ProcessCompletedExample: expected items")
}
item, _ := items[0].(map[string]any)
desc, _ := item["description"].(string)
for _, tag := range []string{"<h1>", "<p>", "<h2>", "<ul>", "<li>"} {
if !strings.Contains(desc, tag) {
t.Fatalf("ProcessCompletedExample description must include %s (got %q)", tag, desc)
}
}
if len(desc) < 800 {
t.Fatalf("ProcessCompletedExample description too short (%d chars); expected full AI HTML body", len(desc))
}
}
func TestV1OpenAPIYAMLProcessItemCompletedExamplesOmitLeakyShape(t *testing.T) { func TestV1OpenAPIYAMLProcessItemCompletedExamplesOmitLeakyShape(t *testing.T) {
t.Parallel() t.Parallel()
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML) root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
+14 -4
View File
@@ -139,6 +139,9 @@ func PublicV1Error(msg string) string {
if code := mapPublicErrorCode(cleaned); code != "" { if code := mapPublicErrorCode(cleaned); code != "" {
return code return code
} }
if code := mapPublicV1FallbackCode(cleaned); code != "" {
return code
}
if looksLikeInternalProviderError(cleaned) { if looksLikeInternalProviderError(cleaned) {
return publicErrProcessingFailed return publicErrProcessingFailed
} }
@@ -172,10 +175,6 @@ func mapPublicErrorCode(msg string) string {
return publicErrProviderUnavailable return publicErrProviderUnavailable
case strings.Contains(lower, "ai_enhance: skipped"): case strings.Contains(lower, "ai_enhance: skipped"):
return publicErrProcessingFailed return publicErrProcessingFailed
case strings.Contains(lower, "unavailable"),
strings.Contains(lower, "http 503"),
strings.Contains(lower, "overloaded"):
return publicErrProviderUnavailable
case strings.Contains(lower, "openai"), case strings.Contains(lower, "openai"),
strings.Contains(lower, "gpt-"), strings.Contains(lower, "gpt-"),
strings.Contains(lower, " for model"): strings.Contains(lower, " for model"):
@@ -184,6 +183,17 @@ func mapPublicErrorCode(msg string) string {
return "" return ""
} }
func mapPublicV1FallbackCode(msg string) string {
lower := strings.ToLower(strings.TrimSpace(msg))
switch {
case strings.Contains(lower, "unavailable"),
strings.Contains(lower, "http 503"),
strings.Contains(lower, "overloaded"):
return publicErrProviderUnavailable
}
return ""
}
func looksLikeInternalProviderError(msg string) bool { func looksLikeInternalProviderError(msg string) bool {
lower := strings.ToLower(msg) lower := strings.ToLower(msg)
for _, n := range []string{ for _, n := range []string{
+6 -12
View File
@@ -62,14 +62,6 @@ func TestTruncateError_mapsInternalProviderLeaks(t *testing.T) {
in: "ai_enhance: skipped (platform OpenAI unset; configure admin settings or company BYOK)", in: "ai_enhance: skipped (platform OpenAI unset; configure admin settings or company BYOK)",
want: "provider_unavailable", want: "provider_unavailable",
}, },
{
in: "openai retries exhausted: green-chat unavailable",
want: "provider_unavailable",
},
{
in: "upstream 503: model overloaded",
want: "provider_unavailable",
},
} }
for _, tc := range cases { for _, tc := range cases {
got := TruncateError(errString(tc.in)) got := TruncateError(errString(tc.in))
@@ -91,6 +83,8 @@ func TestPublicV1Error_stripsProviderInternals(t *testing.T) {
}{ }{
{in: `openai length-capped at max_tokens=8000 for model "gpt-4o"`, want: "processing_failed"}, {in: `openai length-capped at max_tokens=8000 for model "gpt-4o"`, want: "processing_failed"},
{in: "ai_enhance: skipped (platform OpenAI unset; configure admin settings or company BYOK)", want: "provider_unavailable"}, {in: "ai_enhance: skipped (platform OpenAI unset; configure admin settings or company BYOK)", want: "provider_unavailable"},
{in: "openai retries exhausted: green-chat unavailable", want: "provider_unavailable"},
{in: "upstream 503: model overloaded", want: "provider_unavailable"},
{in: "processing_failed", want: "processing_failed"}, {in: "processing_failed", want: "processing_failed"},
{in: "Product data not available", want: "Product data not available"}, {in: "Product data not available", want: "Product data not available"},
{in: "processing.job.error.all_failed|count=3", want: "processing.job.error.all_failed|count=3"}, {in: "processing.job.error.all_failed|count=3", want: "processing.job.error.all_failed|count=3"},
@@ -122,13 +116,13 @@ func TestTruncateError_classifiesProviderFailures(t *testing.T) {
want: "AI provider timed out", want: "AI provider timed out",
}, },
{ {
in: "unauthorized", want: "AI provider rejected the API key"}, in: "unauthorized", want: "AI provider rejected the API key"},
{ {
in: "openai http 401", want: "AI provider rejected the API key"}, in: "openai http 401", want: "AI provider rejected the API key"},
{in: "rate limited or server error", want: "AI provider temporarily unavailable"}, {in: "rate limited or server error", want: "AI provider temporarily unavailable"},
{in: "too many requests", want: "AI provider rate limited"}, {in: "too many requests", want: "AI provider rate limited"},
{in: "upstream 503: model overloaded", want: "provider_unavailable"}, {in: "upstream 503: model overloaded", want: "upstream 503: model overloaded"},
{in: "openai retries exhausted: green-chat unavailable", want: "provider_unavailable"}, {in: "openai retries exhausted: green-chat unavailable", want: "green-chat unavailable"},
} }
for _, tc := range cases { for _, tc := range cases {
got := TruncateError(errString(tc.in)) got := TruncateError(errString(tc.in))
+16 -16
View File
@@ -570,14 +570,14 @@
"ja": "この招待とは別のメールアドレスでログインしています。" "ja": "この招待とは別のメールアドレスでログインしています。"
}, },
"Expired link? Ask a company admin to re-issue — there is no self-serve resend API.": { "Expired link? Ask a company admin to re-issue — there is no self-serve resend API.": {
"es": "¿Enlace caducado? Pide a un administrador que lo reemita — no hay API de reenvío autoservicio. Administradores de plataforma:", "es": "¿Enlace caducado? Pide a un administrador de la empresa que lo reemita — no hay API de reenvío autoservicio.",
"fr": "Lien expiré ? Demandez à un administrateur de le réémettre — il n'y a pas d'API de renvoi en libre-service. Administrateurs de plateforme :", "fr": "Lien expiré ? Demandez à un administrateur de l'entreprise de le réémettre — il n'y a pas d'API de renvoi en libre-service.",
"de": "Abgelaufener Link? Bitten Sie einen Admin um Neuausstellung — es gibt keine Self-Service-API zum erneuten Senden. Plattform-Admins:", "de": "Abgelaufener Link? Bitten Sie einen Unternehmens-Admin um Neuausstellung — es gibt keine Self-Service-API zum erneuten Senden.",
"it": "Link scaduto? Chiedi a un amministratore di riemetterlo — non c'è un'API di reinvio self-service. Amministratori della piattaforma:", "it": "Link scaduto? Chiedi a un amministratore dell'azienda di riemetterlo — non c'è un'API di reinvio self-service.",
"pt": "Link expirado? Peça a um administrador para o reemitir — não há API de reenvio self-service. Administradores da plataforma:", "pt": "Link expirado? Peça a um administrador da empresa para o reemitir — não há API de reenvio self-service.",
"nl": "Verlopen link? Vraag een beheerder om opnieuw uit te geven — er is geen self-service-API voor opnieuw verzenden. Platformbeheerders:", "nl": "Verlopen link? Vraag een bedrijfsbeheerder om opnieuw uit te geven — er is geen self-service-API voor opnieuw verzenden.",
"pl": "Wygasły link? Poproś administratora o ponowne wystawienie — nie ma API samodzielnego ponownego wysyłania. Administratorzy platformy:", "pl": "Wygasły link? Poproś administratora firmy o ponowne wystawienie — nie ma API samodzielnego ponownego wysyłania.",
"ja": "期限切れのリンクですか?管理者に再発行を依頼してください — セルフサービスの再送信APIはありません。プラットフォーム管理者:" "ja": "期限切れのリンクですか?会社の管理者に再発行を依頼してください — セルフサービスの再送信APIはありません。"
}, },
"You're on the team": { "You're on the team": {
"es": "Ya formas parte del equipo", "es": "Ya formas parte del equipo",
@@ -730,14 +730,14 @@
"ja": "再発行の手順:" "ja": "再発行の手順:"
}, },
"ask a company admin to re-issue a set-password link to this email, or to revoke this invite and send a new one to the email you use to sign in.": { "ask a company admin to re-issue a set-password link to this email, or to revoke this invite and send a new one to the email you use to sign in.": {
"es": "si cambió tu correo real de acceso (desfase de correo), pide a un administrador de la empresa que revoque esta invitación y envíe una nueva al correo con el que inicias sesión. Los administradores de plataforma también pueden reemitir enlaces para establecer contraseña desde Admin → Usuarios.", "es": "pide a un administrador de la empresa que reemita un enlace para establecer contraseña a este correo, o que revoque esta invitación y envíe una nueva al correo con el que inicias sesión.",
"fr": "si votre vrai e-mail de connexion a changé (dérive d'e-mail), demandez à un administrateur de l'entreprise de révoquer cette invitation et d'en envoyer une nouvelle à l'e-mail que vous utilisez pour vous connecter. Les administrateurs de la plateforme peuvent aussi réémettre des liens de définition de mot de passe depuis Admin → Utilisateurs.", "fr": "demandez à un administrateur de l'entreprise de réémettre un lien de définition de mot de passe vers cet e-mail, ou de révoquer cette invitation et d'en envoyer une nouvelle à l'e-mail que vous utilisez pour vous connecter.",
"de": "wenn sich Ihre echte Anmelde-E-Mail geändert hat (E-Mail-Drift), bitten Sie einen Unternehmens-Admin, diese Einladung zu widerrufen und eine neue an die E-Mail zu senden, mit der Sie sich anmelden. Plattform-Admins können Passwort-Links auch unter Admin → Benutzer erneut ausstellen.", "de": "bitten Sie einen Unternehmens-Admin, einen Link zum Festlegen des Passworts an diese E-Mail auszustellen oder diese Einladung zu widerrufen und eine neue an die E-Mail zu senden, mit der Sie sich anmelden.",
"it": "se la tua email di accesso reale è cambiata (deriva email), chiedi a un amministratore dell'azienda di revocare questo invito e inviarne uno nuovo all'email con cui accedi. Gli amministratori della piattaforma possono anche riemettere link per impostare la password da Admin → Utenti.", "it": "chiedi a un amministratore dell'azienda di riemettere un link per impostare la password a questa email, oppure di revocare questo invito e inviarne uno nuovo all'email con cui accedi.",
"pt": "se o seu e-mail real de início de sessão mudou (desvio de e-mail), peça a um administrador da empresa para revogar este convite e enviar um novo para o e-mail que utiliza para iniciar sessão. Os administradores da plataforma também podem reemitir links de definição de palavra-passe em Admin → Utilizadores.", "pt": "peça a um administrador da empresa para reemitir um link de definição de palavra-passe para este e-mail, ou para revogar este convite e enviar um novo para o e-mail que utiliza para iniciar sessão.",
"nl": "als uw echte login-e-mail is gewijzigd (e-maildrift), vraag dan een bedrijfsbeheerder om deze uitnodiging in te trekken en een nieuwe te sturen naar het e-mailadres waarmee u inlogt. Platformbeheerders kunnen ook set-wachtwoordlinks opnieuw uitgeven via Admin → Gebruikers.", "nl": "vraag een bedrijfsbeheerder om een set-wachtwoordlink opnieuw uit te geven naar dit e-mailadres, of om deze uitnodiging in te trekken en een nieuwe te sturen naar het e-mailadres waarmee u inlogt.",
"pl": "jeśli zmienił się Twój prawdziwy e-mail logowania (dryf e-mail), poproś administratora firmy o unieważnienie tego zaproszenia i wysłanie nowego na e-mail używany do logowania. Administratorzy platformy mogą też ponownie wystawiać linki ustawienia hasła w Admin → Użytkownicy.", "pl": "poproś administratora firmy o ponowne wystawienie linku do ustawienia hasła na ten e-mail albo o unieważnienie tego zaproszenia i wysłanie nowego na e-mail używany do logowania.",
"ja": "実際のログイン用メールが変わった場合(メール変更)、会社の管理者にこの招待取り消しと、ログインに使うメールへ新しい招待送信を依頼してください。プラットフォーム管理者は「管理 → ユーザー」からパスワード設定リンクも再発行できます。" "ja": "会社の管理者にこのメール向けのパスワード設定リンクの再発行を依頼するか、この招待取り消しログインに使うメールへ新しい招待を送るよう依頼してください。"
}, },
"You don't have permission to open company settings. Ask a company admin for help.": { "You don't have permission to open company settings. Ask a company admin for help.": {
"es": "No tienes permiso para abrir la configuración de la empresa. Pide ayuda a un administrador de la empresa.", "es": "No tienes permiso para abrir la configuración de la empresa. Pide ayuda a un administrador de la empresa.",
+1 -1
View File
@@ -421,7 +421,7 @@ export const DOCS_GUIDE_TREE: DocsGuideTree = {
synonyms: ["ean", "items[].ean", "products/process"], synonyms: ["ean", "items[].ean", "products/process"],
title: "Public process (EAN batch)", title: "Public process (EAN batch)",
outcome: "Start and poll enrichment using POST/GET /products/process.", outcome: "Start and poll enrichment using POST/GET /products/process.",
body: "POST body uses items[].ean (preferred). Handler also accepts raw_product_ids as an alternate on this same path. Responses use HTTP 200 with { data: { process_id, … } } and completed items[] when done.", body: "POST body uses items[].ean (preferred). Responses use HTTP 200 with { data: { process_id, … } }. Poll GET until COMPLETED. items[].description is generated HTML product copy (headings, paragraphs, lists) whose length follows the category description formula — not a one- or two-sentence blurb. Some categories request very long multi-section articles. Handler also accepts raw_product_ids as an alternate on this same path.",
authNote: AUTH_NOTE, authNote: AUTH_NOTE,
endpoints: [ endpoints: [
endpoint("POST", "/products/process", "Start processing by EAN"), endpoint("POST", "/products/process", "Start processing by EAN"),