Add Omnipubs via the API
Overview
Use this guide to add Omnipubs to Cashmere programmatically via the REST API. It covers authentication, request schema, validation rules, and example response
All content upload endpoints require Bearer token authentication. Include your API key in the
Authorizationheader of each request. You can receive yours in the Console
Omnipub: Create an omnipub
Create an omnipub asynchronously from EPUB, HTML, PDF, XML, or Markdown. This endpoint accepts content as a file, a URL to a file, or a JSON object.
- Method: POST
- Path:
/v2/omnipub - Auth: Async Auth Bearer (Bearer token)
- Content type:
multipart/form-data
Notable Upload Behavior
- Direct file uploads are stored in object storage and queued for processing.
- File URLs (publicly accessible) are fetched, stored, and queued.
- Inline content (HTML or Markdown passed directly by the request body) is parsed and converted into a normalized Omnipub object.
- Max file size: 100MB
API Reference
Responses
- 200 OK application/json
{
"status": "success",
"uuid": "2a4f9c2e-7a1b-4b9c-9f2e-1b9c6a2d0f3e",
"pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7",
"error": null
}
- 200 OK (error case)
{
"status": "error",
"uuid": null,
"pub_hash": null,
"error": "File too large. Maximum size is 100MB."
}
cURL example
curl -X POST https://api.cashmere.io/v2/omnipub \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: multipart/form-data" \
-F external_id="9780812968255" \
-F collection_id=42 \
-F file_url="[https://example.com/meditations.epub](https://example.com/book.epub)" \
-F metadata='{"title":"Meditations","authors":["Marcus Aurelius"]}'
Which file format should I upload?
If you only have content in a specific format, just upload that. As long as we support it, the data will be ingested. However, some formats are better than others for specific use-cases.
| Format | Description | When to Use |
|---|---|---|
| EPUB | Standard eBook container; includes rich metadata, structure, and assets. | Ideal for final book content or professionally prepared publications. |
| HTML | Web-native markup. | Best for content exported from CMS or landing pages. |
| Markdown | Lightweight, text-first format. | Useful for internal knowledge bases, blog drafts, or quick ingestion. |
| Page-based representation. | Use if original material is only available in PDF, but note that layout extraction may be less precise than EPUB. | |
| XML | Structured data format. | Use for technical documents or archives where XML schemas define structure. |
Identifiers: What They Mean and Why They Matter
| Identifier | Example | Purpose / Why You Care |
|---|---|---|
| external_id | "9780140455113" | Assigned by you. Used to link your internal record to the Omnipub. Must be unique per account or else the Omnibook is marked as duplicate (code 1100). |
| pub_hash | "ae803b4d136ce2f6a80d64d15ad3dfc7" | Cashmere-generated, unique hash based on the Omnipub content. Allows you to determine if the content you are adding is the same as content you already have in the Cashmere API. Omnibooks with identical pub_hash values are marked as duplicate (code 1100). |
| uuid | "52e24f5a1e0f42f8af09a4a91ded8d16" | Cashmere API reference value for the creation job. Use this to poll the /v2/omnipubs/{uuid}/status endpoint. |
We strongly recommend populating the
external_idfield when adding Omnipubs so that it is easier to identify your data in our database.Examples of an
external_idare an ISBN, blog post id, or UUID that you use to identify content on your system.We also strongly recommend saving the
pub_hashanduuidfrom the API for easy process polling and content lookup.
Ingestion Lifecycle & Status Codes
After a successful POST, you must poll GET /v2/omnipubs/{uuid}/status to verify ingestion.
| Status | Label | Description |
|---|---|---|
| 100 | Ready | Processing complete. Content is searchable via the /search endpoint. |
| 110 | Error | Ingestion failed. Check the error field for details |
| 1100 | Duplicate | Content rejected. Queued for removal. Look for duplicate_type and duplicate_uuid in the response. |
| 95-499 | Processing | In-progress. Content is not yet searchable. |
Sample Use Cases
Prerequisites:
- Have a Cashmere account.
- A cashmere API key. If you need an API key, see this guide.
1. Upload via File (Local)
Steps:
- Prepare:
- Optional: Metadata JSON (if not extracting from EPUB).
- Optional: external_id (to avoid duplicates), tags, collection_id.
- Request:
curl -X POST "https://cashmere.io/api/v2/omnipub" \
-H "Authorization: Bearer YOUR_API_KEY" \
# Note that the file path is relative to the current working directory
-F "file=@/path/to/book.epub" \
-F "metadata={\"title\":\"Meditations\",\"authors\":[\"Marcus Aurelius\"]}" \
-F "external_id=9780140455113" \
-F "tags=metaphysics,philosophy" \
-F "collection_id=42"
-
Response:
We highly recommend saving the
uuid, andpub_hashfor lookup later
{
"status": "success",
"uuid": "52e24f5a1e0f42f8af09a4a91ded8d16",
"pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7"
}
2. Upload via URL
Steps:
- Prepare:
- Publicly accessible URL to EPUB/HTML/PDF/Markdown/XML.
- Optional: Metadata JSON (if not auto-extracted from EPUB format).
- Request:
curl -X POST "https://cashmere.io/api/v2/omnipub" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file_url=https://example.com/meditations.epub" \
-F "metadata={\"title\":\"Meditations\",\"authors\":[\"Marcus Aurelius\"]}" \
-F "external_id=9780140455113" \
-F "collection_id=42"
-
Response:
We highly recommend saving the
uuid, andpub_hashfor lookup later
{
"status": "success",
"uuid": "52e24f5a1e0f42f8af09a4a91ded8d16",
"pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7"
}
3. Upload from Storage Bucket (e.g., S3/GCS/R2)
Steps:
- Generate Pre-Signed URL (if private bucket):
# AWS S3 example
import boto3
s3 = boto3.client('s3')
bucket_url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': 'your-bucket', 'Key': 'book.epub'},
ExpiresIn=3600
)
- Pass URL to Omnipub endpoint:
curl -X POST "https://cashmere.io/api/v2/omnipub" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file_url=BUCKET_URL" \
-F "external_id=9780140455113" \
-F "metadata={\"title\":\"Meditations\",\"authors\":[\"Marcus Aurelius\"]}" \
-F "collection_id=42"
-
Response:
We highly recommend saving the
uuid, andpub_hashfor lookup later
{
"status": "success",
"uuid": "52e24f5a1e0f42f8af09a4a91ded8d16",
"pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7"
}
Notes
- File size: files over 100MB will be rejected.
- Polling: Do not assume a 200 OK from the POST endpoint means the content is live. Always verify status 100.
- Handling 1100: If you receive a 1100 status, use the duplicate_uuid to map your local record to the existing Omnipub.
- When both
fileandfile_urlare provided,filetakes precedence.
Supercharge your API usage
- Use consistent external_id patterns for easier tracking - using your systems article ID, or the ISBN allows you to quickly identify content in status checks and reports. This also makes it trivial to query which content has been uploaded vs. what's still pending in your CMS.
- Include source_url in all uploads - Even if you're uploading files directly, its a good idea to pass the canonical URL in source_url. This helps users find the original source when they discover your content through AI search, and it's essential for proper attribution.
- Provide good metadata upfront - At minimum, we recommend you include title, authors, and external_id. Adding creation_date, publisher, and other metadata improves discoverability and helps with licensing workflows. Metadata can't easily be changed after upload, so it’s best to get it right the first time.