Trademark Documents
curl --request GET \
--url https://api.signa.so/v1/trademarks/{id}/documents \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.signa.so/v1/trademarks/{id}/documents"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.signa.so/v1/trademarks/{id}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/trademarks/{id}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/trademarks/{id}/documents"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.signa.so/v1/trademarks/{id}/documents")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/trademarks/{id}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"object": "<string>",
"source_sync": {
"status": "<string>",
"last_synced_at": "<string>"
},
"data": [
{
"id": "<string>",
"object": "<string>",
"document_kind": "<string>",
"official_date": "<string>",
"description": "<string>",
"mime_type": "<string>",
"page_count": 123,
"url": "<string>"
}
],
"has_more": true,
"pagination": {}
}Records
Trademark Documents
Office documents for a mark (office actions, certificates, filed forms, correspondence) with lazy first-request metadata fetch and a streaming file proxy
GET
/
v1
/
trademarks
/
{id}
/
documents
Trademark Documents
curl --request GET \
--url https://api.signa.so/v1/trademarks/{id}/documents \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.signa.so/v1/trademarks/{id}/documents"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.signa.so/v1/trademarks/{id}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/trademarks/{id}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/trademarks/{id}/documents"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.signa.so/v1/trademarks/{id}/documents")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/trademarks/{id}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"object": "<string>",
"source_sync": {
"status": "<string>",
"last_synced_at": "<string>"
},
"data": [
{
"id": "<string>",
"object": "<string>",
"document_kind": "<string>",
"official_date": "<string>",
"description": "<string>",
"mime_type": "<string>",
"page_count": 123,
"url": "<string>"
}
],
"has_more": true,
"pagination": {}
}Credits: 1 per page.
For a non-USPTO mark you always get
When to use this
If you run docketing or an IPMS product, you need office documents the day they issue: the non-final office action a client must answer, the registration certificate, the filed forms in the prosecution history. This endpoint returns that document list for a mark, and each row carries aurl that streams the actual file. USPTO documents are fetched from TSDR on demand the first time you ask, so you get fresh metadata without running your own TSDR poller or fighting its throttles.
The list is document-type media only. Logos, drawings, and specimens live on the media[] array of the trademark record, not here.
A
media[].url on a trademark record points at the same proxy and behaves the same way: an unauthenticated 200 that serves the image bytes, cache-safe for 24 hours. Safe to drop straight into an <img src>. Prefer the URL as returned; if you need to build one, the SDK’s trademarks.media(id, mediaId) constructs the same URL.Path Parameters
string
required
Trademark ID (
tm_...).Query Parameters
string
Filter by one or more document kinds, comma-separated. Values:
office_action, certificate, correspondence, filed_form, other. Example: document_kind=office_action,certificate.string
Return documents whose official date is on or after this date (
YYYY-MM-DD).string
Return documents whose official date is strictly before this date (
YYYY-MM-DD). Must be after official_date_gte when both are supplied.integer
default:"20"
Page size, 1 to 100.
string
Pagination cursor from a previous response’s
pagination.cursor.Response
A standard list envelope with one extra field,source_sync, describing the freshness of this mark’s document metadata.
string
Always
list.object
Freshness of the document metadata for this mark.
Show source_sync
Show source_sync
string
One of:
synced: metadata is present and current.datareflects the stored documents.pending: a lazy fetch is in flight (another request holds the fetch lock) or upstream is still settling.datamay be empty or partial. Request again shortly.unsupported: the mark belongs to an office without document support (anything other than USPTO today).datais always empty and no fetch is attempted.
string
ISO 8601 timestamp of the last successful sync, or
null if never synced.array
Array of trademark document objects, newest official date first.
Show trademark document
Show trademark document
string
Media ID (
med_...). Use it against the media proxy to stream the file.string
Always
trademark_document.string
One of
office_action, certificate, correspondence, filed_form, other.string
Official document date (
YYYY-MM-DD), or null when the office did not supply one.string
Human-readable label from the office (e.g.
Nonfinal Office Action), or null.string
Content type of the file, typically
application/pdf.integer
Page count when known, otherwise
null.string
Absolute media-proxy URL that streams the file bytes. See Downloading document files.
boolean
Whether more pages exist after this one.
object
Contains
cursor (the token for the next page, or null).First call: metadata fetch in flight
The first time you request documents for a USPTO mark that has never been synced, Signa fetches the metadata inline. If another request already holds the fetch lock, or the upstream fetch is still settling, you getstatus: pending with an empty data array. Request again in a moment.
First call (pending)
{
"object": "list",
"source_sync": { "status": "pending", "last_synced_at": null },
"data": [],
"has_more": false,
"pagination": { "cursor": null },
"request_id": "req_01kx22n3rpj1e6cmnpfbf482k6"
}
Second call: synced with a document row
Once the fetch completes, the list issynced and each row carries its media-proxy url.
Second call (synced)
{
"object": "list",
"source_sync": { "status": "synced", "last_synced_at": "2026-07-09T04:00:00Z" },
"data": [
{
"id": "med_019d2141-6ce9-771b-872e-bc8b20e49fcf",
"object": "trademark_document",
"document_kind": "office_action",
"official_date": "2025-11-04",
"description": "Nonfinal Office Action",
"mime_type": "application/pdf",
"page_count": 12,
"url": "https://api.signa.so/v1/trademarks/tm_8kLm2nPq/media/med_019d2141-6ce9-771b-872e-bc8b20e49fcf"
}
],
"has_more": false,
"pagination": { "cursor": null },
"request_id": "req_01kx22p7c8a0d3fjm2q9x7t4bz"
}
200 with an empty list and source_sync.status of unsupported. This endpoint never returns an error status for an office that lacks document support.
Code Examples
# First call kicks off the lazy fetch
curl "https://api.signa.so/v1/trademarks/tm_8kLm2nPq/documents" \
-H "Authorization: Bearer $SIGNA_API_KEY"
# Filter to office actions issued in 2025
curl "https://api.signa.so/v1/trademarks/tm_8kLm2nPq/documents?document_kind=office_action&official_date_gte=2025-01-01&official_date_lt=2026-01-01" \
-H "Authorization: Bearer $SIGNA_API_KEY"
import { Signa } from "@signa-so/sdk";
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const docs = await signa.trademarks.documents("tm_8kLm2nPq", {
document_kind: "office_action",
official_date_gte: "2025-01-01",
official_date_lt: "2026-01-01",
});
if (docs.source_sync?.status === "pending") {
// metadata still settling; request again shortly
}
for (const doc of docs.data) {
console.log(doc.document_kind, doc.official_date, doc.url);
}
Downloading document files
Each document row’surl points at the media proxy, GET /v1/trademarks/{id}/media/{mediaId}. Following it streams the file bytes:
- The response is the raw file with its real
Content-Type(application/pdffor office actions and certificates). X-Content-Type-Options: nosniffis set. The proxy never relabels a file: an image row stays an image content type.- The endpoint is unauthenticated so it works directly in
<a href>and<embed>, but it is IP rate-limited, so treat it as a per-download link, not a bulk hose.
502 upstream_error with a Retry-After: 60 header. Honor it and retry after the stated delay. Once persisted, subsequent downloads serve the stored bytes and do not touch TSDR.
cURL
# Stream an office action PDF to disk
curl -L "https://api.signa.so/v1/trademarks/tm_8kLm2nPq/media/med_019d2141-6ce9-771b-872e-bc8b20e49fcf" \
-o office-action.pdf
Related Endpoints
- Build a docketing system: the end-to-end recipe using this endpoint
- Retrieve Trademark: the full record, including the
media[]image array