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

# Docketing sync

> Match docket entries to register records, keep them current, and turn changes into docket events.

A docketing system tracks marks by jurisdiction and office number. This guide links each docket entry to a Pinakel record once, then keeps the records current so that status changes, new dates and ownership changes become docket events.

## 1. Match docket entries to records

Look up each entry by number within its register. Up to 100 numbers fit in one request:

```bash theme={null}
curl -G "https://api.pinakelai.com/v1/trademarks" \
  -H "Authorization: Bearer $PINAKEL_API_KEY" \
  --data-urlencode "jurisdiction=AL" \
  --data-urlencode "number=AL/T/2026/001305" \
  --data-urlencode "number=AL/T/2026/001306" \
  --data-urlencode "limit=100"
```

```python Python theme={null}
def compact(number: str) -> str:
    """A comparison key for numbers: upper case, no whitespace."""
    return "".join(number.split()).upper()


def match_numbers(jurisdiction: str, numbers: list[str]) -> dict[str, list[dict]]:
    """Map each docket number (up to 100) to the summaries that carry it."""
    params = [("jurisdiction", jurisdiction), ("limit", "100")] + [("number", n) for n in numbers]
    found: dict[str, list[dict]] = {compact(n): [] for n in numbers}
    while True:
        resp = session.get(f"{API}/trademarks", params=params, timeout=30)
        resp.raise_for_status()
        page = resp.json()
        for tm in page["data"]:
            for field in ("application_number", "registration_number"):
                value = tm["identifiers"][field]
                if value and compact(value) in found and tm not in found[compact(value)]:
                    found[compact(value)].append(tm)
        if not page["has_more"]:
            return found
        params = [p for p in params if p[0] != "cursor"] + [("cursor", page["next_cursor"])]
```

Then decide per entry:

* **One match:** store its `id` on the docket entry.
* **Several matches:** offices reuse numbers. Compare `dates.application` and `mark.text` with the docket entry, and ask a person when they do not settle it.
* **No match:** check the number format against a record from the same office, check the register's coverage in [`GET /v1/jurisdictions`](https://docs.pinakelai.com/api-reference/jurisdictions/list) (`earliest_application_year`), and keep the entry for manual review.

From then on, use the `id`. Ids are permanent; numbers are not unique.

## 2. Load the full records

Fetch full records 100 ids at a time with [batch retrieve](https://docs.pinakelai.com/api-reference/trademarks/batch.md):

```python Python theme={null}
def fetch_records(ids: list[str]) -> tuple[list[dict], list[str], list[str]]:
    """Full records for any number of ids, plus the ids not found and not available now."""
    records, not_found, unavailable = [], [], []
    for i in range(0, len(ids), 100):
        resp = session.post(f"{API}/trademarks/batch", json={"ids": ids[i:i + 100]}, timeout=60)
        resp.raise_for_status()
        body = resp.json()
        records += body["data"]
        not_found += body["not_found"]
        unavailable += body["unavailable"]
    return records, not_found, unavailable
```

Ids under `unavailable` belong to a register that is reloading, or to a record that cannot be assembled right now: retry them on the next run. Ids under `not_found` no longer exist; flag them for review.

## 3. Keep the records current

There are two ways, and the cheaper one depends on the size of your docket and how busy its registers are.

| | Poll your ids | Follow the changes feed |
| - | - | - |
| How | Batch-retrieve every docketed id on a schedule, and compare | Poll [`/v1/changes`](https://docs.pinakelai.com/guides/syncing) for your registers, keep the items whose `id` is in your docket, batch-retrieve those |
| Cost | Your docket size ÷ 100 calls per run: 5,000 marks daily is 50 calls a day | Grows with how much the registers change, whatever your docket size; polls that find nothing are free |
| Latency | Your schedule | Within one poll interval of publication (15 minutes or more) |
| Best for | Dockets spread over large, busy registers such as US, CN or EM | Large dockets, or dockets concentrated in a few quieter registers |

You can mix them: follow the feed for quiet registers and poll ids in the busy ones.

## 4. Turn changes into docket events

A new version of a record is not necessarily a docket event: register reloads and processing changes republish records with nothing visible changed (see [How records change](https://docs.pinakelai.com/data/how-records-change.md#bursts)). Compare the fields that drive your docket with your stored copy, and raise an event only when one of them differs:

| Field | Typical docket event |
| - | - |
| `status.value`, `status.category`, `status.office_status` | Registration, refusal, abandonment, cancellation |
| `status.date` | A new decision or action date |
| `dates.registration` | Registration certificate due |
| `dates.publication`, `dates.opposition_period_end` | Opposition window opens or closes |
| `dates.expiry`, `dates.renewal` | Renewal recorded, or the next deadline moves |
| `dates.termination` | The right ended |
| `parties.owners[].name_key` | Change of ownership (check `include=assignments` for the recorded transfer) |
| `parties.representatives[].name_key` | Change of representative |
| `classification.nice_classes` | Partial refusal or restriction of the specification |

```python Python theme={null}
WATCHED = [
    ("status", "value"), ("status", "category"), ("status", "office_status"), ("status", "date"),
    ("dates", "registration"), ("dates", "publication"), ("dates", "opposition_period_end"),
    ("dates", "expiry"), ("dates", "renewal"), ("dates", "termination"),
    ("classification", "nice_classes"),
]


def docket_events(old: dict, new: dict) -> list[str]:
    events = [f"{a}.{b}: {old[a][b]!r} -> {new[a][b]!r}" for a, b in WATCHED if old[a][b] != new[a][b]]
    for role in ("owners", "representatives"):
        before = {p["name_key"] for p in old["parties"][role]}
        after = {p["name_key"] for p in new["parties"][role]}
        if before != after:
            events.append(f"parties.{role}: {sorted(before)} -> {sorted(after)}")
    return events
```

Name keys can change when Pinakel improves normalization. When an owner change shows the same `name` under a new `name_key`, it is not a change of ownership.

## 5. Check against the office before you act

Status mappings and dates come from each office's own publications, with the delays described in [Coverage & freshness](https://docs.pinakelai.com/data/coverage-and-freshness.md#freshness-is-two-delays). Treat a docket event from the API as a prompt to confirm with the office, especially for deadlines.

## Removed records

Records that an office deletes are not announced in v1. Running the id poll weekly, even if you follow the feed, catches them as `not_found`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.