Check If Text Contains a Substring and Find Its Position
This text contains checker gives you a direct answer to a common validation question: does one piece of text include the substring you expect?
Run — free
It returns a boolean result and the zero-based position of the first match, or -1 when no match exists. You can preserve exact letter casing or ignore uppercase and lowercase differences. The result is deterministic, easy to test, and useful anywhere a workflow needs a quick search condition without regular expressions or fuzzy matching.
Check a substring with an unambiguous result
Provide the complete value in text and the sequence you want to locate in substring. The response includes contains, which is true when a match exists, and position, which identifies where the first match begins. Positions are zero-based, so a substring found at the very beginning has position 0. A missing substring produces false and position -1, keeping absence distinct from a match at the start. This pairing makes the result convenient for people reading it and for software applying a condition. The operation looks for a literal, contiguous substring. Spaces, punctuation, and repeated characters are treated as ordinary text rather than patterns, so symbols do not need regular-expression escaping. If the same substring occurs several times, only the first position is reported. That behavior is particularly useful for validation, where the main questions are whether an expected marker appears and where parsing could begin. An empty substring is rejected because every string technically contains it, which would provide a surprising and unhelpful validation result.
Choose whether letter case matters
The case_sensitive option defaults to true. In that mode, uppercase and lowercase letters must match exactly: searching for “Ready” does not match “ready.” Exact matching is appropriate for identifiers, protocol markers, product codes, and any content where capitalization carries meaning. Set the option to false when you want a practical text check that treats those variants alike, such as finding a status word in user-entered notes or checking whether a heading contains a phrase. Case-insensitive mode normalizes both the source text and requested substring consistently before locating the first occurrence, while the reported position still refers to the original text. The capability does not trim either value or alter punctuation, so leading spaces and punctuation remain meaningful. It also does not perform accent folding, stemming, transliteration, fuzzy comparison, or whole-word detection. For example, a search for “cat” can match the first three letters of “catalog.” Keeping these rules narrow makes every result repeatable and prevents a simple contains check from silently making linguistic assumptions that would be unsuitable for identifiers or automated gates.
Use the position safely in validation workflows
A typical workflow sends a candidate value, checks contains, and branches without having to interpret the numeric field. When a match exists, position can support diagnostics, previews, or a later parsing step. For example, an import check can confirm that a header includes a required token and record where it begins; a test can verify that generated copy contains an approved phrase; or a support tool can detect a known marker in a pasted log line. Always treat the returned position as a JavaScript string index, measured in UTF-16 code units. For ordinary Latin text this aligns with familiar character counting, but some emoji and less common Unicode symbols occupy two code units. The capability deliberately returns the platform-native index so browser and API executions remain identical and callers can use it directly with JavaScript slicing methods. This is a literal search rather than a security sanitizer or content classifier. Validate structure separately when accepting markup, commands, or other sensitive formats, and use a dedicated parser when boundaries, escaping rules, or multiple matches matter to the decision.
What you can do with it
Validate generated text
Confirm that a generated message includes a required disclosure or approved phrase before it is published.
Check import markers
Verify that a pasted header or record contains an expected token and report where parsing can begin.
Test labels and status messages
Assert that application output contains a known label, with exact or case-insensitive matching as needed.
FAQ
What does the result contain?
It returns whether a match exists, the zero-based position of the first match, and the case-sensitivity setting used.
What position is returned when there is no match?
The position is -1 and contains is false. A match at the beginning instead returns position 0.
Is matching case-sensitive by default?
Yes. Set case_sensitive to false when uppercase and lowercase variants should be treated alike.
Does the substring support regular expressions?
No. It is treated as literal text, so punctuation and symbols have no pattern syntax.
Can I search for an empty substring?
No. Empty substrings are rejected because their universal match behavior is rarely useful for validation.
How much does an API request cost?
Each API request costs $0.002. The same deterministic logic can also run in the browser client.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/str/contains \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Release candidate passed validation.","substring":"passed"}'const res = await fetch("https://api.kit.forhosting.com/str/contains", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "Release candidate passed validation.",
"substring": "passed"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/str/contains",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "Release candidate passed validation.",
"substring": "passed"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/str/contains", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"Release candidate passed validation.","substring":"passed"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"Release candidate passed validation.","substring":"passed"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/str/contains", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "Release candidate passed validation.",
"substring": "passed"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "str.contains",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |