{"openapi":"3.1.0","info":{"title":"arText Reports API","description":"Submit text, retrieve the asynchronous report and use structured linguistic recommendations in your own products.","version":"1.0.0"},"paths":{"/v1/reports":{"post":{"tags":["Reports"],"summary":"Create an analysis report","description":"Validates and queues text. The 202 response identifies status and event resources; it does not mean analysis is complete.","operationId":"create_public_report_v1_reports_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":255},{"type":"null"}],"title":"Idempotency-Key"},"description":"Recommended unique key for safely retrying this creation request. It is scoped to the authenticated caller, retained for 24 hours, and must not be reused with a different payload.","example":"document-123-analysis-v1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRequest"},"examples":{"basic":{"summary":"Create a report","value":{"language":"en","domain_slug":"plain-language","text_type_slug":"legal-administrative-text-addressed-to-citizens","text":"The application will be reviewed at the present time.","external_id":"document-123"}}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportAccepted"},"examples":{"queued":{"summary":"Report accepted and queued","value":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"queued","status_url":"/v1/reports/rep_7f12a4c8","events_url":"/v1/reports/rep_7f12a4c8/events"}}}}},"headers":{"Location":{"description":"Canonical status resource for the accepted report.","schema":{"type":"string","example":"/v1/reports/rep_7f12a4c8"}},"Retry-After":{"description":"Minimum number of seconds before the first status poll.","schema":{"type":"integer","minimum":1,"example":2}},"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"409":{"description":"Idempotency-Key was reused with a different payload.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"413":{"description":"Text exceeds the configured word limit.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Request validation failed: unknown genre, or a text_type_slug that names more than one genre and needs domain_slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}},"503":{"description":"The analysis queue is unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}}},"security":[{"ApiKey":[]}]}},"/v1/reports/{report_id}":{"get":{"tags":["Reports"],"summary":"Retrieve status and results","description":"Returns current state. Honor Retry-After while queued/running; completed and failed are terminal.","operationId":"get_public_report_v1_reports__report_id__get","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"string","title":"Report Id"},"description":"Unique report identifier returned by POST /v1/reports."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"},"examples":{"queued":{"summary":"Report queued","value":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"queued","status_url":"/v1/reports/rep_7f12a4c8","events_url":"/v1/reports/rep_7f12a4c8/events"}},"completed":{"summary":"Report completed","value":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"completed","position_in_queue":null,"result":{"schema_version":"2.0","offset_unit":"unicode_code_points","language":"en","genre":{"domain":{"slug":"plain-language","name":"Plain language"},"text_type":{"slug":"legal-administrative-text-addressed-to-citizens","name":"Legal-administrative text"}},"measurements":[{"metric_id":"word-count","value":12,"unit":"words","implementation_version":"1.0"}],"recommendations":[{"id":"rec_1","metric_id":"redundant-expressions","implementation_version":"1.0","metric_title":"Removal of redundant expressions","category":"lexical","summary":"Consider expressing this fragment more directly.","explanation":null,"occurrences":[{"id":"occ_1","text":"at the present time","paragraph_index":0,"sentence_index":0,"start":0,"end":19,"replacement":null,"alternatives":[],"edits":[{"start":0,"end":19,"text":""}]}],"guidance":["Remove the expression when the context allows it."]}]},"error":null}},"failed":{"summary":"Report failed","value":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"failed","position_in_queue":null,"result":null,"error":{"code":"analysis_failed","message":"The analysis could not be completed."}}}}}},"headers":{"Retry-After":{"description":"Present for queued/running reports; wait at least this many seconds before polling again.","schema":{"type":"integer","minimum":1,"example":5}},"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"404":{"description":"Report not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"429":{"description":"Status rate limit or minimum polling interval exceeded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}},"503":{"description":"Request protection is temporarily unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}},"422":{"description":"Validation Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/reports/{report_id}/events":{"get":{"tags":["Reports"],"summary":"Stream transitions over SSE","description":"Optional SSE channel for the initial state, changes and heartbeats. It closes on a terminal state; polling remains the supported fallback.","operationId":"stream_public_report_events_v1_reports__report_id__events_get","parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"string","title":"Report Id"},"description":"Unique report identifier returned by POST /v1/reports."}],"responses":{"200":{"description":"SSE status stream.","content":{"text/event-stream":{"schema":{"type":"string"},"examples":{"transitions":{"summary":"A stream closes after completed or failed","value":"event: status\\ndata: {\"report_id\":\"rep_7f12a4c8\",\"status\":\"running\",\"updated_at\":\"2026-07-15T10:00:02Z\"}\\n\\nevent: status\\ndata: {\"report_id\":\"rep_7f12a4c8\",\"status\":\"completed\",\"updated_at\":\"2026-07-15T10:00:06Z\"}\\n\\n"}}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"description":"Unauthorized","headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"404":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"description":"Not Found","headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"429":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"description":"Too Many Requests","headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}},"503":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"description":"Service Unavailable","headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}},"Retry-After":{"description":"Seconds to wait before a retry can be attempted.","schema":{"type":"integer","minimum":1,"example":2}}}},"422":{"description":"Validation Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/metrics":{"get":{"tags":["Metrics"],"summary":"List metric implementations","description":"Returns measurement and rule implementations, including inactive variants and localized documentation. Narrowing the request to a genre reports which metrics that genre actually evaluates.","operationId":"list_metrics_v1_metrics_get","parameters":[{"name":"analysis_language","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Language"},"description":"Language implementation to return: es, ca or en."},{"name":"documentation_language","in":"query","required":false,"schema":{"type":"string","default":"es","title":"Documentation Language"},"description":"Language used for titles and documentation: es, ca or en."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(measurement|rule)$"},{"type":"null"}],"title":"Kind"},"description":"Filter by measurement or rule."},{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"description":"Filter by the current implementation status."},{"name":"domain_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional genre filter. domain_slug and text_type_slug go together and must name a genre listed by GET /v1/text-types; each metric then reports whether that genre evaluates it and with which thresholds.","title":"Domain Slug"},"description":"Text domain of a genre, as listed by GET /v1/text-types."},{"name":"text_type_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional genre filter. domain_slug and text_type_slug go together and must name a genre listed by GET /v1/text-types; each metric then reports whether that genre evaluates it and with which thresholds.","title":"Text Type Slug"},"description":"Text type (genre) inside domain_slug, as listed by GET /v1/text-types."}],"responses":{"200":{"description":"Metric implementations and their documentation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricCatalog"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Incomplete or unknown genre filter.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/metrics/{metric_id}":{"get":{"tags":["Metrics"],"summary":"Get one metric implementation","description":"Returns the status, category and localized documentation for one language variant, optionally narrowed to a genre.","operationId":"get_metric_v1_metrics__metric_id__get","parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"string","title":"Metric Id"},"description":"Stable conceptual metric identifier."},{"name":"analysis_language","in":"query","required":true,"schema":{"type":"string","title":"Analysis Language"},"description":"Language implementation to return: es, ca or en."},{"name":"documentation_language","in":"query","required":false,"schema":{"type":"string","default":"es","title":"Documentation Language"},"description":"Language used for titles and documentation: es, ca or en."},{"name":"domain_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional genre filter. domain_slug and text_type_slug go together and must name a genre listed by GET /v1/text-types; each metric then reports whether that genre evaluates it and with which thresholds.","title":"Domain Slug"},"description":"Text domain of a genre, as listed by GET /v1/text-types."},{"name":"text_type_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional genre filter. domain_slug and text_type_slug go together and must name a genre listed by GET /v1/text-types; each metric then reports whether that genre evaluates it and with which thresholds.","title":"Text Type Slug"},"description":"Text type (genre) inside domain_slug, as listed by GET /v1/text-types."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Metric"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"404":{"description":"Metric implementation not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Incomplete or unknown genre filter.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/text-types":{"get":{"tags":["Text types"],"summary":"List text domains and genres","description":"Returns the domains (ámbitos) and the genres (text types) available for a language. The slug pair of a genre is what POST /v1/reports expects.","operationId":"list_public_text_types_v1_text_types_get","parameters":[{"name":"language","in":"query","required":false,"schema":{"type":"string","default":"es","title":"Language"},"description":"Catalog language: es, ca or en."}],"responses":{"200":{"description":"Editorial catalog for the requested language.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTextTypeCatalog"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Unsupported language.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/text-types/{domain_slug}/{text_type_slug}":{"get":{"tags":["Text types"],"summary":"Get one genre","description":"Returns the sections and contents recommended for a genre, plus the exact report_defaults to forward to POST /v1/reports.","operationId":"get_public_text_type_v1_text_types__domain_slug___text_type_slug__get","parameters":[{"name":"domain_slug","in":"path","required":true,"schema":{"type":"string","title":"Domain Slug"},"description":"Text domain of a genre, as listed by GET /v1/text-types."},{"name":"text_type_slug","in":"path","required":true,"schema":{"type":"string","title":"Text Type Slug"},"description":"Text type (genre) inside domain_slug, as listed by GET /v1/text-types."},{"name":"language","in":"query","required":false,"schema":{"type":"string","default":"es","title":"Language"},"description":"Catalog language: es, ca or en."}],"responses":{"200":{"description":"Genre detail and its report defaults.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTextTypeDetail"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"404":{"description":"Genre not found for this language.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Unsupported language.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}},"/v1/text-types/{domain_slug}/{text_type_slug}/glossaries":{"get":{"tags":["Text types"],"summary":"List the glossaries a genre resolves","description":"Returns which glossaries apply to a genre, how many entries each one resolves and which cascade level (genre, domain or language-wide) those entries come from. Glossary contents are not exposed.","operationId":"get_genre_glossaries_v1_text_types__domain_slug___text_type_slug__glossaries_get","parameters":[{"name":"domain_slug","in":"path","required":true,"schema":{"type":"string","title":"Domain Slug"},"description":"Text domain of a genre, as listed by GET /v1/text-types."},{"name":"text_type_slug","in":"path","required":true,"schema":{"type":"string","title":"Text Type Slug"},"description":"Text type (genre) inside domain_slug, as listed by GET /v1/text-types."},{"name":"language","in":"query","required":false,"schema":{"type":"string","default":"es","title":"Language"},"description":"Catalog language: es, ca or en."}],"responses":{"200":{"description":"Glossary availability for the genre.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenreGlossaries"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"401":{"description":"Missing, invalid, inactive or revoked API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"404":{"description":"Genre not found for this language.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}},"422":{"description":"Unsupported language.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ApiProblem"}}},"headers":{"X-Request-ID":{"description":"Identifier to retain for support and tracing.","schema":{"type":"string","example":"req_4adf8f2c"}}}}},"security":[{"ApiKey":[]}]}}},"components":{"schemas":{"ApiProblem":{"properties":{"type":{"type":"string","title":"Type","description":"URI reference identifying the problem type."},"title":{"type":"string","title":"Title","description":"Short, stable problem title."},"status":{"type":"integer","title":"Status","description":"HTTP status code."},"detail":{"type":"string","title":"Detail","description":"Human-readable explanation for this occurrence."},"instance":{"type":"string","title":"Instance","description":"Request path for this occurrence."},"code":{"type":"string","title":"Code","description":"Stable machine-readable arText error code."},"request_id":{"type":"string","title":"Request Id","description":"Identifier to retain for support and tracing."},"retry_after_seconds":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Retry After Seconds"},"errors":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Errors"}},"type":"object","required":["type","title","status","detail","instance","code","request_id"],"title":"ApiProblem"},"Genre":{"properties":{"domain":{"$ref":"#/components/schemas/TextDomain"},"text_type":{"$ref":"#/components/schemas/TextType"}},"type":"object","required":["domain","text_type"],"title":"Genre","description":"Genre the report was analyzed with.\n\nIt names both coordinates because a text type slug is only unique inside\nits domain, and the pair is what determined the rules, the thresholds and\nthe glossaries used. Resubmitting these two slugs reproduces the analysis."},"GenreGlossaries":{"properties":{"language":{"type":"string","title":"Language"},"domain_slug":{"type":"string","title":"Domain Slug"},"text_type_slug":{"type":"string","title":"Text Type Slug"},"glossaries":{"items":{"$ref":"#/components/schemas/Glossary"},"type":"array","title":"Glossaries"}},"type":"object","required":["language","domain_slug","text_type_slug","glossaries"],"title":"GenreGlossaries"},"Glossary":{"properties":{"key":{"type":"string","title":"Key","description":"Stable glossary identifier."},"title":{"type":"string","title":"Title","description":"Human-readable glossary name."},"entry_count":{"type":"integer","title":"Entry Count","description":"Entries resolved for this genre, active or not."},"active_entry_count":{"type":"integer","title":"Active Entry Count","description":"Entries actually applied; the rest are deliberately disabled."},"source_level":{"type":"string","enum":["text_type","domain","global"],"title":"Source Level","description":"Cascade level the entries come from. The most specific level holding rows wins: the genre's own list, the domain's, or the language-wide one."},"inherited":{"type":"boolean","title":"Inherited","description":"True when source_level is less specific than the requested genre."},"used_by_metrics":{"items":{"type":"string"},"type":"array","title":"Used By Metrics","description":"metric_id values enabled for this genre that read this glossary."}},"type":"object","required":["key","title","entry_count","active_entry_count","source_level","inherited"],"title":"Glossary","description":"Disponibilidad de un glosario para un género. Sin contenido."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Measurement":{"properties":{"metric_id":{"type":"string","title":"Metric Id","description":"Stable metric identifier documented by GET /v1/metrics/{metric_id}."},"value":{"anyOf":[{"type":"integer"},{"type":"number"}],"title":"Value","description":"Calculated numeric value."},"unit":{"type":"string","title":"Unit","description":"Semantic unit defined by the metric catalog."},"implementation_version":{"type":"string","title":"Implementation Version","description":"Calculator version used for this report."}},"type":"object","required":["metric_id","value","unit","implementation_version"],"title":"Measurement"},"Metric":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","enum":["measurement","rule"],"title":"Kind"},"analysis_language":{"type":"string","title":"Analysis Language"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether reports for analysis_language currently evaluate this variant."},"genre_scope":{"anyOf":[{"$ref":"#/components/schemas/MetricGenreScope"},{"type":"null"}],"description":"Genre-specific status, present only when the request selects a genre."},"available":{"type":"boolean","title":"Available","description":"False when the variant can never produce output for analysis_language with the current linguistic runtime; see availability_note.","default":true},"availability_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Availability Note","description":"Localized explanation present only when available is false."},"implementation_version":{"type":"string","title":"Implementation Version"},"category":{"type":"string","title":"Category"},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit"},"documentation_language":{"type":"string","title":"Documentation Language","description":"Language actually used by the returned documentation."},"documentation":{"$ref":"#/components/schemas/MetricDocumentation"},"references":{"items":{"type":"string"},"type":"array","title":"References"},"example":{"additionalProperties":true,"type":"object","title":"Example","description":"Example conforming to the public report contract."}},"type":"object","required":["id","kind","analysis_language","enabled","implementation_version","category","documentation_language","documentation"],"title":"Metric"},"MetricCatalog":{"properties":{"genre":{"anyOf":[{"$ref":"#/components/schemas/Genre"},{"type":"null"}],"description":"Genre the genre_scope of every metric refers to, when one was requested."},"metrics":{"items":{"$ref":"#/components/schemas/Metric"},"type":"array","title":"Metrics"}},"type":"object","required":["metrics"],"title":"MetricCatalog"},"MetricDocumentation":{"properties":{"title":{"type":"string","title":"Title","default":""},"summary":{"type":"string","title":"Summary","default":""},"methodology":{"type":"string","title":"Methodology","default":""},"limitations":{"type":"string","title":"Limitations","default":""},"interpretation":{"type":"string","title":"Interpretation","default":""}},"type":"object","title":"MetricDocumentation"},"MetricGenreScope":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether reports for this genre evaluate the metric."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Thresholds this genre configures for the metric; empty means the defaults."}},"type":"object","required":["enabled"],"title":"MetricGenreScope","description":"How a metric behaves for one genre.\n\nPresent only when the request narrows the catalog with domain_slug and\ntext_type_slug. `enabled` is the intersection of the language variant and\nthe genre configuration: a variant enabled for the language still does not\nrun unless the genre opts into it."},"Occurrence":{"properties":{"id":{"type":"string","title":"Id","description":"Identifier unique within its parent recommendation."},"text":{"type":"string","title":"Text","description":"Fragment identified by the metric."},"paragraph_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Paragraph Index","description":"Zero-based paragraph index when available."},"sentence_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Sentence Index","description":"Zero-based sentence index when available."},"start":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Start","description":"Inclusive offset in the original input text."},"end":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"End","description":"Exclusive offset in the original input text."},"spans":{"items":{"$ref":"#/components/schemas/TextSpanBase"},"type":"array","title":"Spans","description":"Every appearance of this occurrence in the text, in reading order, when the metric identifies a unit rather than a single fragment: a repeated word or connector, or a glossary entry found in several places. The first entry is the one published in start/end. Empty when the occurrence appears only once."},"connectors":{"items":{"$ref":"#/components/schemas/SentenceConnectorBase"},"type":"array","title":"Connectors","description":"Discourse connectors found inside this occurrence, in reading order, with the alternatives that would fit if the sentence were split there. Only long sentences fill this; every other metric leaves it empty."},"replacement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Replacement","description":"Preferred direct replacement, or null when none is available."},"alternatives":{"items":{"type":"string"},"type":"array","title":"Alternatives","description":"Additional reviewed replacement candidates, excluding replacement."},"edits":{"items":{"$ref":"#/components/schemas/TextEdit"},"type":"array","title":"Edits","description":"Validated edits for the preferred deterministic change, ordered from the highest offset to the lowest."}},"type":"object","required":["id","text"],"title":"Occurrence"},"PublicPhraseology":{"properties":{"value":{"type":"string","title":"Value","description":"Suggested wording for the content it belongs to."}},"type":"object","required":["value"],"title":"PublicPhraseology"},"PublicTextContent":{"properties":{"name":{"type":"string","title":"Name","description":"Content the section is expected to cover."},"occurrence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Occurrence","description":"How many times the content usually appears in the section, when the catalog states it."},"phraseology":{"items":{"$ref":"#/components/schemas/PublicPhraseology"},"type":"array","title":"Phraseology","description":"Wordings the editorial team proposes for this content."}},"type":"object","required":["name"],"title":"PublicTextContent"},"PublicTextDomain":{"properties":{"slug":{"type":"string","title":"Slug","description":"Domain identifier. Send it as domain_slug in POST /v1/reports."},"name":{"type":"string","title":"Name","description":"Domain name in the requested language."},"clear_language":{"type":"boolean","title":"Clear Language","description":"True when the whole domain is plain-language.","default":false},"text_types":{"items":{"$ref":"#/components/schemas/PublicTextType"},"type":"array","title":"Text Types","description":"Genres available inside this domain."}},"type":"object","required":["slug","name"],"title":"PublicTextDomain"},"PublicTextSection":{"properties":{"name":{"type":"string","title":"Name","description":"Section of the document, in reading order."},"contents":{"items":{"$ref":"#/components/schemas/PublicTextContent"},"type":"array","title":"Contents"}},"type":"object","required":["name"],"title":"PublicTextSection"},"PublicTextType":{"properties":{"slug":{"type":"string","title":"Slug","description":"Genre identifier. Send it as text_type_slug in POST /v1/reports."},"name":{"type":"string","title":"Name","description":"Genre name in the requested language."},"clear_language":{"type":"boolean","title":"Clear Language","description":"True when the genre is written in plain language for the general public.","default":false}},"type":"object","required":["slug","name"],"title":"PublicTextType"},"PublicTextTypeCatalog":{"properties":{"language":{"type":"string","title":"Language","description":"Language the catalog is returned in."},"domains":{"items":{"$ref":"#/components/schemas/PublicTextDomain"},"type":"array","title":"Domains"}},"type":"object","required":["language","domains"],"title":"PublicTextTypeCatalog"},"PublicTextTypeDetail":{"properties":{"language":{"type":"string","title":"Language"},"domain":{"$ref":"#/components/schemas/PublicTextDomain","description":"Domain the genre belongs to, without its sibling genres."},"text_type":{"$ref":"#/components/schemas/PublicTextType"},"structure":{"items":{"$ref":"#/components/schemas/PublicTextSection"},"type":"array","title":"Structure","description":"Sections and contents the editorial team recommends for this genre."},"report_defaults":{"$ref":"#/components/schemas/ReportDefaults","description":"Exact payload fragment to forward to POST /v1/reports."}},"type":"object","required":["language","domain","text_type","report_defaults"],"title":"PublicTextTypeDetail"},"Recommendation":{"properties":{"id":{"type":"string","title":"Id","description":"Identifier unique within this report."},"metric_id":{"type":"string","title":"Metric Id","description":"Stable metric identifier documented by GET /v1/metrics/{metric_id}."},"implementation_version":{"type":"string","title":"Implementation Version","description":"Detector version used for this report."},"metric_title":{"type":"string","title":"Metric Title","description":"Localized human-readable metric title captured for this report."},"category":{"type":"string","title":"Category","description":"Stable linguistic category used by the metric catalog."},"summary":{"type":"string","title":"Summary","description":"Explanation of the issue found in this report."},"explanation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Explanation","description":"Optional extended contextual explanation."},"guidance":{"items":{"type":"string"},"type":"array","title":"Guidance","description":"Human-readable review guidance written by the editorial team."},"examples":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Examples","description":"Optional worked examples illustrating the recommendation, as plain text with meaningful line breaks. Editorial copy attached to the metric: it never quotes the analysed text and does not vary between reports. Null when the metric's copy has no examples."},"occurrences":{"items":{"$ref":"#/components/schemas/Occurrence"},"type":"array","title":"Occurrences","description":"Concrete appearances of this issue in the submitted text, in reading order: sorted by `start`, with occurrences that could not be located last. The order of the recommendations themselves is linguistic, not positional."}},"type":"object","required":["id","metric_id","implementation_version","metric_title","category","summary"],"title":"Recommendation","description":"Un problema encontrado por una regla, con todas sus apariciones.\n\nEl contrato público la llama `Recommendation` y no `Suggestion` porque es\nel término que usa el producto de cara a quien lo lee. El modelo interno\n(`app.domain.models.Suggestion`) conserva su nombre: el renombrado es de la\nproyección pública, no de la nomenclatura del motor."},"Report":{"properties":{"report_id":{"type":"string","title":"Report Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"status":{"$ref":"#/components/schemas/ReportStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"UTC acceptance timestamp."},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"UTC last-transition timestamp."},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"UTC processing start timestamp."},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At","description":"UTC terminal timestamp."},"position_in_queue":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position In Queue","description":"Approximate queue position while waiting."},"phase":{"anyOf":[{"$ref":"#/components/schemas/ReportPhase"},{"type":"null"}],"description":"Pipeline stage while the report is running: `analyzing` (linguistic analysis, by far the longest part) or `building` (applying rules). Not a percentage: it marks what the server is doing, not how far along it is."},"estimated_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Seconds","description":"Rough estimate of the analysis duration, derived from the text length when the report was accepted. Indicative only: it excludes queue wait and does not react to server load."},"result":{"anyOf":[{"$ref":"#/components/schemas/ReportResult"},{"type":"null"}],"description":"Analysis result, present only when completed."},"error":{"anyOf":[{"$ref":"#/components/schemas/ReportFailure"},{"type":"null"}],"description":"Safe failure information, present only when failed."}},"type":"object","required":["report_id","status"],"title":"Report"},"ReportAccepted":{"properties":{"report_id":{"type":"string","title":"Report Id","description":"Unique arText report identifier."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Caller-provided correlation identifier."},"status":{"$ref":"#/components/schemas/ReportStatus","description":"Initial queue status; always queued for a newly accepted report."},"status_url":{"type":"string","title":"Status Url","description":"Relative URL of the report status resource."},"events_url":{"type":"string","title":"Events Url","description":"Relative URL of the optional SSE status stream."},"estimated_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Seconds","description":"Rough estimate of how long the analysis will take, derived from the length of the submitted text. Indicative only: it excludes queue wait and does not react to server load."}},"type":"object","required":["report_id","status","status_url","events_url"],"title":"ReportAccepted"},"ReportDefaults":{"properties":{"language":{"type":"string","title":"Language","description":"Analysis language the slugs below belong to."},"domain_slug":{"type":"string","title":"Domain Slug","description":"Value for the domain_slug field of a report request."},"text_type_slug":{"type":"string","title":"Text Type Slug","description":"Value for the text_type_slug field of a report request."}},"type":"object","required":["language","domain_slug","text_type_slug"],"title":"ReportDefaults","description":"Exact fields to forward to POST /v1/reports for this genre."},"ReportFailure":{"properties":{"code":{"type":"string","const":"analysis_failed","title":"Code","default":"analysis_failed"},"message":{"type":"string","title":"Message","description":"Safe failure description suitable for API clients."}},"type":"object","required":["message"],"title":"ReportFailure"},"ReportPhase":{"type":"string","enum":["analyzing","building"],"title":"ReportPhase","description":"Punto de la tubería mientras el estado es `running`.\n\nNo es un porcentaje y no debe presentarse como tal: `analyzing` se lleva el\n94 % del tiempo (una única llamada a FreeLing, que no informa de su avance),\nasí que una barra basada en esto se quedaría clavada casi todo el rato. Sirve\npara decir qué está pasando y que sigue vivo; para «cuánto queda» está\n`estimated_seconds`."},"ReportRequest":{"properties":{"language":{"type":"string","title":"Language","description":"Language used for analysis: es, ca or en.","default":"es"},"domain_slug":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Domain Slug","description":"Optional text domain (ámbito) the genre belongs to, as listed by GET /v1/text-types. Omit it to name the genre by text_type_slug alone; send it only to disambiguate after a 422 with code ambiguous-genre.","examples":["lenguaje-claro"]},"text_type_slug":{"type":"string","minLength":1,"title":"Text Type Slug","description":"Text type (genre) inside domain_slug, as listed by GET /v1/text-types. The pair selects the rules, thresholds and glossaries applied to the text.","examples":["texto-juridico-administrativo-dirigido-a-la-ciudadania"]},"text":{"type":"string","minLength":1,"title":"Text","description":"Plain text to analyze."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Optional caller-owned correlation identifier returned unchanged."}},"additionalProperties":false,"type":"object","required":["text_type_slug","text"],"title":"ReportRequest","description":"Stable input accepted from API integrations."},"ReportResult":{"properties":{"schema_version":{"type":"string","const":"2.0","title":"Schema Version","description":"Version of this public result contract.","default":"2.0"},"language":{"type":"string","title":"Language","description":"Language actually used for analysis."},"genre":{"$ref":"#/components/schemas/Genre"},"measurements":{"items":{"$ref":"#/components/schemas/Measurement"},"type":"array","title":"Measurements"},"recommendations":{"items":{"$ref":"#/components/schemas/Recommendation"},"type":"array","title":"Recommendations","description":"Issues found by the suggestion rules, one item per rule that matched."},"applied_thresholds":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Applied Thresholds","description":"Thresholds each rule actually read while evaluating this report, keyed by metric_id. Rules disabled for the genre never run and are absent. Values are the effective ones, whether configured for the genre or inherited from the language defaults."},"offset_unit":{"type":"string","const":"unicode_code_points","title":"Offset Unit","description":"Unit used by every start and end offset in the result.","default":"unicode_code_points"}},"type":"object","required":["language","genre"],"title":"ReportResult"},"ReportStatus":{"type":"string","enum":["queued","running","completed","failed"],"title":"ReportStatus"},"SentenceConnectorBase":{"properties":{"start":{"type":"integer","minimum":0.0,"title":"Start","description":"Inclusive offset in the original input text."},"end":{"type":"integer","minimum":0.0,"title":"End","description":"Exclusive offset in the original input text."},"text":{"type":"string","title":"Text","description":"Fragment found in this range."},"relation":{"type":"string","title":"Relation","description":"Semantic relation the connector expresses (cause, antithesis, concession…), as named by the discourse connector model."},"scope":{"type":"string","pattern":"^(intra|inter)$","title":"Scope","description":"Whether the connector joins clauses inside one sentence (intra) or links separate sentences (inter). The ones found inside a long sentence are intra; their alternatives are inter, because splitting turns the parts into sentences."},"alternatives":{"items":{"type":"string"},"type":"array","title":"Alternatives","description":"Inter-sentence connectors of the same relation, capitalised and in alphabetical order: what to write instead if the sentence is split at this point."}},"type":"object","required":["start","end","text","relation","scope"],"title":"SentenceConnectorBase","description":"A discourse connector found inside a long sentence, with its position.\n\nIt is not another appearance of the occurrence —that is what `spans` is for—\nbut something that lives inside it: the connector joining two clauses of a\nsentence the report proposes to split. Splitting turns those clauses into\nseparate sentences, so the connector would have to change with them, and\nthat is why it travels with its own alternatives."},"TextDomain":{"properties":{"slug":{"type":"string","title":"Slug","description":"Stable text domain identifier accepted by POST /v1/reports."},"name":{"type":"string","title":"Name","description":"Localized text domain name."}},"type":"object","required":["slug","name"],"title":"TextDomain"},"TextEdit":{"properties":{"start":{"type":"integer","minimum":0.0,"title":"Start","description":"Inclusive offset in the original input text."},"end":{"type":"integer","minimum":0.0,"title":"End","description":"Exclusive offset in the original input text."},"text":{"type":"string","title":"Text","description":"Text to splice into the range. Empty text deletes it; start == end inserts text."}},"type":"object","required":["start","end","text"],"title":"TextEdit"},"TextSpanBase":{"properties":{"start":{"type":"integer","minimum":0.0,"title":"Start","description":"Inclusive offset in the original input text."},"end":{"type":"integer","minimum":0.0,"title":"End","description":"Exclusive offset in the original input text."},"text":{"type":"string","title":"Text","description":"Fragment found in this range."}},"type":"object","required":["start","end","text"],"title":"TextSpanBase","description":"Un tramo del texto original, con lo que hay escrito en él."},"TextType":{"properties":{"slug":{"type":"string","title":"Slug","description":"Stable text type identifier accepted by POST /v1/reports."},"name":{"type":"string","title":"Name","description":"Localized text type name."}},"type":"object","required":["slug","name"],"title":"TextType"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"art_live_…"}}},"servers":[{"url":"http://demo.sistema-artext.com"}],"x-artext-metrics":[{"id":"average-sentence-words","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"readability","unit":"words","documentation_language":"en","documentation":{"title":"Average sentence words","summary":"Average number of words per sentence.","methodology":"Divides word-count by sentence-count and rounds to two decimals; returns 0 when no sentences exist.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `words`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"average-sentence-words","value":24,"unit":"words","implementation_version":"1.0"}},{"id":"maximum-paragraph-words","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"readability","unit":"words","documentation_language":"en","documentation":{"title":"Maximum paragraph words","summary":"Word length of the longest paragraph.","methodology":"Takes the maximum paragraph word_count; returns 0 when no paragraph exists.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `words`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"maximum-paragraph-words","value":24,"unit":"words","implementation_version":"1.0"}},{"id":"maximum-sentence-words","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"readability","unit":"words","documentation_language":"en","documentation":{"title":"Maximum sentence words","summary":"Word length of the longest sentence.","methodology":"Takes the maximum sentence word_count; returns 0 when no sentence exists.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `words`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"maximum-sentence-words","value":24,"unit":"words","implementation_version":"1.0"}},{"id":"paragraph-count","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"document","unit":"count","documentation_language":"en","documentation":{"title":"Paragraph count","summary":"Number of non-empty paragraphs detected.","methodology":"Segments paragraph text and counts units containing analyzable content.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `count`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"paragraph-count","value":24,"unit":"count","implementation_version":"1.0"}},{"id":"segment-count","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"document","unit":"count","documentation_language":"en","documentation":{"title":"Segment count","summary":"Number of recognized intra-sentence segments.","methodology":"Sums the syntactic/discourse segments produced by DiSeg for all sentences.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `count`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"segment-count","value":24,"unit":"count","implementation_version":"1.0"}},{"id":"sentence-count","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"document","unit":"count","documentation_language":"en","documentation":{"title":"Sentence count","summary":"Number of sentences segmented by the analyzer.","methodology":"Counts sentences recognized by FreeLing/DiSeg, rather than simply counting full stops.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `count`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"sentence-count","value":24,"unit":"count","implementation_version":"1.0"}},{"id":"token-count","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"document","unit":"count","documentation_language":"en","documentation":{"title":"Token count","summary":"Number of lexical units produced by the analyzer.","methodology":"Counts FreeLing/DiSeg tokens; punctuation and contractions may make this differ from word-count.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `count`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"token-count","value":24,"unit":"count","implementation_version":"1.0"}},{"id":"word-count","kind":"measurement","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.0","category":"document","unit":"count","documentation_language":"en","documentation":{"title":"Word count","summary":"Number of words in the normalized text.","methodology":"Normalizes whitespace and counts every non-empty space-separated item.","limitations":"The value depends on analyzer normalization and segmentation; it should be interpreted together with the other metrics and the text genre.","interpretation":"`value` contains the numeric result and `unit` is `count`. Absence means the variant was disabled."},"references":[],"example":{"metric_id":"word-count","value":24,"unit":"count","implementation_version":"1.0"}},{"id":"acronym-consistency","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Use acronyms consistently","summary":"Detects repeated use of an expanded term after its acronym has already been introduced.","methodology":"After identifying an acronym and expansion at first occurrence, checks whether the full expanded term appears again later.","limitations":"This is a review signal, not an automatic correction or a complete assessment of text quality.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"acronym-consistency","implementation_version":"1.2","metric_title":"Use acronyms consistently","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"acronym-introduction","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Introduce acronyms","summary":"Detects the first occurrence of uppercase acronyms that do not appear with an expanded form.","methodology":"Extracts uppercase candidates, ignores stopwords, keeps the first occurrence and searches immediately before or after it for an expanded form.","limitations":"This is a review signal, not an automatic correction or a complete assessment of text quality.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"acronym-introduction","implementation_version":"1.2","metric_title":"Introduce acronyms","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"archaic-verb-forms","kind":"rule","analysis_language":"en","enabled":false,"genre_scope":null,"available":false,"availability_note":"Not applicable to this language: the linguistic phenomenon it detects does not exist in its grammar.","implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Remove archaic verb forms","summary":"Detects forms tagged as future subjunctive, generally considered archaic in current usage.","methodology":"Looks for FreeLing tags starting with VSSF, VASF or VMSF, including vaux-cat, while excluding auxiliaries.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"archaic-verb-forms","implementation_version":"1.2","metric_title":"Remove archaic verb forms","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"connector-variety","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Vary connectors","summary":"Detects discourse connectors repeated above the threshold and offers alternatives expressing the same relation.","methodology":"Counts disc_mk XML nodes, normalizes their forms and flags model connectors whose frequency exceeds n_variacion_conectores. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"connector-variety","implementation_version":"1.2","metric_title":"Vary connectors","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"definition-usage","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Use of definitions","summary":"Compares the number of definition markers in the text with the genre's reference value.","methodology":"Counts the text's definition markers and compares them with the genre's reference value; a mode parameter decides whether the notice is raised for too many or too few. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"definition-usage","implementation_version":"1.2","metric_title":"Use of definitions","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"difficult-expressions","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Replace difficult expressions","summary":"Finds difficult-to-understand expressions in the catalog and returns alternatives when available.","methodology":"Matches complete expressions directly against the text, case- and accent-insensitively, while respecting word boundaries. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"difficult-expressions","implementation_version":"1.2","metric_title":"Replace difficult expressions","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"gerunds","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Review gerunds","summary":"Finds standalone gerund forms worth reviewing, excluding recognized auxiliaries and compound constructions.","methodology":"Uses gerund verb tags (VBG in EN) and auxiliary attributes to exclude already recognized periphrastic forms.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"gerunds","implementation_version":"1.2","metric_title":"Review gerunds","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"imprecise-words","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Replace ambiguous words","summary":"Finds imprecise words or sequences listed in the editable catalog and returns their alternatives.","methodology":"Normalizes token lemmas and matches complete sequences from the active catalog; returns the term, alternative and text offsets when available. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"imprecise-words","implementation_version":"1.2","metric_title":"Replace ambiguous words","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"list-usage","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Include lists","summary":"Warns, without pointing at any fragment, when no structured list has been recognized in the text.","methodology":"Returns a single document-level suggestion, with no text offsets, when has_list is false; has_list is derived from text and structured HTML.","limitations":"Results depend on plain-text segmentation and recognizable structural markers; the signal alone does not imply a writing error.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"list-usage","implementation_version":"1.2","metric_title":"Include lists","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"long-paragraphs","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Review long paragraphs","summary":"Flags paragraphs exceeding the word limit configured for the text genre.","methodology":"Compares each paragraph word_count with the genre's n_palabras_por_parrafo value; only strictly greater values are reported.","limitations":"Results depend on plain-text segmentation and recognizable structural markers; the signal alone does not imply a writing error.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"long-paragraphs","implementation_version":"1.2","metric_title":"Review long paragraphs","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"long-sentences","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Review long sentences","summary":"Flags sentences exceeding the word limit configured for the text genre and, when the sentence can be divided, the points where it might be split.","methodology":"Selects sentences longer than n_division_oraciones words; for those made of several segments it also returns segments, connectors and possible split boundaries.","limitations":"Results depend on plain-text segmentation and recognizable structural markers; the signal alone does not imply a writing error.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"long-sentences","implementation_version":"1.2","metric_title":"Review long sentences","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"long-words","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Review long words","summary":"Finds long words in the editorial catalog and proposes shorter alternatives when available.","methodology":"Normalizes token lemmas and matches complete sequences from the active catalog; returns the term, alternative and text offsets when available. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"long-words","implementation_version":"1.2","metric_title":"Review long words","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"missing-definitions","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Lack of definitions","summary":"Warns when the text contains no definition marker although the genre usually includes definitions.","methodology":"Looks for definition markers (a defining verb next to a context word) and returns a document-level notice when the text contains none. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"missing-definitions","implementation_version":"1.2","metric_title":"Lack of definitions","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"negative-formulations","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Rephrase negative ideas","summary":"Finds catalogued negative words or sequences to encourage direct affirmative wording.","methodology":"Normalizes token lemmas and matches complete sequences from the active catalog; returns the term, alternative and text offsets when available. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"negative-formulations","implementation_version":"1.2","metric_title":"Rephrase negative ideas","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"paragraph-initial-connectors","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Introduce connectors at the beginning of paragraphs","summary":"Detects paragraphs that do not begin with a recognized discourse connector.","methodology":"Skips empty paragraphs and those with has_initial_connector; returns the remaining paragraphs plus editable connector groups. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"paragraph-initial-connectors","implementation_version":"1.2","metric_title":"Introduce connectors at the beginning of paragraphs","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"participles","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Review participles","summary":"Finds standalone participles while excluding recognized compound tenses and passive constructions.","methodology":"Uses participle tags (VBN in EN) and excludes auxiliaries, compound tenses, passive forms and auxiliary lemmas.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"participles","implementation_version":"1.2","metric_title":"Review participles","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"passive-voice","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Use of passive voice","summary":"Finds constructions identified by morphosyntactic analysis as possible passive voice.","methodology":"For ES/CA it uses verb_pass or ger_pas nodes, then ser/ésser + participle as fallback. For EN it looks for be + VBN in vb_chunk and then tokens.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"passive-voice","implementation_version":"1.2","metric_title":"Use of passive voice","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"redundant-expressions","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Remove redundant expressions","summary":"Finds catalogued redundant expressions so they can be removed or rephrased.","methodology":"Matches complete expressions directly against the text, case- and accent-insensitively, while respecting word boundaries. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"redundant-expressions","implementation_version":"1.2","metric_title":"Remove redundant expressions","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"single-sentence-paragraphs","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"discourse","unit":null,"documentation_language":"en","documentation":{"title":"Review sentence-paragraphs","summary":"Finds paragraphs made up of a single sentence so they can be reviewed for excessive density or insufficient development.","methodology":"Selects every paragraph containing exactly one closed sentence and reaching the genre's minimum word count; fragments without final punctuation (heading lines) do not count as a sentence.","limitations":"Results depend on plain-text segmentation and recognizable structural markers; the signal alone does not imply a writing error.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"single-sentence-paragraphs","implementation_version":"1.2","metric_title":"Review sentence-paragraphs","category":"discourse","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"subjectivity-markers","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Use of subjectivity markers","summary":"Combines lexical items, sequences, morphological categories and exclamations to find possible subjectivity markers.","methodology":"Combines simple lemma matches, compound sequences, configured morphological categories and exclamation spans, then removes duplicates.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"subjectivity-markers","implementation_version":"1.2","metric_title":"Use of subjectivity markers","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"transparent-terms","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Use more transparent terms","summary":"Detects catalogued difficult terms and proposes more transparent wording.","methodology":"Normalizes token lemmas and matches complete sequences from the active catalog; returns the term, alternative and text offsets when available. Detection uses the active editorial catalog for the language.","limitations":"Coverage depends on the active language catalog; every match still requires contextual review.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"transparent-terms","implementation_version":"1.2","metric_title":"Use more transparent terms","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"verb-person-consistency","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Use verbs consistently","summary":"Warns when first-person singular and plural verb forms coexist in the same document.","methodology":"Extracts person and number from verb tags; returns a result only when both at least one 1S and one 1P form occur.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"verb-person-consistency","implementation_version":"1.2","metric_title":"Use verbs consistently","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"verbal-nominalizations","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"morphosyntax","unit":null,"documentation_language":"en","documentation":{"title":"Review verbal nominalizations","summary":"Detects nouns matching productive verbal-nominalization patterns and excludes catalogued exceptions.","methodology":"Filters lowercase nouns ending in -ción/-ciones for ES, -ció/-cions for CA and -tion/-tions for EN; catalogued exceptions are removed.","limitations":"Results depend on FreeLing/DiSeg tagging and may contain false positives or miss ambiguous constructions.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"verbal-nominalizations","implementation_version":"1.2","metric_title":"Review verbal nominalizations","category":"morphosyntax","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}},{"id":"word-repetition","kind":"rule","analysis_language":"en","enabled":true,"genre_scope":null,"available":true,"availability_note":null,"implementation_version":"1.2","category":"lexical","unit":null,"documentation_language":"en","documentation":{"title":"Review repeated words","summary":"Flags content words repeated in the text above the threshold configured for the genre.","methodology":"Counts how often each lemma occurs, excluding auxiliary and copular lemmas, and flags noun, adjective, adverb or verb lemmas whose frequency exceeds the genre threshold.","limitations":"This is a review signal, not an automatic correction or a complete assessment of text quality.","interpretation":"Each `occurrences` item identifies an affected span. `start` is inclusive and `end` exclusive, both measured in Unicode code points over the submitted text. `replacement`, `alternatives` and `edits` contain values only when a safe change is available. Absence means no match was found, not necessarily that the text is correct."},"references":[],"example":{"id":"rec_1","metric_id":"word-repetition","implementation_version":"1.2","metric_title":"Review repeated words","category":"lexical","summary":"Example detection returned by this metric.","explanation":null,"guidance":["Review the detected fragment in context."],"examples":null,"occurrences":[{"id":"occ_1","text":"example","paragraph_index":0,"sentence_index":0,"start":18,"end":25,"spans":[],"connectors":[],"replacement":null,"alternatives":[],"edits":[]}]}}],"x-artext-guides":{"language":"en","examples":{"curl":"# Keep the key out of source code and load it from the environment.\n# Requires curl and jq.\n# export ARTEXT_API_KEY='art_live_...'\nset -euo pipefail\nAPI_BASE='http://demo.sistema-artext.com'\nIDEMPOTENCY_KEY=\"report-$(date +%s)-$RANDOM\"\nheaders_file=$(mktemp)\ntrap 'rm -f \"$headers_file\"' EXIT\n\n# Create the report once. Idempotency-Key makes a POST retry safe after a network failure.\ncreated=$(curl --fail-with-body --silent --show-error --dump-header \"$headers_file\" \\\n  --request POST \"$API_BASE/v1/reports\" \\\n  --header \"Authorization: Bearer $ARTEXT_API_KEY\" \\\n  --header \"Idempotency-Key: $IDEMPOTENCY_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --data-binary '{\"language\":\"en\",\"domain_slug\":\"plain-language\",\"text_type_slug\":\"legal-administrative-text-addressed-to-citizens\",\"text\":\"The application will be reviewed at the present time.\",\"external_id\":\"document-123\"}')\n\n# status_url is relative; resolve it against API_BASE.\nstatus_url=\"$API_BASE$(jq -r '.status_url' <<<\"$created\")\"\nretry_after=$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub(\"\\r\", \"\", $2); print $2 }' \"$headers_file\" | tail -1)\nretry_after=${retry_after:-2}\n\n# Poll the same report while honoring Retry-After and an overall deadline.\nfor attempt in $(seq 1 90); do\n  sleep \"$retry_after\"\n  report=$(curl --fail-with-body --silent --show-error --dump-header \"$headers_file\" \\\n    --header \"Authorization: Bearer $ARTEXT_API_KEY\" \\\n    \"$status_url\")\n  status=$(jq -r '.status' <<<\"$report\")\n  case \"$status\" in\n    completed) break ;;\n    failed) jq -r '.error.message // \"Analysis failed\"' <<<\"$report\" >&2; exit 1 ;;\n    queued|running)\n      retry_after=$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub(\"\\r\", \"\", $2); print $2 }' \"$headers_file\" | tail -1)\n      retry_after=${retry_after:-2}\n      ;;\n    *) echo \"Unknown status: $status\" >&2; exit 1 ;;\n  esac\ndone\n\n# Process the result only after completed.\ntest \"$status\" = completed || { echo 'Polling timed out' >&2; exit 1; }\njq '.result.measurements, .result.recommendations' <<<\"$report\"","python":"import os\nimport time\nimport uuid\nfrom urllib.parse import urljoin\n\nimport requests\n\n# Keep the key out of source code and load it from the environment.\nAPI_BASE = 'http://demo.sistema-artext.com'\nAPI_KEY = os.environ[\"ARTEXT_API_KEY\"]\nHEADERS = {\"Authorization\": f\"Bearer {API_KEY}\"}\npayload = {'language': 'en', 'domain_slug': 'plain-language', 'text_type_slug': 'legal-administrative-text-addressed-to-citizens', 'text': 'The application will be reviewed at the present time.', 'external_id': 'document-123'}\n\n# Create the report once. Idempotency-Key makes a POST retry safe after a network failure.\nresponse = requests.post(\n    f\"{API_BASE}/v1/reports\",\n    headers={**HEADERS, \"Idempotency-Key\": str(uuid.uuid4())},\n    json=payload,\n    timeout=30,\n)\nresponse.raise_for_status()\ncreated = response.json()\n# status_url is relative; resolve it against API_BASE.\nstatus_url = urljoin(f\"{API_BASE}/\", created[\"status_url\"])\nretry_after = int(response.headers.get(\"Retry-After\", \"2\"))\n\n# Poll the same report while honoring Retry-After and an overall deadline.\ndeadline = time.monotonic() + 180\nwhile True:\n    time.sleep(retry_after)\n    response = requests.get(status_url, headers=HEADERS, timeout=30)\n    retry_after = int(response.headers.get(\"Retry-After\", \"2\"))\n    if response.status_code == 429:\n        continue\n    response.raise_for_status()\n    report = response.json()\n    status = report[\"status\"]\n    if status == \"completed\":\n        break\n    if status == \"failed\":\n        raise RuntimeError((report.get(\"error\") or {}).get(\"message\") or \"Analysis failed\")\n    if status not in {\"queued\", \"running\"}:\n        raise RuntimeError(f\"Unknown report status: {status}\")\n    if time.monotonic() >= deadline:\n        raise TimeoutError(\"Report polling timed out\")\n\n# Process the result only after completed.\nresult = report[\"result\"]\nfor measurement in result[\"measurements\"]:\n    print(measurement[\"metric_id\"], measurement[\"value\"], measurement[\"unit\"])\nfor index, recommendation in enumerate(result[\"recommendations\"]):\n    print(index, recommendation[\"metric_id\"], len(recommendation[\"occurrences\"]))","javascript":"// Node.js 18+. Run this code on a server; never expose the key in a browser.\nconst API_BASE = \"http://demo.sistema-artext.com\";\nconst API_KEY = process.env.ARTEXT_API_KEY;\nif (!API_KEY) throw new Error(\"ARTEXT_API_KEY is required\");\n\nconst headers = {\n  \"Authorization\": `Bearer ${API_KEY}`,\n  \"Content-Type\": \"application/json\"\n};\nconst api = async (path, options = {}) => {\n  const response = await fetch(new URL(path, `${API_BASE}/`), {\n    ...options,\n    headers: { ...headers, ...options.headers },\n    signal: AbortSignal.timeout(30_000)\n  });\n  if (!response.ok && response.status !== 429) {\n    throw new Error(`${response.status}: ${await response.text()}`);\n  }\n  return response;\n};\nconst delay = (milliseconds) =>\n  new Promise((resolve) => setTimeout(resolve, milliseconds));\n\n// Create the report once. Idempotency-Key makes a POST retry safe after a network failure.\nconst createdResponse = await api(\"v1/reports\", {\n  method: \"POST\",\n  headers: { \"Idempotency-Key\": crypto.randomUUID() },\n  body: JSON.stringify({\"language\":\"en\",\"domain_slug\":\"plain-language\",\"text_type_slug\":\"legal-administrative-text-addressed-to-citizens\",\"text\":\"The application will be reviewed at the present time.\",\"external_id\":\"document-123\"})\n});\nconst created = await createdResponse.json();\nconst deadline = Date.now() + 180_000;\nlet retryAfter = Number(createdResponse.headers.get(\"Retry-After\") || 2);\nlet report;\n\n// Poll the same report while honoring Retry-After and an overall deadline.\nwhile (Date.now() < deadline) {\n  await delay(retryAfter * 1_000);\n  const statusResponse = await api(created.status_url);\n  retryAfter = Number(statusResponse.headers.get(\"Retry-After\") || 2);\n  if (statusResponse.status === 429) continue;\n  report = await statusResponse.json();\n  if (report.status === \"completed\") break;\n  if (report.status === \"failed\") {\n    throw new Error(report.error?.message || \"Analysis failed\");\n  }\n  if (![\"queued\", \"running\"].includes(report.status)) {\n    throw new Error(`Unknown report status: ${report.status}`);\n  }\n}\nif (report?.status !== \"completed\") {\n  throw new Error(\"Report polling timed out\");\n}\n\n// Process the result only after completed.\nfor (const item of report.result.measurements) {\n  console.log(item.metric_id, item.value, item.unit);\n}\nfor (const [index, item] of report.result.recommendations.entries()) {\n  console.log(index, item.metric_id, item.occurrences.length);\n}"},"sdk":{"python":"# Install the official client: pip install artext\n# Run this code on a server; never expose the key in a browser.\nimport os\n\nfrom artext import Artext\n\nclient = Artext(\n    api_key=os.environ[\"ARTEXT_API_KEY\"],\n    base_url=\"http://demo.sistema-artext.com\",\n)\n\n# analyze creates the report, waits honoring Retry-After and returns the result.\n# A failed analysis raises an exception carrying the error detail.\nreport = client.analyze(\n    \"The application will be reviewed at the present time.\",\n    language=\"en\",\n    domain_slug=\"plain-language\",\n    text_type_slug=\"legal-administrative-text-addressed-to-citizens\",\n)\n\nprint(report.result.measurement(\"word-count\").value)\nfor recommendation in report.result.recommendations:\n    print(recommendation.metric_id, recommendation.summary)\n    for occurrence in recommendation.occurrences:\n        print(\"  \", occurrence.text, \"->\", occurrence.replacement)\n","javascript":"// Install the official client: npm install artext\n// Run this code on a server; never expose the key in a browser.\nimport { Artext } from \"artext\";\n\nconst client = new Artext({\n  apiKey: process.env.ARTEXT_API_KEY,\n  baseUrl: \"http://demo.sistema-artext.com\",\n});\n\n// analyze creates the report, waits honoring Retry-After and returns the result.\n// A failed analysis raises an exception carrying the error detail.\nconst report = await client.analyze({\n  text: \"The application will be reviewed at the present time.\",\n  language: \"en\",\n  domainSlug: \"plain-language\",\n  textTypeSlug: \"legal-administrative-text-addressed-to-citizens\",\n});\n\nfor (const recommendation of report.result.recommendations) {\n  console.log(recommendation.metric_id, recommendation.summary);\n  for (const occurrence of recommendation.occurrences) {\n    console.log(\"  \", occurrence.text, \"->\", occurrence.replacement);\n  }\n}\n"},"request":{"language":"en","domain_slug":"plain-language","text_type_slug":"legal-administrative-text-addressed-to-citizens","text":"The application will be reviewed at the present time.","external_id":"document-123"},"responses":{"queued":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"queued","status_url":"/v1/reports/rep_7f12a4c8","events_url":"/v1/reports/rep_7f12a4c8/events"},"completed":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"completed","position_in_queue":null,"result":{"schema_version":"2.0","offset_unit":"unicode_code_points","language":"en","genre":{"domain":{"slug":"plain-language","name":"Plain language"},"text_type":{"slug":"legal-administrative-text-addressed-to-citizens","name":"Legal-administrative text"}},"measurements":[{"metric_id":"word-count","value":12,"unit":"words","implementation_version":"1.0"}],"recommendations":[{"id":"rec_1","metric_id":"redundant-expressions","implementation_version":"1.0","metric_title":"Removal of redundant expressions","category":"lexical","summary":"Consider expressing this fragment more directly.","explanation":null,"occurrences":[{"id":"occ_1","text":"at the present time","paragraph_index":0,"sentence_index":0,"start":0,"end":19,"replacement":null,"alternatives":[],"edits":[{"start":0,"end":19,"text":""}]}],"guidance":["Remove the expression when the context allows it."]}]},"error":null},"failed":{"report_id":"rep_7f12a4c8","external_id":"document-123","status":"failed","position_in_queue":null,"result":null,"error":{"code":"analysis_failed","message":"The analysis could not be completed."}}}}}