> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signa.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Compare Marks

> Score one candidate against up to 10 specific trademark conflicts (alpha, internal testing)

<Warning>
  **Alpha, internal testing.** Compare is not open to customers yet, and the risk bands are
  still being calibrated. These endpoints run on internal and design-partner keys during internal testing and their
  request and response shapes can still change. Email
  [support@signa.so](mailto:support@signa.so) to join the early access list.
  Compare returns an informational comparison, not legal advice or a registrability
  determination.
</Warning>

## Overview

Screening **finds** possible conflicts for a candidate. Compare **scores** pairs
you already chose: one candidate against 1–10 register records or inline marks.
Each request-ordered result includes the same coarse risk-band vocabulary used
by served screening, plus numeric string, phonetic, and goods/services
measurements. Compare does not return an overall or confusion-probability score.

The endpoint is synchronous and deterministic within the reported rule,
mapping, resolver, and corpus versions. Each request is billed at **10 units**,
regardless of the number of conflicts (up to 10). Optionally supply an
`Idempotency-Key`; retrying the same body with the same key replays the
original response without billing the comparison again.

## Request

Supply the candidate either as an existing `trademark_id` or as an inline
`{ mark, use }` object. Each conflict is either a `trademark_id` or an inline
`{ mark, nice_classes, goods_services?, status? }` object. These alternatives
are mutually exclusive.

```bash theme={null}
curl --request POST https://api.signa.so/v1/compare \
  --header "Authorization: Bearer $SIGNA_API_KEY" \
  --header "Idempotency-Key: compare-kofee-001" \
  --header "Content-Type: application/json" \
  --data '{
    "candidate": {
      "mark": "KOFEE",
      "use": {
        "nice_classes": [30, 43],
        "business_description": "Coffee beans and cafe services"
      }
    },
    "conflicts": [
      { "trademark_id": "tm_abc123" },
      {
        "mark": "COFFEE HOUSE",
        "nice_classes": [30, 43],
        "goods_services": "Coffee and restaurant services",
        "status": "active"
      }
    ],
    "jurisdictions": ["US"],
    "offices": ["US"]
  }'
```

| Field                                | Type      | Notes                                                                                                                                                                                                                                                                 |
| ------------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `candidate`                          | object    | **Required.** Exactly one of `{ trademark_id }` or `{ mark, use }`. Mark text is 2–200 characters.                                                                                                                                                                    |
| `candidate.use.nice_classes`         | number\[] | Intended Nice classes, 1–45.                                                                                                                                                                                                                                          |
| `candidate.use.goods_services`       | object\[] | Structured lines shaped as `{ nice_class?, text }`; text is at most 500 characters.                                                                                                                                                                                   |
| `candidate.use.business_description` | string    | Plain-English intended use, up to 2,000 characters.                                                                                                                                                                                                                   |
| `conflicts`                          | object\[] | **Required.** 1–10 items. Each is exactly `{ trademark_id }` or an inline conflict.                                                                                                                                                                                   |
| `conflicts[].nice_classes`           | number\[] | Required for inline conflicts; at least one class, 1–45.                                                                                                                                                                                                              |
| `conflicts[].goods_services`         | string    | Optional goods/services text, up to 2,000 characters.                                                                                                                                                                                                                 |
| `conflicts[].status`                 | string    | Optional closed vocabulary: `active`, `pending`, `inactive`, or `unknown`; defaults to `unknown`. Unknown is treated as live so missing status never lowers risk.                                                                                                     |
| `jurisdictions`                      | string\[] | Optional ISO 3166-1 alpha-2 territory codes such as `US` and `EU`; trimmed, uppercased, maximum 30.                                                                                                                                                                   |
| `offices`                            | string\[] | Optional office codes such as `US` and `EM`; trimmed, maximum 10 characters each and 30 items. ST.3 codes and legacy internal codes (`uspto`, `euipo`) resolve to one canonical office identity and duplicates are collapsed; unrecognized tokens pass through as-is. |

## Response

