# DTS Text Services Extractor (`datamule/dts-text-services-extractor`) Actor

Point at ANY Distributed Text Services (DTS) endpoint — DraCor, Perseids, DoTS and the TEI text-corpus world — and extract it: browse the collection tree, list a text's citeable references, or fetch the TEI/XML of any passage. One actor for digital-humanities texts.

- **URL**: https://apify.com/datamule/dts-text-services-extractor.md
- **Developed by:** [Datamule](https://apify.com/datamule) (community)
- **Categories:** Developer tools, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## DTS Text Services Extractor

**Point at ANY Distributed Text Services (DTS) endpoint and extract it — browse the collection tree, list a text's citeable references, or pull the TEI/XML of any passage.** Give the entry URL of a DTS service, choose a mode, and this actor returns structured, flat records from any compliant digital-humanities text repository.

One actor speaks to the entire DTS world. There is no per-repository scraper and no host list to maintain: **[DraCor](https://dracor.org) (drama corpora), [Perseids / Perseus](https://www.perseus.tufts.edu) (Greek & Latin classics), the [École nationale des chartes](https://chartes.psl.eu) (DoTS)** and the growing population of TEI text repositories all expose the identical [Distributed Text Services](https://distributed-text-services.github.io/specifications/) API.

### What is DTS?

Distributed Text Services is a specification that standardises how text collections are published on the web, so that a single client can navigate and retrieve text from any conformant repository. It is three linked endpoints, discovered from an **entry document**:

- **Collection** — the browse tree of a corpus: child collections and readable resources, each with Dublin Core metadata (title, creator, language …) and child / parent counts.
- **Navigation** — the citeable reference tree of one resource (its `front` / `body` / `act` / `scene` … citation units), so any passage can be addressed by its citation `ref`.
- **Document** — the actual **TEI / XML** text of a resource, or of a single passage / citation range.

The three endpoint URLs are **not hard-coded**: the actor reads the entry document's URI templates and resolves the real endpoints per host (DraCor serves `/api/v1/dts/collection`, Perseids serves `/collections` with no `/dts` segment, DoTS serves `/demo/api/dts/collection`), so whatever base URL you give it just works.

### What it does

- **Collection mode** — GETs the Collection endpoint and emits one record per member (child collection or readable resource), with `title`, `description`, `totalItems` / `totalChildren`, `dublinCore`, and the `collection` / `navigation` / `document` links. Recurses into child collections up to `maxDepth`, following Hydra pagination cursors.
- **Navigation mode** — lists the citeable references of a resource: one record per `CitableUnit` with its `ref`, `citeType` and `level`.
- **Document mode** — fetches the resource's (or one passage's) TEI/XML and emits it as a `rawXml` field plus a whitespace-normalised `plainText` rendering of the TEI `<text>` body.

Every optional field is nullable, because DTS members routinely omit Dublin Core creator / date / language. The member list is decoded from any serialization the spec and its drafts permit (`member` in DTS 1.0, `dts:member` / `@graph` on 1-alpha / rc hosts). A resource's Document reply is treated as XML, never JSON.

An HTTP error or non-conformant response is surfaced as a real error — never a fabricated empty result.

### Input

| Field | Required | Description |
|-------|----------|-------------|
| `entryUrl` | yes | The entry / base URL of a DTS service (e.g. `https://dev.dracor.org/api/v1/dts`). The actor resolves the Collection / Navigation / Document endpoints from it. You may also pass a Collection / Navigation / Document URL directly. |
| `mode` | no | `collection` (default), `navigation`, or `document`. |
| `collectionId` | no | *(collection mode)* The `@id` of a specific collection to descend into. Empty = the service's top collection. |
| `resource` | navigation / document | The identifier of the resource to navigate or fetch (a Resource member's `@id` from collection mode). |
| `ref` | no | A single citation reference to target (navigation: list its children; document: return just that passage). |
| `start` / `end` | no | A citation range to select a span of passages. |
| `down` | no | *(navigation mode)* How many citation levels below the target to return. Defaults to 1. |
| `mediaType` | no | *(document mode)* DTS `mediaType`, e.g. `application/tei+xml`, `text/plain`. |
| `maxDepth` | no | *(collection mode)* Levels of child collections to descend. `0` = no recursion, `1` (default) = expand one level. |
| `maxRecords` | no | Global cap on records emitted (each record is one billable event). Default 1000. |
| `timeoutSecs` | no | Per-request read timeout (default 120). |
| `userAgent` | no | Override the request User-Agent. |
| `bearer` | no | Bearer token for an auth-gated deployment (never logged). |
| `extraHeaders` | no | Extra HTTP headers as a JSON object (values never logged). |

#### Examples

Browse the top of the DraCor corpus tree, one level deep:

```json
{
  "entryUrl": "https://dev.dracor.org/api/v1/dts",
  "mode": "collection",
  "maxDepth": 1,
  "maxRecords": 200
}
```

List the citeable references of a play:

```json
{
  "entryUrl": "https://dev.dracor.org/api/v1/dts",
  "mode": "navigation",
  "resource": "https://dev.dracor.org/id/ger000171",
  "down": 1
}
```

Fetch the TEI/XML of one passage:

```json
{
  "entryUrl": "https://dev.dracor.org/api/v1/dts",
  "mode": "document",
  "resource": "https://dev.dracor.org/id/ger000171",
  "ref": "body"
}
```

Any DTS host works the same way — point `entryUrl` at Perseids (`https://dts.perseids.org`) or DoTS (`https://dots.chartes.psl.eu/demo/api/dts`) and the endpoints are resolved automatically.

### Output

Each record is a flat JSON object. Key fields:

| Field | Description |
|-------|-------------|
| `_mode` | `collection` / `navigation` / `document`. |
| `id` | The member / resource `@id`. |
| `type` | `Collection`, `Resource`, `CitableUnit`, `Document`. |
| `title`, `description` | Human-readable metadata (nullable). |
| `totalItems`, `totalChildren`, `totalParents` | Collection counts (nullable). |
| `dublinCore` | The member's Dublin Core metadata block (nullable). |
| `collectionUrl`, `navigationUrl`, `documentUrl`, `downloadUrl` | Links to descend / navigate / fetch (nullable). |
| `ref`, `citeType`, `level` | *(navigation)* the citation unit's reference, structural type, and depth. |
| `rawXml`, `plainText`, `mediaType` | *(document)* the TEI/XML, its stripped plaintext, and the response content type. |
| `_raw` | A lossless copy of the source member object. |

### Pricing

This actor is **pay-per-event**: you are charged per **record** emitted (one collection member, one citeable reference, or one document passage). Set `maxRecords` to cap the run.

### Notes

- This is a metadata & text runner over public DTS APIs. It respects each service's own responses and follows Hydra pagination.
- A resource with no members / an empty collection returns zero records (never fabricated rows).
- For auth-gated deployments, pass a `bearer` token or `extraHeaders`; credentials are never logged.

# Actor input Schema

## `entryUrl` (type: `string`):

The entry (base) URL of ANY Distributed Text Services (DTS) endpoint. ONE actor speaks to all of them — DraCor (https://dev.dracor.org/api/v1/dts), Perseids (https://dts.perseids.org), the École des chartes DoTS (https://dots.chartes.psl.eu/demo/api/dts) and the growing set of TEI text repositories. The actor reads this entry document and resolves the real Collection / Navigation / Document endpoints from its URI templates, so paths that differ per host (‘/api/v1/dts/collection’ vs ‘/collections’) all work. You can also pass a service's Collection / Navigation / Document URL directly.

## `mode` (type: `string`):

Which DTS endpoint to read. ‘collection’ browses the corpus tree (child collections + readable resources with Dublin Core metadata). ‘navigation’ lists the citeable references (front / body / act / scene …) of one resource. ‘document’ fetches the actual TEI/XML text of a resource or one passage.

## `collectionId` (type: `string`):

Collection mode only. The ‘@id’ of a specific collection to descend into (sent as the DTS Collection ‘id’ parameter). Leave empty to start at the service's top collection. Example (DraCor): https://dev.dracor.org/id/ger

## `resource` (type: `string`):

Navigation and Document modes require this: the identifier of the readable resource to navigate or fetch (the ‘@id’ of a Resource member from collection mode). Example (DraCor): https://dev.dracor.org/id/ger000171

## `ref` (type: `string`):

Optional. A single citation reference to target — the ‘ref’ of a CitableUnit (e.g. ‘body’, ‘1.1’). In navigation mode, lists the children of this ref; in document mode, returns just this passage's TEI/XML. Leave empty for the whole resource.

## `start` (type: `string`):

Optional. The start of a citation range (used with ‘end’) to select a span of passages, e.g. start=1 end=3. Overridden by ‘ref’ when both are set.

## `end` (type: `string`):

Optional. The end of a citation range (used with ‘start’).

## `down` (type: `integer`):

Navigation mode only. How many citation levels below the target to return (the DTS ‘down’ parameter): 1 = the immediate children, 2 = children + grandchildren, -1 = the full tree. Defaults to 1 when no ref/range is given.

## `mediaType` (type: `string`):

Optional DTS ‘mediaType’ for the Document endpoint (e.g. ‘application/tei+xml’, ‘text/plain’). Leave empty to use the service's default (usually TEI/XML). Not all services honour every value.

## `maxDepth` (type: `integer`):

Collection mode only. How many levels of child collections to descend into. 0 = only the members of the requested collection (no recursion). 1 (default) = also expand each child collection one level. Deeper values traverse more of the corpus tree (and cost more, one billable record per member).

## `maxRecords` (type: `integer`):

A GLOBAL cap on the number of records emitted (each record is one billable event). Reached mid-traversal, the run stops deterministically. Default 1000.

## `userAgent` (type: `string`):

Optional override for the request User-Agent. A descriptive User-Agent is sent by default — some repositories throttle or reject a blank / generic UA. Only override if a specific service asks you to identify differently.

## `timeoutSecs` (type: `integer`):

How long to wait for the service to answer (default 120). Fetching a large TEI document or a deep navigation tree can be slow.

## `bearer` (type: `string`):

Optional bearer token for an auth-gated DTS deployment (sent as Authorization: Bearer \*\*\*). NOT required for public services. Never logged.

## `extraHeaders` (type: `object`):

Optional extra HTTP headers as a JSON object, e.g. {"X-Api-Key": "..."} for a gateway-fronted service. Not required for public services. Header values are never logged.

## Actor input object example

```json
{
  "entryUrl": "https://dev.dracor.org/api/v1/dts",
  "mode": "collection",
  "maxDepth": 1,
  "maxRecords": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "entryUrl": "https://dev.dracor.org/api/v1/dts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("datamule/dts-text-services-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "entryUrl": "https://dev.dracor.org/api/v1/dts" }

# Run the Actor and wait for it to finish
run = client.actor("datamule/dts-text-services-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "entryUrl": "https://dev.dracor.org/api/v1/dts"
}' |
apify call datamule/dts-text-services-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=datamule/dts-text-services-extractor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/uluFR0vXjgV3m6CB3/builds/zPkM0eScuR6zAd9SY/openapi.json
