Skip to main content

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.

Trademark data changes infrequently for most records. By leveraging ETags and conditional requests, you can avoid re-downloading unchanged data, reduce your rate limit consumption, and speed up your application.

How ETags Work

When you fetch a resource, the response includes an ETag header containing a content fingerprint:
HTTP/1.1 200 OK
ETag: "a3f2b7c8d1e4"
Cache-Control: private, max-age=300
Content-Type: application/json

{
  "id": "tm_7d4e1f2a",
  "object": "trademark",
  "mark_text": "SIGNA",
  ...
}
On subsequent requests, send the ETag back via If-None-Match. If the resource has not changed, you get a 304 Not Modified with no body, saving bandwidth and processing time:
GET /v1/trademarks/tm_7d4e1f2a
If-None-Match: "a3f2b7c8d1e4"

HTTP/1.1 304 Not Modified
ETag: "a3f2b7c8d1e4"
A 304 Not Modified response does not count against your rate limit, so using ETags effectively gives you free requests.

Endpoints That Support ETags

EndpointETag SupportTypical max-age
GET /v1/trademarks/:idYes300 s (5 min)
GET /v1/owners/:idYes300 s
GET /v1/attorneys/:idYes300 s
GET /v1/firms/:idYes300 s
GET /v1/officesYes3600 s (1 hour)
GET /v1/jurisdictionsYes3600 s
GET /v1/trademarks (list/search)No
POST /v1/trademarks (search)No
POST /v1/trademarks/batchNo
Reference data endpoints (offices, jurisdictions, classifications) change very rarely. Cache these aggressively with a long TTL.

Cache-Control Headers

Every cacheable response includes a Cache-Control header:
Cache-Control: private, max-age=300
DirectiveMeaning
privateResponse is specific to this API key and must not be stored by shared caches (CDNs, proxies).
max-age=NThe response is considered fresh for N seconds. After that, revalidate with If-None-Match.
no-storePresent on mutation responses (POST, PATCH, DELETE). Do not cache.
All Signa responses include private because they are scoped to your organization. Never cache API responses in a shared/public cache.

Conditional Request Flow

const cache = new Map<string, { etag: string; data: any }>();

async function getTrademarkCached(id: string, apiKey: string) {
  const url = `https://api.signa.so/v1/trademarks/${id}`;
  const headers: Record<string, string> = {
    Authorization: `Bearer ${apiKey}`,
  };

  // Send ETag if we have a cached version
  const cached = cache.get(id);
  if (cached) {
    headers['If-None-Match'] = cached.etag;
  }

  const response = await fetch(url, { headers });

  if (response.status === 304 && cached) {
    // Not modified -- return cached data
    return cached.data;
  }

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const data = await response.json();
  const etag = response.headers.get('ETag');

  // Store in cache
  if (etag) {
    cache.set(id, { etag, data });
  }

  return data;
}

Caching Strategies for Trademark Data

Different types of trademark data have different change frequencies. Tailor your caching strategy accordingly.

Reference Data (offices, jurisdictions, classifications)

These change only when Signa adds a new office or jurisdiction.
  • Strategy: Cache locally with a 24-hour TTL. Revalidate with ETags daily.
  • Storage: In-memory or local file.

Individual Trademarks

Most trademark records change infrequently (a few times per year), but some change during active prosecution.
  • Strategy: Cache with the max-age value from the response (typically 5 min). Revalidate with ETags after expiry.
  • Storage: In-memory cache (Redis, local Map) keyed by trademark ID.

Search Results

Search results are dynamic and depend on query parameters, so they are not ETag-cacheable.
  • Strategy: Client-side TTL cache keyed by the full query hash. A 30—60 second TTL works well for typeahead and repeated searches.
  • Storage: In-memory only. Do not persist search result caches.

List Endpoints

Paginated lists may change as new records are added.
  • Strategy: Short TTL (60 s from max-age). Revalidate with ETags. Note that the cursor itself provides consistency within a pagination session.
  • Storage: In-memory, keyed by the full URL including query parameters.

Cache Invalidation

ETags handle revalidation automatically: a conditional GET against the same resource returns 304 Not Modified when the record is unchanged, so your cache entry stays valid and the request counts against your rate budget at the light-weight “not modified” rate. For records you display in hot UI surfaces, complement ETag revalidation with a background refresh on the Trademark Changes endpoint — it returns the versioned change log for a specific mark, letting you invalidate a cache entry the moment a new version is detected without waiting for the next ETag check.
Pair ETags for normal traffic with a low-frequency poll of trademark-changes for your hottest marks. You get efficient revalidation on every read plus proactive busting on the small set of records that actually churn.

Multi-Tier Caching

For applications that display trademark data to end users, consider a two-tier approach:
TierTTLPurpose
L1: In-process30—60 sEliminates redundant API calls within a single request cycle.
L2: Redis / Memcached5—15 minShares cached data across application instances. Uses ETag revalidation on miss.
async function getTrademark(id: string): Promise<Trademark> {
  // L1: Check in-process cache
  const l1 = memoryCache.get(id);
  if (l1) return l1;

  // L2: Check Redis
  const l2 = await redis.get(`tm:${id}`);
  if (l2) {
    const parsed = JSON.parse(l2);
    memoryCache.set(id, parsed, { ttl: 30_000 });
    return parsed;
  }

  // L3: Fetch from API with ETag revalidation
  const data = await getTrademarkCached(id, apiKey);
  memoryCache.set(id, data, { ttl: 30_000 });
  await redis.set(`tm:${id}`, JSON.stringify(data), 'EX', 900);

  return data;
}