Find It, Fix It, Validate It: A Complete Guide to the Aspose.Cells Cloud Search, Replace & Broken-Link API (.NET / Python / Java / cURL)

“Which of our 400 budget workbooks still says Old Vendor Ltd?” “Replace every {{COMPANY}} placeholder in this template.” “Do any of our reports still link to that domain we retired last year?” — three very different questions, one shared answer: a REST call that reads and rewrites spreadsheets in the cloud, with no Excel installed anywhere.

Spreadsheet content problems show up in almost every data-heavy codebase:

  • Content audit — finding every cell, shape or hyperlink that mentions a deprecated product name, an old office address, or a stale legal entity;
  • Template rendering — filling {{PLACEHOLDER}} tokens across sheets before sending a workbook to a customer;
  • Link rot — checking that the URLs embedded in a workbook still resolve, before a compliance or archival job signs it off;
  • Bulk correction — fixing a typo or a misspelled column value across dozens of sheets, or scoped to just one region of one sheet.

The traditional options — a scripting library running on your own box, an Excel macro, or a COM component on a Windows server — all mean you’re the one hosting and scaling spreadsheet logic. Aspose.Cells Cloud turns it into plain REST: send a file (or just a file name), get matches — or a corrected file — back.

This post walks through the APIs exposed by the core SearchController in the Aspose.Cells Cloud microservice (source: src/Aspose.Cells.Cloud.MicroService/Controllers/SearchController.cs), with runnable examples in cURL and C# (.NET), plus notes for Python and Java users.


1. What can the SearchController do?

SearchController covers three capability families, each available at several scopes:

Family What it does Mutates the workbook?
Search Returns the text items matching a query — with their sheet and cell/shape/hyperlink position No
Replace Substitutes matched text, then either streams a corrected file back or saves it into cloud storage Yes
Broken links Validates every http(s) hyperlink in the workbook and returns the ones that fail to resolve No

And each family is exposed at four scopes, which is where the endpoint list gets long:

Scope Search Replace Broken links
Local file → online processing (upload in request body) PUT /v4.0/cells/search/content PUT /v4.0/cells/replace/content PUT /v4.0/cells/search/broken-links
Whole workbook in cloud storage PUT /v4.0/cells/{name}/search/content PUT /v4.0/cells/{name}/replace/content PUT /v4.0/cells/{name}/search/broken-links
Single worksheet in cloud storage PUT /v4.0/cells/{name}/worksheets/{worksheet}/search/content PUT /v4.0/cells/{name}/worksheets/{worksheet}/replace/content PUT /v4.0/cells/{name}/worksheets/{worksheet}/search/broken-links
Single range in cloud storage PUT /v4.0/cells/{name}/worksheets/{worksheet}/ranges/{cellArea}/search/content PUT /v4.0/cells/{name}/worksheets/{worksheet}/ranges/{cellArea}/replace/content PUT /v4.0/cells/{name}/worksheets/{worksheet}/ranges/{cellArea}/search/broken-links

Plus two bonus endpoints that dump every text item in a workbook — see §4:

Goal Endpoint
All text items in a cloud file PUT /v4.0/cells/{name}/search/content/all-textitems
All text items in a local upload PUT /v4.0/cells/search/content/all-textitems

Implementation note: routes are prefixed with v{version:apiVersion}/cells and this controller is [ApiVersion("4.0")], so live calls look like https://api.aspose.cloud/v4.0/cells/.... Everything in this controller is a PUT — even the read-only searches — because a search is modelled as an operation submitted against a resource, not a cacheable GET.

The two operation modes, and why they matter

The controller decorates each action, and the decorator tells you exactly where the file comes from:

  • [OnlineOperation] — stateless. You upload the workbook in the request body (multipart), the server processes it in memory, and nothing is persisted to cloud storage. Perfect for “just tell me what’s in this file” and for one-shot templating.
  • [CloudFileOperation] — storage-backed. The server fetches {name} from cloud storage (optionally under folder=), does the work, and — for the replace operations — writes the result straight back. Nothing travels to your machine.

