# Classification and data handling

Every capability request can declare what kind of data it carries. That declaration is a constraint you accept, not a permission you are granted. This page covers what each classification commits you to, how Hydracept resolves the effective classification, and how to build a product that handles restricted data correctly.

Declaring a classification

context.dataClassification on the invoke or job envelope takes one of six values:

ValueMeaning
PublicNothing sensitive. Safe to publish as-is.
InternalOrdinary business content that should not be published.
ConfidentialContractually or commercially restricted content.
PrivateUserContentContent belonging to an identifiable end user.
ManuscriptLong-form authored work under revision.
ChildDirectedContent from or about a child, under a parental or guardian consent record.

The default is Internal. The enum is published on CapabilityContext in the public OpenAPI document.


{
  "context": {
    "productId": "my-product",
    "projectId": "cpr_...",
    "dataClassification": "PrivateUserContent"
  },
  "input": { "messages": [{ "role": "user", "content": "..." }] },
  "idempotencyKey": "<uuid-v4>"
}

context.retentionRequirement is a second, independent declaration: HashesOnly, Transient, EncryptedShortTerm, ProductManaged, or ProductionAsset.

Together these two fields describe the handling you need. Neither one authorizes anything by itself.

Two facts decide the effective classification

Hydracept computes the classification an execution must actually honor from two inputs, and the stricter one always wins:

  1. The project's server-side binding. A project provisioned for restricted traffic stays restricted even when a caller declares something weaker. A request cannot lower this.
  2. The request's declaration. A request can raise the floor but never lower it.

So there are three possible outcomes:

SituationResult
Declaration is stricter than the project bindingThe stricter declared class is honored
Declaration matches the bindingThe binding is honored
Declaration is weaker than the bindingThe request is rejected as a downgrade attempt

A downgrade attempt returns 403 with code: "classification_downgrade_rejected". This is a terminal caller bug, not a transient condition — never retry it with the same body, and never treat a re-declaration as a fix.

The practical rule: declare the class your data actually is. Declaring Internal for content that belongs to a user, or for content about a child, does not make the request more likely to succeed. It makes it more likely to be refused, and it is the single most common way integrations break at the classification boundary.

Restricted classes and route approval

Each classification carries handling obligations that determine whether content may leave the platform at all:

When no route may carry the data, the API returns 403 with code: "processing_not_permitted" and retryable: false. When a profile covers the class but not this particular route, the code is processing_route_unavailable — a different signal, meaning the class is supported in principle but this provider/model is not approved for it.

Both checks run before credential resolution, input materialization, cost reservation, and provider dispatch. An ineligible route never produces a paid provider call.

ChildDirected is refused by default

ChildDirected is the most restrictive class. It carries all of the following obligations at once:

Because of this, ChildDirected requests are rejected with 403 processing_not_permitted today, by design. No managed route has an approved processing profile for child-directed traffic.

Consequences you should plan for:

If you need child-directed processing for a specific workflow, that is an operator decision backed by per-route contractual and technical evidence. It is not a client-side setting.

Processing profiles

A processing profile is a versioned, operator-approved record that answers two questions for one classification:

  1. Which (provider, model, region, capability) combinations may serve it.
  2. How its payloads may be retained afterward.

Profile behavior you can rely on:

Profiles apply where a capability opts in, and always where a project carries a floor. That keeps capabilities outside the registry on their existing behavior while restricting ones with stricter obligations.

Retention declarations

context.retentionRequirement is checked against what your credential is permitted to request. A retention value your principal may not use is rejected with code RetentionViolation and details.reasons: ["retention_not_allowed"].

Choose the weakest retention that meets your obligation, not the strongest you can get away with:

ProductionAsset on a synchronous invoke is unusual and is flagged as such; durable jobs are the right shape for artifacts.

Building against these rules

A pattern that holds up:

  1. Classify in the product, not at the API boundary. Decide the classification where you know what the data is, and pass that value through unchanged. Do not infer it from a prompt scan.
  2. Fail closed on every non-success. processing_not_permitted, processing_route_unavailable, and classification_downgrade_rejected all mean "do not proceed with this data on this route." They are not retryable and there is no silent fallback to an unapproved route.
  3. Keep restricted data out of identifiers. Do not put names, addresses, or transcript text into idempotency keys, external references, tags, or metric labels. Hydracept does not send externalRef, tags, or metadata to providers, but your own telemetry and logs are your responsibility.
  4. Treat a provider failure as unknown, not as approval. If a classifier or an ingestion route is unavailable, the safe outcome is a safe fallback in your product — never an unmoderated or unverified pass-through.
  5. Verify provider eligibility yourself when you introduce a new dependency. Hydracept's route approvals cover Hydracept's dispatch. Any third-party service your product calls directly is outside them.