> ## Documentation Index
> Fetch the complete documentation index at: https://docs.terminal3.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Placeholders in outbound calls

> Send private data to a third-party API without it ever entering your contract, using http-with-placeholders.

When your contract needs to send private data (e.g., PII — name, date of birth, email, etc.) to a third-party API, it does **not** read the values and inline them. Instead it uses the **`http-with-placeholders`** host interface: you put `{{profile.<field>}}` markers in the request, and the host resolves them from the calling user's profile **inside the enclave**, just before the request goes out. The plaintext never enters your WASM.

```
  Agent  →  z:<tid>:contract              →  host (http-with-placeholders)  →  Duffel
   book-offer      templates {{profile.*}}        resolves the markers from        POST /orders
                   into the order body            the calling user's profile,      (real PII)
                                                  then sends the rendered request
   { id, pnr } ◀───────────────────────────────────────────────────────────────  { id, pnr }
```

Your contract in Rust:

```rust theme={null}
use crate::bindings::t3n::host::http_with_placeholders as hwp;

// The {{profile.<path>}} markers are resolved host-side from the calling
// user's profile — this contract never sees the plaintext values.
let body = serde_json::json!({
    "data": {
        "type": "instant",
        "selected_offers": [req.offer_id],
        "passengers": [{
            "id": "passenger_0",
            "given_name":  "{{profile.first_name}}",
            "family_name": "{{profile.last_name}}",
            "born_on":     "{{profile.date_of_birth}}",
            "email":       "{{profile.verified_contacts.email.value}}",
        }]
    }
});

let resp = hwp::call(&hwp::Request {
    method:  "POST".to_string(),
    url:     "https://api.duffel.com/air/orders".to_string(),
    headers: vec![
        ("Authorization".to_string(), format!("Bearer {api_key}")),
        ("Duffel-Version".to_string(), "v2".to_string()),
        ("Content-Type".to_string(), "application/json".to_string()),
    ],
    body: Some(serde_json::to_vec(&body)?),
})?;
// resp.body — Duffel's response (booking id + PNR). The passport/name/DOB
// were substituted by the host; your WASM never held them.
```

Key points:

* **Synchronous.** Like plain `http`, you get the upstream response back in the same invocation — there's no deferred queue.
* **Profile access is gated by the user's delegation.** The markers resolve only when the calling agent is authorized to act for that user (see [Invoke your contract](/developers/adk/get-started/walkthrough/invoke-contract)). A marker your contract isn't permitted to resolve fails with `placeholder not permitted: <marker>`.
* **Egress is the same rule as `http`.** The target host must be on the user's allowed-hosts grant, or the call is denied (`host/http.egress_denied`).
* **Markers reference the user profile schema** — e.g. `{{profile.first_name}}`, `{{profile.date_of_birth}}`, `{{profile.verified_contacts.email.value}}`. Fields the schema doesn't carry yet (passport, title) are supplied by your contract directly.