That single distinction, plus the {worksheet} / {cellArea} route segments, is the entire mental model. The rest of this post is just the details.


2. The response models you’ll actually parse

Both search families return a small, flat, easy-to-bind shape. Search returns SearchResponse:

{
  "Code": 200,
  "Status": "OK",
  "TextItems": [
    {
      "Filename": "Book1.xlsx",
      "Worksheet": "Q3 Forecast",
      "Position": "Cell:B7",
      "Content": "Old Vendor Ltd"
    },
    {
      "Filename": "Book1.xlsx",
      "Worksheet": "Q3 Forecast",
      "Position": "Shape:TextBox 3",
      "Content": "Prepared by Old Vendor Ltd"
    }
  ]
}

Broken-link search returns BrokenLinksResponse:

{
  "Code": 200,
  "Status": "OK",
  "BrokenLinks": [
    {
      "Filename": "Book1.xlsx",
      "Worksheet": "Links",
      "Position": "Hyperlink:Sheet1!B4",
      "LinkAddress": "https://retired-domain.example.com/report"
    }
  ]
}

Two things are worth pointing at immediately:

  1. Position is a tagged location, not just a cell address. It’s prefixed with Cell:, Hyperlink: or Shape:, which is how a single flat list can tell you what kind of thing matched. That tag is the difference between “fix cell B7” and “fix the text box sitting over B7”.
  2. Both models are self-describing for reporting — Filename and Worksheet are returned on every item, so a scan across many workbooks can be concatenated into one audit table without post-processing.

3. Two things you need before you start

  1. Register a free account on the Aspose Cloud Dashboard and create an Application to get a Client ID and Client Secret (the SDKs exchange these for a JWT automatically — no need to build auth headers yourself).
  2. Install the SDK for your language of choice:
<!-- .NET -->
<PackageReference Include="Aspose.Cells-Cloud" Version="25.x" />
# Python
pip install asposecellscloud
<!-- Java (Maven) — note: NOT on Maven Central, so the Aspose repo below is required -->
<repositories>
    <repository>
        <id>AsposeJavaAPI</id>
        <name>Aspose Java API</name>
        <url>https://repository.aspose.cloud/repo/</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-cells-cloud</artifactId>
    <version>26.8</version>
</dependency>

No SDK required? Everything is plain REST, so cURL works just as well (see below). The search/replace/broken-link family is also reachable through typed SDK request models — SearchSpreadsheetContentRequest, ReplaceContentInRemoteRangeRequest, and friends — which is what the C# snippets below use.


4. Scenario A: Search a local file online (nothing persisted)

This is PUT /v4.0/cells/search/content — upload the workbook, get back the matches. No cloud storage, no cleanup, no copy left behind.

① cURL

# 1) First, get a token
curl -X POST "https://api.aspose.cloud/connect/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

# 2) Find "Old Vendor Ltd" anywhere in a local workbook
curl -X PUT "https://api.aspose.cloud/v4.0/cells/search/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "ignoringCase=true" \
  -F "file=@Book1.xlsx"

Narrow it to one sheet, or to one region of one sheet, with two more query parameters:

# Only "Q3 Forecast", only the range A1:F40
curl -X PUT "https://api.aspose.cloud/v4.0/cells/search/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "worksheet=Q3 Forecast" \
  --data-urlencode "cellArea=A1:F40" \
  -F "file=@Book1.xlsx"

② C# / .NET

using Aspose.Cells.Cloud.SDK.Api;
using Aspose.Cells.Cloud.SDK.Request;

var cellsApi = new CellsApi("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");

// Local file, whole workbook
var request = new SearchSpreadsheetContentRequest(
    spreadsheet:  "C:/audit/Book1.xlsx",   // local file path
    searchText:   "Old Vendor Ltd",
    ignoringCase: true);