```json theme={null}
{
  "object": "list",
  "comparison": {
    "candidate": {
      "mark": "KOFEE",
      "normalized": "kofee",
      "effective_nice_classes": [30, 43]
    },
    "rules_version": "compare-v1.1",
    "band_rules_version": "screening-v1",
    "mapping_version": "mark-search-v3",
    "resolver": {
      "engine_version": "resolver-v1",
      "ruleset_version": "rules-v1",
      "catalog_version": "nice-12"
    },
    "as_of": "2026-07-13T00:00:00.000Z",
    "warnings": [],
    "disclaimer": "This comparison is informational, not legal advice or a registrability determination; the band table is under active recalibration."
  },
  "data": [
    {
      "object": "comparison_result",
      "comparison_status": "ok",
      "review_recommended": true,
      "warnings": [],
      "conflict": {
        "trademark_id": "tm_abc123",
        "mark": "COFFEE",
        "nice_classes": [30],
        "status_as_evaluated": "active",
        "record_as_of": "2026-07-13T00:00:00.000Z"
      },
      "risk_level": "medium",
      "reason_codes": ["phonetic_mark", "same_nice_class"],
      "mark_match": {
        "level": "strong",
        "matched_levels": ["phonetic_strong"]
      },
      "goods_services": {
        "level": "same_class",
        "overlap_classes": [30],
        "text_similarity": 0.8
      },
      "similarity": { "string": 0.67, "phonetic": 1, "semantic": null }
    }
  ],
  "summary": { "high": 0, "medium": 1, "low": 0, "none": 0, "not_comparable": 0, "review_recommended": 1 },
  "has_more": false,
  "pagination": { "cursor": null },
  "request_id": "req_…"
}
```

`comparison_status`, risk levels, and match levels are closed vocabularies.
`reason_codes`, `data[].warnings`, and `comparison.warnings` are open vocabularies; tolerate new
values. `matched_levels` names engine clauses for the pair. Screening calls its
corresponding post-sensitivity-gate field `admitted_match_levels`; Compare has
no sensitivity admission gate. `similarity.semantic` is reserved and currently
always `null`.

`review_recommended` is `true` exactly when the comparison succeeded and at
least one mark-similarity clause fired. It deliberately includes dead-mark and
`below_screening_band` pairs: it is a conservative human-review triage bit,
independent of `risk_level`. `summary.review_recommended` counts those results.

An unknown conflict ID degrades to `not_found`. A conflict whose database
record exists but cannot be compared, because it is design-only or its search
document is still being indexed, degrades to `not_comparable`; index lag is
reported in that item's `warnings`. The other batch items still succeed. The
The five risk/status buckets in `summary` count every requested conflict exactly
once; `review_recommended` is an additional, overlapping count.

### Error codes

Compare can return `insufficient_use_profile` when the candidate's intended use
cannot be resolved, `batch_too_large` when more than 10 conflicts are supplied,
or `not_screenable` when a candidate trademark has no screenable mark text.

### Why can `risk_level` be null?

An `ok` comparison has no band only when no mark-similarity clause fired. Those
pairs count in `summary.none`:

| Reason code            | `risk_level` | Meaning                                                                                                                                        |
| ---------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `no_mark_similarity`   | `null`       | The marks did not match any meaningful name-similarity clause, so assigning a risk band would invent a signal.                                 |
| `below_screening_band` | `low`        | A clause matched, but the pair's mark and goods/services combination falls below the served screening band table; review is still recommended. |

## Suggest → screen → compare

Use classification suggestion to turn plain English into classes, screening to
find candidates, then Compare to inspect a selected pair. This sequence uses
`jq` to pass results between calls:

```bash theme={null}
# 1. Turn the intended use into suggested Nice classes.
curl --silent --request POST https://api.signa.so/v1/classifications/suggest \
  --header "Authorization: Bearer $SIGNA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"description":"Coffee beans and cafe services"}' > /tmp/signa-classes.json

CLASSES=$(jq -r '[.classes[].class_number] | join(",")' /tmp/signa-classes.json)

# 2. Find possible conflicts.
curl --silent --get https://api.signa.so/v1/screening \
  --header "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=KOFEE" \
  --data-urlencode "nice_classes=$CLASSES" \
  --data-urlencode "jurisdictions=US" > /tmp/signa-screen.json

CONFLICT_ID=$(jq -r '.data[0].trademark.id' /tmp/signa-screen.json)
CLASS_JSON=$(jq '[.classes[].class_number]' /tmp/signa-classes.json)

# 3. Score the selected pair.
jq -n --arg id "$CONFLICT_ID" --argjson classes "$CLASS_JSON" '{
  candidate: {
    mark: "KOFEE",
    use: {nice_classes: $classes, business_description: "Coffee beans and cafe services"}
  },
  conflicts: [{trademark_id: $id}],
  jurisdictions: ["US"]
}' | curl --request POST https://api.signa.so/v1/compare \
  --header "Authorization: Bearer $SIGNA_API_KEY" \
  --header "Content-Type: application/json" \
  --data-binary @-
```

## Intent-to-use candidates

You do not need to know the Nice system before comparing a proposed mark.
Describe the planned business in ordinary language, call [Suggest
Classifications](/api-reference/reference/suggest-classifications), review its
ranked classes, then place those class numbers and the original description in
`candidate.use`. Compare resolves that intent into the effective classes echoed
in `comparison.candidate.effective_nice_classes` before scoring every pair.

## SDK

