Browse docs
previewThese docs describe OpenIntents as it is being built. Endpoints and payloads may change before launch.

Secrets

A secret is something about a payer that a purchase may need and nobody else should see: a shipping address, a phone number, a shop password, a loyalty number. You store it once, and an intent names it. The agent that reads the shop's page is given only a handle (sec_…), its label and a mask. The value is typed into the page by the worker, and it is never returned by any endpoint, tool, event or webhook.

Secrets are write-only. You can create, replace, list and delete them. There is no way to read one back, so keep your own copy if you need it.

The values you send pass through your request, and through an MCP tool call they pass through the model's context. Where the payer can enter a value themselves, prefer that to sending it through an agent.

The secret object

json
{
  "id": "sec_5a1c93d2e0b4",
  "object": "secret",
  "customer": "cus_42",
  "kind": "address",
  "label": "Home",
  "mask": "London, GB",
  "public": { "city": "London", "country": "GB" },
  "shops": [],
  "default": false,
  "mode": "live",
  "entered_by": "developer",
  "version": 1,
  "created_at": "2026-10-13T08:30:00Z",
  "updated_at": "2026-10-13T08:30:00Z",
  "expires_at": null,
  "last_used_at": null
}
FieldTypeDescription
idstringThe handle, prefixed sec_.
customerstringThe customer it belongs to; null for your account's own.
kindstringOne of the kinds below.
labelstringThe name you gave it. Agents see it, so never put a value in it.
maskstringWhat a person may be shown to recognise it, such as London, GB.
publicobjectThe few fields kept in the clear (a city, a loyalty program).
shopsarrayThe shops (domains) it may be used on. Empty: any shop the purchase names.
defaultbooleanOffered first among the payer's secrets of its kind.
modestringtest or live, from the key that wrote it. A test key never sees a live secret.
entered_bystringdeveloper (the API) or payer (their own page).
versionintegerCounts replacements.

Kinds and fields

Every fields value is text. Fields not listed for a kind are refused.

KindFields (sealed)Public
addressrecipient_name, line1, line2, postal_code, phonecity, region, country
contactgiven_name, family_name, email, phonenone
identity_documentnumber, full_name, date_of_birth, expirydocument_type, issuing_country
shop_passwordusername, passwordshop
loyaltymember_number, pinprogram

A shop_password needs exactly one entry in shops, and is used nowhere else. A payer may have up to 50 secrets in each mode, and one secret may hold at most 4 KB.

Create a secret

POST/v1/secrets
bash
curl https://api.openintents.io/v1/secrets \
  -H "Authorization: Bearer $OPENINTENTS_API_KEY" \
  -H "Idempotency-Key: addr-cus_42-1" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cus_42",
    "kind": "address",
    "label": "Home",
    "fields": {
      "recipient_name": "Ada Lovelace",
      "line1": "12 Example Street",
      "postal_code": "N1 9GU",
      "city": "London",
      "country": "GB"
    }
  }'
ParameterTypeDescription
customerstringThe customer it is for. Leave out for your account's own.
kindstringRequired.
labelstringRequired, at most 60 characters.
fieldsobjectRequired. The kind's fields, as text.
shopsarrayDomains it may be used on.
defaultbooleanMake it the payer's default for its kind.
expires_atstringAn RFC 3339 time after which it is deleted.

It answers 201 with the secret object. A request with an Idempotency-Key that is sent again with the same body gets the same answer. The same key with any change in the body, values included, is 409. The comparison is made on a keyed digest of the whole request, and the stored record holds nothing from which a value could be guessed.

Replace a secret

PUT/v1/secrets/{id}

Sends the whole secret again, in the same shape as a create. There is no partial update. The old values are deleted and version goes up. A secret cannot move to another customer.

List and get

GET/v1/secrets?customer=cus_42&kind=address
GET/v1/secrets/{id}

Both return the object above and nothing more. customer and kind narrow a list. Without customer, the list is your account's own.

Delete a secret

DELETE/v1/secrets/{id}

Answers 204. The values are gone for good.

See how a secret was used

GET/v1/secrets/{id}/uses

Every use of a secret, newest first, paged with limit and cursor: when it was created, replaced or deleted, when a worker typed a field into a shop, and when a use was turned away. Each secret_use has action (create, replace, delete, expire, fill or refuse), actor (worker, api, payer, developer or system), outcome (ok or refused), the reason for a refusal (shop_not_allowed, expired or wrong_use), and where they apply the field, field_names, shop and intent. It never holds a value or a mask. The history of a deleted secret stays readable. Your dashboard shows the same under Secrets.

Let the payer enter it themselves

To keep a value out of your code and your agent altogether, ask the payer to type it on a page of ours:

bash
curl https://api.openintents.io/v1/customers/cus_8f2a/secret_requests \
  -H "Authorization: Bearer $OPENINTENTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kinds": ["address", "contact"], "return_url": "https://app.example.com/done" }'
json
{
  "id": "sr_Q7mX2kLp0aVbN4cR8sTuYw1e",
  "object": "secret_request",
  "customer": "cus_8f2a",
  "kinds": ["address", "contact"],
  "url": "https://openintents.io/vault/sr_Q7mX2kLp0aVbN4cR8sTuYw1e",
  "expires_at": "2026-01-02T10:00:00Z"
}

Send the payer to url. It works once, and ttl_seconds (60 to 604800, default one day) sets how long it lasts. The page sends what the payer types straight to OpenIntents, sealed, with no referrer, and never shows it back. The secrets it makes have entered_by: payer.

When the payer is done, an endpoint that names secret_request.completed is sent the request with the handles it made (no values). You can also read it with GET /v1/secret_requests/{id}: state is pending, done or expired, and secrets lists the handles.

Use secrets in an intent

On POST /v1/intents, two optional parameters say what the purchase may use:

ParameterTypeDescription
secretsarrayHandles of the payer's secrets the purchase may use. Left out: the payer's defaults. []: none.
shopsarrayDomains you let the purchase fill secrets on. ["*"] allows any shop, for contact and address secrets only.

A handle that is not the payer's own, is from the other mode, or does not exist is 404 with param: secrets. An expired one is 400.

Errors

StatusCodeWhen
400invalid_requestA field fails its rule. The error names the field and the rule, never the value.
404not_foundThe secret is not yours, is from the other mode, or is gone.
409idempotency_key_reusedThe Idempotency-Key was used with a different body.
409too_many_secretsThe payer already has 50 in that mode.
413invalid_requestThe request is over 16 KB.
429too_many_writesA key may write 60 secrets a minute. Retry-After says when to try again.
503secrets_unavailableSecrets cannot be stored right now.

MCP tools

The MCP server has create_secret, list_secrets and delete_secret, with the same arguments and results. There is no tool that reads a secret.