SearchResponse response = cellsApi.SearchSpreadsheetContent(request);

foreach (var item in response.TextItems)
{
    Console.WriteLine($"{item.Worksheet} | {item.Position} | {item.Content}");
}
// Same call, scoped to one sheet and one range
var scoped = new SearchSpreadsheetContentRequest(
    spreadsheet:  "C:/audit/Book1.xlsx",
    searchText:   "Old Vendor Ltd",
    ignoringCase: true,
    sheetname:    "Q3 Forecast",
    cellarea:     "A1:F40");

The request models also expose password (open a protected workbook) and regoin (region/locale setting) — the same optional parameters you’ll see on the cloud-storage variants as storageName. Note the .NET SDK keeps the regoin spelling; the Python SDK calls the same two settings password and region.

③ Python / Java

The Python SDK wraps the same endpoint family, with the request-model fields mapping to the C# ones one-to-one but in snake_case:

from asposecellscloud.apis.cells_api import CellsApi
from asposecellscloud.requests import (
    SearchSpreadsheetContentRequest,
    SearchContentInRemoteRangeRequest,
)

instance = CellsApi("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")

# Local upload — returned items carry Worksheet / Position / Content
local = instance.search_spreadsheet_content(
    SearchSpreadsheetContentRequest(
        "Book1.xlsx", "Old Vendor Ltd",
        ignoring_case=True, worksheet="Q3 Forecast", cell_area="A1:F40"))

# Range-scoped search of a file already in cloud storage
remote = instance.search_content_in_remote_range(
    SearchContentInRemoteRangeRequest(
        "Book1.xlsx", "Q3 Forecast", "A1:F40", "Old Vendor Ltd",
        ignoring_case=True, folder="Reports"))

for item in remote.text_items:
    print(item.worksheet, item.position, item.content)

The Java SDK follows the same shape with Request objects built via setters:

import com.aspose.cloud.cells.api.CellsApi;
import com.aspose.cloud.cells.model.SearchResponse;
import com.aspose.cloud.cells.request.SearchContentInRemoteRangeRequest;

public class SearchDemo {
    public static void main(String[] args) throws Exception {
        CellsApi api = new CellsApi("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");

        SearchContentInRemoteRangeRequest req = new SearchContentInRemoteRangeRequest();
        req.setName("Book1.xlsx");
        req.setWorksheet("Q3 Forecast");
        req.setCellArea("A1:F40");
        req.setSearchText("Old Vendor Ltd");
        req.setIgnoringCase(true);
        req.setFolder("Reports");

        SearchResponse response = api.searchContentInRemoteRange(req);
        response.getTextItems().forEach(i ->
            System.out.println(i.getWorksheet() + " | " + i.getPosition() + " | " + i.getContent()));
    }
}

If you’re on raw REST, the cURL above is the complete contract — the multipart field simply has to carry the filename, because the service derives the workbook’s name (and therefore its format) from it.


5. Scenario B: Search a file that already lives in cloud storage

When the workbook is already in Aspose Cloud Storage, skip the download entirely — the service reads it where it sits:

# Whole workbook
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/search/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "ignoringCase=true" \
  --data-urlencode "folder=Reports"

# Just one worksheet
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/worksheets/Q3%20Forecast/search/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd"

# Just one range of one worksheet
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/worksheets/Q3%20Forecast/ranges/A1:F40/search/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd"
// Whole workbook in storage
var response = cellsApi.SearchContentInRemoteSpreadsheet(
    new SearchContentInRemoteSpreadsheetRequest(
        name:         "Book1.xlsx",
        searchText:   "Old Vendor Ltd",
        ignoringCase: true,
        folder:       "Reports"));

// One worksheet
var sheetHits = cellsApi.SearchContentInRemoteWorksheet(
    new SearchContentInRemoteWorksheetRequest(
        name:       "Book1.xlsx",
        sheetname:  "Q3 Forecast",
        searchText: "Old Vendor Ltd",
        folder:     "Reports"));