```typescript theme={null}
const result = await signa.compare.compare({
  candidate: {
    mark: 'KOFEE',
    use: {
      nice_classes: [30, 43],
      business_description: 'Coffee beans and cafe services',
    },
  },
  conflicts: [{ trademark_id: 'tm_abc123' }],
  jurisdictions: ['US'],
});

console.log(result.data[0]?.risk_level, result.data[0]?.similarity);
```

## Limitations

* Compare v1 is text-only; it does not compare logos, designs, or images.
* Numeric dimensions are measurements beneath the band, not a legal conclusion
  or an overall confusion probability.
* The band table is under active recalibration.

## Compare vs Screening divergence FAQ

Compare and [Screening](/api-reference/screening/screen) use the same mark and
goods/services levels. Compare must never return a less alarming band for a
pair that the equivalent broad screen surfaced. A few differences are expected
because Screening searches a corpus while Compare evaluates a chosen pair.

### Why can Screening say `medium` when Compare says `high`?

Screening can demote a weak or crowded mark using corpus-wide evidence that a
pair-only comparison cannot see. The Screening hit then includes
`weak_mark_demoted` or `crowded_field`. Compare deliberately gives the more
conservative reading, and it can be only one band higher.

```bash theme={null}
SCREEN=$(curl -sG https://api.signa.so/v1/screening \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=YOUR_MARK" --data-urlencode "nice_classes=30" \
  --data-urlencode "sensitivity=broad" --data-urlencode "include=all")
ID=$(jq -r '.data[0].trademark.id' <<<"$SCREEN")
jq -n --arg id "$ID" '{candidate:{mark:"YOUR_MARK",use:{nice_classes:[30]}},conflicts:[{trademark_id:$id}]}' |
  curl -s https://api.signa.so/v1/compare -H "Authorization: Bearer $SIGNA_API_KEY" \
    -H "Content-Type: application/json" --data-binary @-
```

**What to do:** treat Compare's higher band as the conservative pair reading;
retain Screening's strength reason codes in your audit trail.

### Why does Compare score a pair that default Screening did not show?

Compare evaluates every match tier. Screening defaults to `standard`, while a
`fuzzy_2`-only match is admitted only at `broad` sensitivity.

```bash theme={null}
curl -sG https://api.signa.so/v1/screening \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=QUORUM" --data-urlencode "nice_classes=9" \
  --data-urlencode "sensitivity=broad" --data-urlencode "include=all"
curl -s https://api.signa.so/v1/compare \
  -H "Authorization: Bearer $SIGNA_API_KEY" -H "Content-Type: application/json" \
  -d '{"candidate":{"mark":"QUORUM","use":{"nice_classes":[9]}},"conflicts":[{"mark":"QUXRUZ","nice_classes":[9]}]}'
```

**What to do:** use `sensitivity=broad&include=all` when validating Screening
against Compare.

### Why does a Madrid-family result change when I add a jurisdiction?

Madrid families can contain both live and dead territories. An unscoped call
uses family-wide liveness; a scoped call evaluates only the requested
territories. Passing a jurisdiction to one surface but not the other therefore
changes the record state being evaluated.

```bash theme={null}
curl -sG https://api.signa.so/v1/screening \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "trademark_id=$CANDIDATE_ID" \
  --data-urlencode "jurisdictions=EU" --data-urlencode "sensitivity=broad" \
  --data-urlencode "include=all"
curl -s https://api.signa.so/v1/compare \
  -H "Authorization: Bearer $SIGNA_API_KEY" -H "Content-Type: application/json" \
  -d "{\"candidate\":{\"trademark_id\":\"$CANDIDATE_ID\"},\"conflicts\":[{\"trademark_id\":\"$CONFLICT_ID\"}],\"jurisdictions\":[\"EU\"]}"
```

**What to do:** pass the same `jurisdictions` (and `offices`, when used) to both
calls.

### Why can Compare score an identical mark in an unrelated class?

Supplying `nice_classes` to Screening is a retrieval hard filter, so a record
outside those classes is never surfaced. Compare does not use class as a
retrieval filter: it evaluates the supplied pair and an identical, live mark in
an unrelated class can score `medium`.

```bash theme={null}
curl -sG https://api.signa.so/v1/screening \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=CLASHSCOPE" --data-urlencode "nice_classes=30" \
  --data-urlencode "sensitivity=broad" --data-urlencode "include=all"
curl -s https://api.signa.so/v1/compare \
  -H "Authorization: Bearer $SIGNA_API_KEY" -H "Content-Type: application/json" \
  -d '{"candidate":{"mark":"CLASHSCOPE","use":{"nice_classes":[30]}},"conflicts":[{"mark":"CLASHSCOPE","nice_classes":[9]}]}'
```

**What to do:** omit the Screening class filter when you need discovery across
all classes, then use Compare for pair-level class context.

The response disclaimer is authoritative: **This comparison is informational,
not legal advice or a registrability determination; the band table is under
active recalibration.**
