Skip to main content

Python Quickstart

Developer Key

A rate-limited API developer key can be provisioned from the Sapling API dashboard. Developer keys allow for processing of 50,000 characters every 24 hours. Subscribe for production access and usage-based pricing.

Installation​

Install the sapling-py package from PyPI with pip:

python -m pip install sapling-py

Or add it to a uv project:

uv add sapling-py

Getting Edits​

Here's a sample script using the Sapling package:

from sapling import SaplingClient

api_key ='<api-key>'
client = SaplingClient(api_key=api_key)
edits = client.edits('Lets get started!', session_id='test_session')

The result of running the script should be an array of edits of this form:

[{
"id": "aa5ee291-a073-5146-8ebc-c9c899d01278",
"sentence": "Lets get started!",
"sentence_start": 0,
"start": 0,
"end": 4,
"replacement": "Let's",
"error_type": "R:OTHER",
"general_error_type": "Other",
}]

Applying Edits

After you've sent the Sapling API text and gotten JSON edits in the response, how do you apply the edits to get updated text?

The simplest way is to use the auto_apply argument. By setting this to true, the returned response will have an extra field, applied_text, that contains the text with the edits applied.

However, you can also easily apply edits programmatically.

Programmatically Applying Edits​

Recall the Edit data structure:

{
"id": <str, UUID>, // Opaque edit id, used to give feedback
"sentence": <str>, // Unedited sentence
"sentence_start": <int>, // Offset of sentence from start of text
"start": <int>, // Offset of edit start relative to sentence
"end": <int>, // Offset of edit end relative to sentence
"replacement": <str>, // Suggested replacement
"error_type": <str>, // Error type, see "Error Categories"
"general_error_type": <str>, // See "Error Categories"
}

When programmatically applying edits, go in reverse start offset (sentence_start + start) order so changes don't affect the offsets of the remaining edits.

For example, consider the sentence: Lets go to the housee. where Sapling returns the following list of edits:

[
{
'sentence_start': 0,
'start': 0,
'end': 4,
'replacement': "Let's",
...
},
{
'sentence_start': 0,
'start': 15,
'end': 21,
'replacement': 'house',
...
}
]

The simplest way to apply the edits to your text is in reverse order:

  1. Replace characters 15-21 with house.
  2. Replace characters 0-4 with Let's.

If the characters for Lets are replaced before housee, the offsets for other edits would need to be updated.

Sample Code​

We provide sample code below for applying edits.

A few things to keep in mind:

  • The edits array is ordered by starting position, though we include logic below to ensure this is the case.
  • For some languages where assignment is by reference, you will want to create a copy of the original string before modifying it.
text = str(text)
edits = sorted(edits, key=lambda e: (e['sentence_start'] + e['start']), reverse=True)
for edit in edits:
start = edit['sentence_start'] + edit['start']
end = edit['sentence_start'] + edit['end']
if start > len(text) or end > len(text):
print(f'Edit start:{start}/end:{end} outside of bounds of text:{text}')
continue
text = text[: start] + edit['replacement'] + text[end:]
return text

Detecting AI-Generated Content​

Use aidetect to score how likely a piece of text is to be AI-generated. We recommend at least 300 characters of text.

from sapling import SaplingClient

api_key = '<api-key>'
client = SaplingClient(api_key=api_key)

text = 'Phone chargers are essential accessories that have become an integral part of modern life. These devices are designed to replenish the battery life of our smartphones and other electronic gadgets, enabling us to stay connected and productive throughout the day. With advancements in technology, phone chargers have evolved significantly, offering various types such as wired, wireless, fast-charging, and portable chargers. The convenience and versatility of phone chargers have revolutionized the way we use our devices, ensuring we can always stay powered up and connected in our fast-paced, digital world.'
result = client.aidetect(text)

print(result['score'], result['ai_fraction'], result.get('verdict'))

for sentence in result['sentence_scores']:
if sentence['score'] >= 0.5:
print(sentence['score'], sentence['sentence'])

The result is of this form, with text and sentence_scores covering all of your input. The response also includes token-level scores, omitted here:

{
"score": 0.98,
"ai_fraction": 1.0,
"verdict": "ai",
"segments": [
{ "start": 0, "end": 90, "score": 0.98 }
],
"low_confidence": false,
"low_confidence_reasons": [],
"sentence_scores": [
{
"sentence": "Phone chargers are essential accessories that have become an integral part of modern life.",
"score": 0.98,
"start": 0,
"end": 90
}
],
"text": "Phone chargers are essential accessories that have become an integral part of modern life.",
"version": "..."
}
  • score rates the whole text from 0 to 1, where a value closer to 1 means more confidence that it's AI-generated.
  • ai_fraction is the share of the text in sentences scoring 0.5 or above, which flags documents that are only partly AI-written.
  • verdict sums both up as human, mixed or ai. It's omitted if the text couldn't be scored.
  • segments and sentence_scores show where in the text the result comes from.
  • low_confidence is true for inputs with a higher false-positive rate, such as text under 500 characters. Show those results hedged rather than as a bare score.

All AI detectors have false positives and false negatives, so treat the result as a signal to review rather than as proof.

Checking Several Texts​

Pass a list of 1–10 texts to score them in one request. The response has a results list with one result per text, in the same order:

response = client.aidetect(['First text to check.', 'Second text to check.'])
scores = [result['score'] for result in response['results']]

Checking Long Documents​

A single request accepts up to 200,000 characters. For long documents, splitting the text into smaller chunks that you score in parallel should lower latency. get_ai_detection.py is a complete script that chunks a text, scores each chunk and combines the results.

More detail on the response and on options that the Python client doesn't expose yet, such as lang, zero_retention, and scoring files or web pages, can be found in the AI Detector API reference.

Processing Files​

The Sapling API can be used to process files as well. Sapling's Edit API currently has a 100,000 character limit, so larger documents will need to be chunked.

Sapling provides a pre-processing API endpoint that helps with chunking. This endpoint breaks long documents into pieces, prioritising splitting on things like page and paragraph breaks in order to preserve overall text context.

from sapling import SaplingClient

file_name = '<FILE_TO_PROCESS>'
api_key = '<api-key>'

text = ''
with open(file_name) as f:
text = f.read().strip()

client = SaplingClient(api_key=api_key)
chunks = client.chunk_text(text, max_length=20000)['chunks']

for chunk in chunks:
edits = client.edits(chunk, session_id=file_name)

More information about the Chunking/Preprocessing endpoint can be found here.

More Details​

More detail on the API request options and response structure can be found here for edits and here for AI detection.

Documentation on SaplingClient is available on Read the Docs.