// One range
var rangeHits = cellsApi.SearchContentInRemoteRange(
    new SearchContentInRemoteRangeRequest(
        name:        "Book1.xlsx",
        sheetname:   "Q3 Forecast",
        cellArea:    "A1:F40",
        searchText:  "Old Vendor Ltd",
        ignoringCase: true,
        folder:      "Reports"));

Why three endpoints instead of one? Because the route carries the scope. The whole-workbook endpoint takes a name and a query; the worksheet endpoint takes a worksheet segment; the range endpoint takes a worksheet and a range segment. It’s a clean, cacheable REST shape — and it means the “narrow my search” parameters (worksheet, cellArea) exist only on the local-file endpoint, which is worth remembering when you’re porting a call from one scope to another.


6. Scenario C: Get every text item in a workbook

Sometimes you don’t have a search term — you want the inventory. The all-textitems endpoints return every text item in the workbook, so you can diff two revisions, build a translation glossary, or feed an indexer:

# Cloud file — every text item
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/search/content/all-textitems" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "folder=Reports"

# Local upload — every text item
curl -X PUT "https://api.aspose.cloud/v4.0/cells/search/content/all-textitems" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@Book1.xlsx"

The response is the same SearchResponse envelope, with Content populated for every cell string, hyperlink caption and shape text in the file. This is the “grep the whole workbook” primitive.


7. Scenario D: Replace — and the two very different outcomes

Replace is where the API splits in two, and the split is deliberate:

Local upload → download the corrected file Cloud file → saved back to storage
Endpoint PUT /v4.0/cells/replace/content PUT /v4.0/cells/{name}/replace/content
Returns A file stream (same format you uploaded) CellsCloudResponse { "Code": 200, "Status": "OK" }
Storage touched None The original file is overwritten

① Replace in a local workbook, stream the result back

curl -X PUT "https://api.aspose.cloud/v4.0/cells/replace/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText={{COMPANY}}" \
  --data-urlencode "replaceText=Northwind Traders" \
  -F "file=@template.xlsx" \
  -o template-rendered.xlsx
using Aspose.Cells.Cloud.SDK.Request;

var request = new ReplaceSpreadsheetContentRequest(
    spreadsheet: "C:/templates/template.xlsx",
    searchText:  "{{COMPANY}}",
    replaceText: "Northwind Traders");

// Option A: get the corrected workbook as a Stream
using Stream corrected = cellsApi.ReplaceSpreadsheetContent(request);
using var outFile = File.Create("C:/templates/template-rendered.xlsx");
corrected.CopyTo(outFile);

// Option B: let the SDK write it for you (.NET overload with an output path)
// cellsApi.ReplaceSpreadsheetContent(request, "C:/templates/template-rendered.xlsx");

Two scope parameters are available here as well — sheetname and cellarea — so you can render placeholders in one sheet without touching the rest of the template.

② Replace in a cloud workbook, persist it in place

# Whole workbook, saved back to storage
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/replace/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "replaceText=Northwind Traders" \
  --data-urlencode "folder=Reports"

# One worksheet
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/worksheets/Q3%20Forecast/replace/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "replaceText=Northwind Traders" \
  --data-urlencode "folder=Reports"

# One range
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/worksheets/Q3%20Forecast/ranges/A1:F40/replace/content" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "searchText=Old Vendor Ltd" \
  --data-urlencode "replaceText=Northwind Traders" \
  --data-urlencode "folder=Reports"
// Whole workbook — result saved back into cloud storage
cellsApi.ReplaceContentInRemoteSpreadsheet(
    new ReplaceContentInRemoteSpreadsheetRequest(
        name:        "Book1.xlsx",
        searchText:  "Old Vendor Ltd",
        replaceText: "Northwind Traders",
        folder:      "Reports"));

