Hanzo

SEO

Package seo is search visibility as data: what a phrase is worth, what a site already places for, who places beside it, who links to it, and what one page gets wrong.

Package seo is search visibility as data: what a phrase is worth, what a site already places for, who places beside it, who links to it, and what one page gets wrong.

Base URLhttps://api.hanzo.ai
Operations7
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1150 · SEO — Search Visibility as Data — Draft · read the specification →

/v1/seo is search visibility as data: what a phrase is worth, what a site already places for, who places beside it, who links to it, and what one page gets wrong. Six questions, six typed operations, plus a seventh that prices the other six. It is implemented in hanzoai/cloud at apps/seo, and it is a resale: the measurements come from a commercial upstream, and the price a caller pays is the upstream's own number, read live, never copied into a table here.

Motivation

The measurement — a crawl of the web's link graph and a log of what is searched — is not a thing this platform should rebuild to answer six questions. What is worth owning is the shape of the questions: each operation is one small typed request and one small typed answer, so each is a findable MCP tool and a real SDK method, where a single passthrough of the vendor's sixty-field row would be one tool no model can call and one method whose argument is any (apps/seo/seo.go, "Why six typed ops and not one passthrough").

The failure mode of a reseller is a copy of the vendor's price going stale in a table somebody has to remember to edit. So no table is kept.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

It owns no store

Every operation is one live upstream call answered in the same request — no task ids, no polling, no per-tenant state. The full site audit (a crawl with a job lifecycle) is deliberately absent: it is a job, jobs need a plane, and seoAudit answers the same questions about the one page a caller is looking at.

The address

Everything is under /v1/seo, and every operation is typed: seoKeyword, seoIdea, seoRank, seoCompetitor, seoBacklink and seoAudit as POSTs, and seoRate as a GET. The rate card MUST be free: asking what a call costs must not require the balance that would pay for it, and if the upstream cannot be reached the card comes back empty rather than stale — a price nobody can confirm is not a price (apps/seo/rate.go).

Tenancy

Every operation serves a validated principal and nobody else. The preamble (who, apps/seo/typed.go) distinguishes "a request exists" from "the identity middleware minted this caller from a verified credential" — a surface that checks only the first admits a forged header, and this one spends money at a vendor per call. It fails closed off the HTTP path too, where the CLI projection invokes an op with no request at all.

Money

The surface declares cloud.Metered (plugin/seo/main.go), so the edge charges nothing and the app owns the whole debit, in four ordered steps: refuse without a validated principal; authorize the caller's balance against the vendor's published QUOTE before the call (Bill.Gate, apps/seo/typed.go:139); make the call; debit exactly what the vendor CHARGED, off their answer, never a recomputation (Bill.MeterUsage, apps/seo/typed.go:159). The quote is their price list, served free and cached for an hour; the charge is the cost field on the answer itself, exact to 18 decimals — their cheapest call is $0.00012, which cents cannot hold. A failed call the vendor billed for is still debited, because that money has already left; a refusal that cost nothing debits nothing. A margin MUST NOT be added here — margin belongs in the plan a customer buys, not in the proxy that spends. seo is in the standing list the balance gate reads (spend.go:314).

Events, observability, stage

It publishes nothing on the bus. It emits nothing beyond the request span every route gets; the meter rows the debit writes are the durable record of spend. The stage is beta: a vertical marketing measurement, not part of the self-service agentic-OS core.

Upstream

It derives from no OSS. The upstream is the DataForSEO REST API v3 (apps/seo/dataforseo.go:29), a commercial service reached over HTTPS; no vendor SDK is linked, and the client is this package's own. One vendor account serves every tenant, so the credential is the deployment's own, sealed in KMS (apps/seo/seo.go:104-105) and resolved through the KMS interface at call time — never an environment variable, never logged; every error names the ref and never the value.

Rationale

The alternative to reading the vendor's two numbers at run time is a price table, and a price table is the drift: their price moves, ours does not, and the deployment either eats the difference or overcharges until someone notices. Reading the quote before and the charge after keeps the two ledgers equal with no redeploy on a vendor price change.

Security Considerations

Two assets sit behind this surface: the deployment's vendor account and the caller's balance. The wrong implementation of the preamble — accepting an org from a header rather than a validated principal — lets an unauthenticated caller spend the vendor account with no ledger to debit, which is theft of service that looks like traffic. The wrong implementation of credential custody — an env var, a literal, a logged value — turns one debug log into the vendor account. Both are closed the same way: the principal must be minted from a verified credential (HIP-0026), and the credential exists only as a KMS ref that is read, never held.

Four surfaces

SurfaceReaches this capability asCoverage
RESTseo at its own prefix7 operations
CLIno command reaches it yet — use HTTP or an SDK
SDKno published client declares one yet — regenerating the clients is what adds them
MCPno tool names it yet — use HTTP or an SDK

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/seo/rates, operation seoRate:

hanzo has no subcommand for this operation — the CLI serves only what cloud's live route table confirms. Use HTTP or an SDK.

Answers 200 with object — ok.

Endpoints

EndpointWhat it does
POST /v1/seo/auditFetch one page and report what it gets wrong
POST /v1/seo/backlinksWho links to a target, and how much of it is broken or spam
POST /v1/seo/competitorsThe domains that place for the same phrases
POST /v1/seo/ideasGrow a seed phrase into the phrases nobody named yet
POST /v1/seo/keywordsHow often named phrases are searched, and what a click costs
POST /v1/seo/rankingsEvery phrase a domain already places for, with its position
GET /v1/seo/ratesWhat each call on this surface costs, from the vendor's own list

All Hanzo APIs · Interactive reference

How is this guide?

On this page