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 Authorization header 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

Omnipub API

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.
PDF 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_id field when adding Omnipubs so that it is easier to identify your data in our database.

Examples of an external_id are an ISBN, blog post id, or UUID that you use to identify content on your system.

We also strongly recommend saving the pub_hash and uuid from 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:

  1. Prepare:
    • Optional: Metadata JSON (if not extracting from EPUB).
    • Optional: external_id (to avoid duplicates), tags, collection_id.
  2. 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"
  1. Response:

    We highly recommend saving the uuid, and pub_hash for lookup later

{
  "status": "success",
  "uuid": "52e24f5a1e0f42f8af09a4a91ded8d16",
  "pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7"
}

2. Upload via URL

Steps:

  1. Prepare:
    • Publicly accessible URL to EPUB/HTML/PDF/Markdown/XML.
    • Optional: Metadata JSON (if not auto-extracted from EPUB format).
  2. 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"
  1. Response:

    We highly recommend saving the uuid, and pub_hash for lookup later

{
  "status": "success",
  "uuid": "52e24f5a1e0f42f8af09a4a91ded8d16",
  "pub_hash": "ae803b4d136ce2f6a80d64d15ad3dfc7"
}

3. Upload from Storage Bucket (e.g., S3/GCS/R2)

Steps:

  1. 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
)
  1. 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"
  1. Response:

    We highly recommend saving the uuid, and pub_hash for 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 file and file_url are provided, file takes precedence.

Supercharge your API usage

  1. 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.
  2. 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.
  3. 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.