// Range-scoped
cellsApi.ReplaceContentInRemoteRange(
    new ReplaceContentInRemoteRangeRequest(
        name:        "Book1.xlsx",
        searchText:  "Old Vendor Ltd",
        replaceText: "Northwind Traders",
        worksheet:   "Q3 Forecast",
        cellarea:    "A1:F40",
        folder:      "Reports"));

In-place replacement is destructive by design. The cloud variants overwrite {name} — no backup copy is made. For audit-heavy workflows, keep a revisioned copy (e.g. upload Book1-2026-09-11.xlsx first, then replace) or use the local-upload variant and store the returned stream yourself.


Link checking is the sleeper feature here — it’s the kind of thing that’s tedious to write yourself and embarrassing to skip. All four scopes are available; the local-file variant is the quickest sanity check:

# Local workbook, whole file
curl -X PUT "https://api.aspose.cloud/v4.0/cells/search/broken-links" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@Book1.xlsx"

# Cloud workbook, whole file
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/search/broken-links" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "folder=Reports"

# Cloud workbook, one worksheet
curl -X PUT "https://api.aspose.cloud/v4.0/cells/Book1.xlsx/worksheets/Links/search/broken-links" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "folder=Reports"
var broken = cellsApi.SearchSpreadsheetBrokenLinks(
    new SearchSpreadsheetBrokenLinksRequest(
        spreadsheet: "C:/audit/Book1.xlsx"));

foreach (var link in broken.BrokenLinks)
{
    Console.WriteLine($"{link.Worksheet} {link.Position} -> {link.LinkAddress}");
}

The result is a list you can act on directly: each entry names the sheet, the position, and the offending LinkAddress.


9. What actually gets scanned? (the implementation details that change your expectations)

This is the part that separates a working integration from a surprising one. Reading WorkbookExtension.cs, a search walks three distinct element types in every worksheet:

Element type Source What’s matched Position tag
Cell strings worksheet.Cells The cell’s string value Cell:A1
Hyperlinks worksheet.Hyperlinks Both the link address and its display text Hyperlink:Sheet1!B4
Shapes worksheet.Shapes shape.Text — text boxes, callouts, labels Shape:TextBox 3

And here are the behaviors worth knowing before you design around them:

Only string cells are searched. The scan filters on CellValueType.IsString. A cell holding a number, a date or a boolean is skipped — so a search for 2025 will not match a date-formatted cell whose serial value happens to be 2025. Cells whose formulas evaluate to text are matched, because their value type is string.

Case sensitivity is the only matching option. ignoringCase switches between InvariantCultureIgnoreCase and InvariantCulture comparison — nothing more. There’s no whole-word, regex, or “match entire cell value” mode, despite what the generated API docs might suggest. If you need those semantics, pull the all-textitems inventory and filter it on your side.

Replace is plain, case-sensitive, literal substitution. Search’s ignoringCase flag has no equivalent on Replace — the replace path has no case-insensitivity parameter at all. And the two code paths differ internally: a whole-worksheet replace delegates to the engine’s Worksheet.Replace(...), while a range-scoped replace iterates the range and rewrites each string cell with cell.StringValue.Replace(...). Practical consequence: range-scoped replace only touches string cells, and it’s literal, not pattern-based.

Hyperlink matches report the address, not the caption. When a search matches a hyperlink’s display text, the returned Content is still hyperlink.Address. If you’re rendering a “found it here” report, expect the URL — the caption isn’t echoed back.

Broken-link checking only looks at http:// and https://. mailto: links, local file paths, and internal #Sheet1!A1 anchors are skipped entirely — they’re never tested, so they’ll never appear in the results. It also scans hyperlinks attached to shapes as well as cells, which is exactly where dead links like to hide. Note that a broken-link Position is built from the raw hyperlink.Area (not the friendlier Area.ToRangName() the search endpoints use), so don’t assume the two families render positions identically — parse the Worksheet + LinkAddress pair if you need a stable key.

“Broken” means “unreachable”, not “wrong status code”. The check issues an HTTP HEAD request per link and treats any response — including 404 or 500 — as accessible; it only marks a link broken when the request throws (DNS failure, refused connection, TLS error, timeout). So treat the output as “links that could not be contacted”. If what you actually need is “links that don’t return 2xx”, you’ll want to re-check the returned LinkAddress values yourself.

cellArea is accepted but not applied on the broken-link endpoints. The BrokenLinks implementation filters by worksheet name only; the cell-area argument is passed in but never used. In practice that means .../ranges/A1:F40/search/broken-links currently behaves the same as /worksheet/search/broken-links for the same worksheet — links are validated across the whole sheet. Scope your reports accordingly (or filter the returned list by position) rather than assuming the range narrows the check.

None of these are blockers — they’re the difference between a feature that “works in the demo” and one that behaves predictably in production.


10. Error handling & operational notes

The contract the controller documents

Status Meaning Typical cause
400 Bad Request Invalid URL/parameters — e.g. a malformed cellArea, or a missing searchText
401 Unauthorized Authentication failed or no valid credentials were provided
404 Not Found Source file is not accessible in cloud storage
500 Server Error The service hit an anomaly while obtaining spreadsheet data

Internally, every action wraps its work in a helper (WorkbookElementOperation, UpdateWorkbookElementOperation, UpdateWorkbookElementOperationAndSave) that catches failures and rethrows them as a CellsApiException prefixed with the operation name — for example SearchContentInRemoteRange Error Message: .... That operation name is the string you’ll see in your logs:

Operation name Endpoint family
SearchSpreadsheetContent, SearchContentInRemoteSpreadsheet Local / remote search
SearchContentInRemoteWorksheet, SearchContentInRemoteRange Scoped remote search
ReplaceSpreadsheetContent Local replace (streams result)
ReplaceContentInRemoteSpreadsheet, ReplaceContentInRemoteWorksheet, ReplaceContentInRemoteRange Remote replace (saves result)
SearchSpreadsheetBrokenLinks, SearchBrokenLinksInRemoteSpreadsheet, SearchBrokenLinksInRemoteWorksheet, SearchBrokenLinksInRemoteRange Link validation

Optional parameters the SDKs expose

Beyond the documented parameters, the cloud-storage request models all carry the same three optional settings:

  • password — open a password-protected workbook (searches don’t require write access, so read-protected files work fine);
  • storageName — target a custom/attached cloud storage instead of the default one (Python: storage_name);
  • regoin — the region/locale setting used when interpreting the workbook (Python spells this region).

Authentication

All SDKs authenticate automatically with OAuth 2.0 client_credentials and a JWT; when using raw cURL, call /connect/token once first (see §4, ①).


11. Why Aspose.Cells Cloud for search & replace?

  • ✅ Zero local dependencies — no Office/Excel, no COM, no scripting host. Callable from any language, any platform (Linux, containers, serverless);
  • ✅ Deeper than text search — cells, shapes and hyperlinks are all first-class match targets, which is exactly where real spreadsheets keep their content;
  • ✅ Scoped operations — workbook / worksheet / range, so you can fix one region of one sheet without rewriting a 50 MB file;
  • ✅ Cloud-native or stateless — files already in storage never leave it; local-upload operations persist nothing at all;
  • ✅ High-fidelity rewriting — replacements are applied through the Aspose.Cells engine and re-serialized, so formulas, styles, charts and formatting survive the round trip;
  • ✅ One shape across SDKs — the cURL, .NET, Python and Java surfaces map 1:1, so the pattern you copy from one language works in the others.

Get started now: register on the Aspose Cloud Dashboard, create an Application, drop your credentials into any snippet above, and point it at one of your own workbooks — you’ll have a working content audit in minutes.

Found this useful? Star, bookmark, or share it — and tell us in the comments what you’re using workbook search and replace for.