← Sign in API tester

Using DBLU to build end user apps

Become a partner

Calling DBLU APIs on behalf of users is reserved to vetted partners. Tell us about your company and your integration; we review every application and get back to you within 48 hours.

Note

DBLU partnerships are granted to companies (not individuals). Once approved, you receive a partner_id and client_secret, and can start the flow described in the next section.

Up to 5,000 characters.

We'll get back to you within 48 hours.
How to get the DBLU key to call the APIs on behalf of the user

The DBLU key (technically request_token) is what your backend sends on every DBLU API call made on behalf of a specific end-user. You obtain it once, after the user has explicitly approved your integration from inside the DoubleU mobile app.

Note

The request_token is scoped to the personas, bundles and duration the user approved. It expires when that grant expires.

Overview

  1. Your app opens the DBLU login page in a browser (full page or popup).
  2. The user signs in to DoubleU.
  3. The user picks which doubles / personas to share and submits.
  4. DBLU sends a push notification to the user's iPhone DoubleU app.
  5. The user approves the bundles and chooses an access duration in the app.
  6. The browser tab navigates to a success screen with the request_token — visible on the page and present in the URL.
  7. You save the token on your backend and use it on subsequent partner API calls.

Prerequisites

  • You have a registered DBLU partner integration with a partner_id and a client_secret.
  • The end-user has installed the DoubleU iOS app, signed in there at least once, and granted push-notification permission.

Step 1 — Open the DBLU login page from your app

From your client (web app, mobile app or desktop app) point the user to:

https://api.dblu.ai/login-page?partner_id=<your_partner_id>

For a partner with id 7865571 the URL becomes:

https://api.dblu.ai/login-page?partner_id=7865571

You have two integration shapes:

  • Full-page redirect — simplest. After approval the browser lands on the DBLU success page; your app reads the request_token from the URL.
  • Popup — recommended for SPAs. Open a popup pointing at the same URL and listen for a postMessage; the popup auto-closes on success.
const popup = window.open(
  'https://api.dblu.ai/login-page?partner_id=7865571',
  'dblu-connect',
  'width=560,height=720'
);

window.addEventListener('message', (event) => {
  const msg = event.data || {};

  if (msg.type === 'DOUBLEU_CONNECTION_SUCCESS') {
    const requestToken = msg.data.request_token;
    // -> send requestToken to your backend and store it
  }

  if (msg.type === 'DOUBLEU_CONNECTION_ERROR') {
    console.error('DBLU connect error:', msg.error);
  }

  if (msg.type === 'DOUBLEU_CONNECTION_CANCELLED') {
    console.warn('User closed the DBLU window');
  }
});

Important

If partner_id does not match an active row in DBLU's clients table, the login page returns an access-denied view and no flow can start.

Step 2 — The user signs in

The user authenticates with their DoubleU credentials. Once signed in, they pick which persona(s) from their vault they want to expose to your integration, then submit.

Step 3 — A push notification is sent to the user's device

On submit, DBLU:

  1. Records a partner_request for this grant.
  2. Sends an APNs push to the user's DoubleU iOS app, with payload type: "partner_request".

The user sees a notification similar to:

Partner <your-app-name> is requesting access to your selected Doubles

Meanwhile, the browser tab parks on a “Partner request sent!” waiting screen that polls in the background for approval.

Step 4 — The user approves bundles and duration in the DoubleU app

Inside the DoubleU iOS app the user picks:

  • The bundles to share for the selected persona.
  • The access duration: 1 hour, 24 hours, or Indefinitely.

When they tap Approve, DoubleU calls POST https://api.dblu.ai/partner-request/accept on their behalf and the browser tab is unblocked.

Step 5 — The token is returned to your app

The success page appears at:

https://api.dblu.ai/access-granted?request_token=<TOKEN>

The token is rendered on the page and is one-click copy-pasteable. If you opened DBLU in a popup (see Step 1) you'll instead receive a postMessage of type DOUBLEU_CONNECTION_SUCCESS carrying the same request_token; the popup will auto-close shortly after.

Tip

Persist the request_token on your backend, indexed by your internal user record. Treat it as a secret — anyone holding it can act on behalf of that user within the approved scope until the grant expires.

Terminology

Term What it is Where it lives
partner_id Your integration's public identifier. Sent in the login URL; safe in clients.
client_secret Your integration's server credential. Server-only; sent as X-Client-Secret.
request_token DBLU key Per-user grant token issued after approval. Returned on the success page and via postMessage.

request_token is not the same as a user session token, an MCP token, or a partner_key. It is the value stored on client_access.request_token in DBLU.

/partner/llm - Augmented LLM with User's Data

Once you hold a request_token for a user, you call POST https://api.dblu.ai/partner/llm to send a prompt that DBLU automatically enriches with the personas, bundles, and governance rules the user approved for your integration.

Important

This is a server-side only endpoint. The X-Client-Secret header is your private credential — never expose it to a browser, mobile app, or any client-side code.

Prerequisites

  • You are an active DBLU partner with a partner_id and client_secret.
  • You hold a valid request_token issued for the target user (see the previous chapter).

Request

Endpoint: POST https://api.dblu.ai/partner/llm

Required headers:

Content-Type: application/json
X-Client-Secret: <your_client_secret>

JSON body:

{
  "prompt": "<the user-facing prompt>",
  "request_token": "<saved DBLU key for this user>",
  "model": "gpt-3.5-turbo",
  "temperature": 0.7,
  "max_tokens": 500,
  "system_message": "You are a helpful AI assistant."
}
Field Required Default Notes
prompt Yes The user-facing instruction. Cannot be empty.
request_token Yes The DBLU key for this user. Must be active and not expired.
model No gpt-3.5-turbo OpenAI model identifier.
temperature No 0.7 Sampling temperature, 0.0 — 2.0.
max_tokens No 500 Maximum tokens in the completion.
system_message No "You are a helpful AI assistant." System role content prepended to the conversation.

What DBLU does behind the scenes

For every call, DBLU:

  1. Authenticates your partner integration from X-Client-Secret.
  2. Resolves request_token to a client_access grant — user, allowed personas, allowed bundles, expiry.
  3. Refreshes last_access_at for the grant.
  4. Decrypts the user's vault and pulls only the traits inside the approved bundles for the approved persona(s).
  5. Appends those traits and any active governance constraints to your prompt as additional context.
  6. Calls the underlying LLM with your system_message + the enriched user message.
  7. Logs the request for billing and governance audit (no plaintext vault data is stored in logs).
  8. Returns the model's response to you.

Note

You do not need to fetch persona data yourself. Just write the prompt as if the user were typing it; DBLU injects the consented context for you and enforces the user's governance rules on the model.

Responses

DBLU separates partner authentication (X-Client-Secret) from application outcome (prompt validation, request_token / grant state, LLM provider errors). Bad client_secret yields HTTP 401 with a FastAPI detail JSON body. Valid partner auth almost always returns HTTP 200 with a PartnerLLMResponse — inspect success and error. An expired or inactive user grant is therefore still 200 with success: false, not 401.

HTTP 401 — partner secret

Returned before the route handler runs if X-Client-Secret is missing or does not match an active row in clients. Body is FastAPI's default error JSON (not PartnerLLMResponse):

  • {"detail":"Client secret is required"} — header absent or treated as empty.
  • {"detail":"Invalid client secret"} — no matching active client.

HTTP 422 — request body validation

If the JSON body does not satisfy PartnerLLMRequest (wrong types, invalid structure for Pydantic), FastAPI returns 422 Unprocessable Entity with a validation error payload. Fix the body and retry.

HTTP 200 — PartnerLLMResponse

When X-Client-Secret is accepted, the handler responds with HTTP 200 and this shape:

{
  "success": true,
  "response": "<model output>",
  "error": null,
  "llm_model": "gpt-3.5-turbo",
  "tokens_used": 412
}
success error response llm_model / tokens_used Meaning
true null Completion text from the model. llm_model: requested model id; tokens_used: total tokens when the provider returns usage (otherwise may be null). Grant resolved; prompt enriched; LLM call completed successfully.
false "Prompt cannot be empty" null Both null. prompt was blank after trim.
false "Request token is required" null Both null. request_token missing or empty in the JSON body.
false "Invalid or inactive request token" null Both null. No active client_access row for this token (unknown token, revoked grant, or is_active false).
false "Access has expired" null Both null. Grant exists but expires_at is in the past. User must re-approve to obtain a fresh request_token.
false "Error processing LLM request: …" null Both null. Unhandled exception during enrichment or the LLM call; error includes the exception message after the fixed prefix.

Optional: many integrations also send Authorization: Bearer <request_token> for consistency; the API does not require it for this route — the token in the JSON body is authoritative.

Examples

curl

curl -X POST https://api.dblu.ai/partner/llm \
  -H "Content-Type: application/json" \
  -H "X-Client-Secret: $DBLU_CLIENT_SECRET" \
  -d '{
    "prompt": "Suggest a weekend plan that fits this user.",
    "request_token": "<saved_request_token>",
    "model": "gpt-4o-mini",
    "max_tokens": 600
  }'

Node.js (server-side)

const res = await fetch('https://api.dblu.ai/partner/llm', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Client-Secret': process.env.DBLU_CLIENT_SECRET,
  },
  body: JSON.stringify({
    prompt: 'Suggest a weekend plan that fits this user.',
    request_token: savedRequestToken,
    model: 'gpt-4o-mini',
    max_tokens: 600,
  }),
});

const data = await res.json();
if (!data.success) {
  throw new Error(data.error || 'DBLU LLM call failed');
}
console.log(data.response);

Python (server-side)

import os, requests

resp = requests.post(
    "https://api.dblu.ai/partner/llm",
    headers={
        "Content-Type": "application/json",
        "X-Client-Secret": os.environ["DBLU_CLIENT_SECRET"],
    },
    json={
        "prompt": "Suggest a weekend plan that fits this user.",
        "request_token": saved_request_token,
        "model": "gpt-4o-mini",
        "max_tokens": 600,
    },
    timeout=60,
)
data = resp.json()
if not data["success"]:
    raise RuntimeError(data.get("error") or "DBLU LLM call failed")
print(data["response"])

Tip

Use tokens_used from the response to track cost on a per-user basis. DBLU also logs every call for the partner's monthly usage report.

Each call is rate-limited and recorded against your partner account. Re-using a revoked or expired request_token always returns success: false; ask the user to re-approve to get a fresh token.

Sample: Run it up (Android) — partner LLM POC

We ship a small open-source Android sample, Run it up, that walks through the same partner pattern as this page: your app sends the user to the DBLU login-page flow (with partner_id), the user approves access in DoubleU, you obtain a request_token for that grant, then your integration calls POST https://api.dblu.ai/partner/llm with X-Client-Secret and that token so DBLU can return personalized model output (the sample uses a running-shoe storefront as a concrete use case). The app opens on a two-column product grid with category filters and a typical ecommerce top bar; use the toolbar icons for Partner settings and Recommendations (DBLU).

Note

The repository is a proof of concept for developers: it may paste request_token manually after approval and is not a production security pattern. For client_secret, the sample normally bakes DBLU_POC_CLIENT_SECRET into the APK via Gradle; it may also receive client_secret from partner POST /login JSON in the WebView. Ship client_secret only on your server in production, and complete the login / token capture flow for your platform.

Clone or fork the sample from https://github.com/dieffe/dblu-android-demo-partner-app (Kotlin, Material 3, Retrofit, Chrome Custom Tab stub for the login URL).

Using DBLU to build business apps

/governance-list - List User's Governance Packages

List the governance packages a DBLU user has created. Useful for MCP-style clients and dashboards that want to display, audit, or attach the user's own governance rule sets to a partner integration. GET https://api.dblu.ai/governance-list

Important

This endpoint authenticates with the user's MCP token — not the partner X-Client-Secret and not a request_token. The MCP token is a personal credential issued to the user; treat it as a secret and never expose it to a browser, mobile app, or any client-side code.

Prerequisites

  • The user has generated an MCP token in their DBLU account.
  • Your server holds that MCP token securely (typically per-user, scoped to that individual).

Request

Endpoint: GET https://api.dblu.ai/governance-list

Required headers:

Authorization: Bearer <mcp_token>

This endpoint takes no query parameters and no body.

Response

On success, returns the user's custom governance packages, ordered alphabetically by name. Built-in DBLU governance packages are intentionally excluded.

{
  "packages": [
    {
      "id": "8c2e7a6f-7d4b-4a3a-9d52-6e2cc8f4b8a1",
      "package_key": "marketing_safe_personal",
      "persona_type": "generic",
      "name": "Marketing — safe personal",
      "description": "Share preferences and biographical context only.",
      "data_permissions": [
        { "bundle": "preferences & tastes", "allowed": true },
        { "bundle": "biographical & demographic", "allowed": true },
        { "bundle": "health & wellness", "allowed": false }
      ],
      "data_retention": "days_90",
      "language_controls": "default_business",
      "language_custom_blocklist": "",
      "is_built_in": false,
      "created_at": "2026-04-18T09:12:34.122000",
      "updated_at": "2026-05-09T17:03:14.011000"
    }
  ]
}
Field Type Notes
id UUID Stable identifier of the governance package.
package_key string Short slug-style key the user can reference internally.
persona_type string Persona scope. New packages use "generic"; legacy values may appear.
name string Human-readable display name.
description string Free-text description.
data_permissions array Per-bundle allow/deny entries that drive what data partners may receive.
data_retention string One of session_only, days_90, until_manual_pii_90.
language_controls string One of default_business, default_custom, custom_only.
language_custom_blocklist string Newline-separated user blocklist. Only consulted for default_custom / custom_only.
is_built_in boolean Always false in this response — built-ins are not returned.
created_at ISO 8601 UTC timestamp the package was created.
updated_at ISO 8601 UTC timestamp of the last update.

Errors

  • 401 {"detail": "Invalid MCP authentication token"} — missing, malformed, or unknown bearer token.
  • 503 — the governance_packages table has not been migrated on this DBLU instance yet. The response body carries a human-readable hint with the migration to run.
  • 500 — unexpected server error.

Tip

An empty packages array is a valid success response — it means the user simply hasn't created any custom governance package yet.

To load the full rules JSON for one package, use GET /get-governance?governance_id=… with the same MCP token (see the next section).

Examples

curl

curl -X GET https://api.dblu.ai/governance-list \
  -H "Authorization: Bearer $DBLU_MCP_TOKEN"

Node.js (server-side)

const res = await fetch('https://api.dblu.ai/governance-list', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${process.env.DBLU_MCP_TOKEN}`,
  },
});

if (!res.ok) {
  throw new Error(`DBLU governance-list failed: ${res.status}`);
}
const { packages } = await res.json();
console.log(`User has ${packages.length} custom governance package(s).`);

Python (server-side)

import os, requests

resp = requests.get(
    "https://api.dblu.ai/governance-list",
    headers={"Authorization": f"Bearer {os.environ['DBLU_MCP_TOKEN']}"},
    timeout=30,
)
resp.raise_for_status()
packages = resp.json()["packages"]
print(f"User has {len(packages)} custom governance package(s).")

MCP tokens are scoped per user. Rotating or revoking the token in DBLU immediately invalidates any client holding the old value; subsequent calls return 401.

/get-governance - Fetch rules for one governance package

After you list packages with /governance-list, call this endpoint with a package id to retrieve the JSON rule payload: data permissions (what may be shared and how), plus retention and language-control settings for that package. GET https://api.dblu.ai/get-governance

Important

Same authentication as /governance-list: the user's MCP token in Authorization: Bearer …. Server-side only.

Prerequisites

  • A valid MCP token for the user who owns the package.
  • A governance_id value taken from the id field of an object in the packages array returned by GET /governance-list.

Request

Endpoint (query string required): GET https://api.dblu.ai/get-governance?governance_id=<UUID>

Required headers:

Authorization: Bearer <mcp_token>

Query parameter:

Parameter Type Notes
governance_id UUID Required. Must match a custom package id from /governance-list for this user.

Response

On success, 200 with a JSON object. The data_permissions array is the primary rule set: each entry has category, policy (allowed, ask_each_time, never, or trace), and optional label.

{
  "governance_id": "8c2e7a6f-7d4b-4a3a-9d52-6e2cc8f4b8a1",
  "package_key": "marketing_safe_personal",
  "name": "Marketing — safe personal",
  "description": "Share preferences and biographical context only.",
  "data_permissions": [
    { "category": "preferences & tastes", "policy": "allowed", "label": null },
    { "category": "biographical & demographic", "policy": "allowed", "label": null },
    { "category": "health & wellness", "policy": "never", "label": null }
  ],
  "data_retention": "days_90",
  "language_controls": "default_business",
  "language_custom_blocklist": ""
}
Field Type Notes
governance_id UUID Same as the package id you passed in.
package_key string Stable slug for the package.
name / description string Display metadata.
data_permissions array Governance rules for data categories.
data_retention string session_only, days_90, or until_manual_pii_90.
language_controls string default_business, default_custom, or custom_only.
language_custom_blocklist string Newline-separated terms when language controls use custom lists.

Errors

  • 401 — missing, malformed, or invalid MCP bearer token (same body as /governance-list).
  • 404 {"detail":"Governance package not found"} — unknown UUID, another user's package, or a built-in package.
  • 422governance_id missing or not a valid UUID.
  • 503 — governance tables not migrated (same hint as /governance-list).
  • 500 — unexpected server error.

Examples

curl

curl -sS -G "https://api.dblu.ai/get-governance" \
  --data-urlencode "governance_id=${GOVERNANCE_ID}" \
  -H "Authorization: Bearer ${DBLU_MCP_TOKEN}"

Node.js (server-side)

const gid = process.env.GOVERNANCE_ID;
const url = new URL('https://api.dblu.ai/get-governance');
url.searchParams.set('governance_id', gid);

const res = await fetch(url, {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${process.env.DBLU_MCP_TOKEN}`,
  },
});

if (!res.ok) {
  throw new Error(`DBLU get-governance failed: ${res.status}`);
}
const rules = await res.json();
console.log(rules.data_permissions);

Python (server-side)

import os, requests

gid = os.environ["GOVERNANCE_ID"]
resp = requests.get(
    "https://api.dblu.ai/get-governance",
    params={"governance_id": gid},
    headers={"Authorization": f"Bearer {os.environ['DBLU_MCP_TOKEN']}"},
    timeout=30,
)
resp.raise_for_status()
rules = resp.json()
print(rules["data_permissions"])

Typical flow: GET /governance-list → pick packages[i].idGET /get-governance?governance_id=… for the full rule JSON for integrations, audits, or MCP tools.

/governance-challenge — Check text or PDF against DENY rules

Submit a governance_id (from /governance-list) plus either plain text or a PDF file. DBLU runs the same deterministic DENY / NEVER checks used in production governance flows. If any rule matches the content, the response has "allowed": false and triggered_rules lists human-readable rule names; otherwise "allowed": true and an empty list. POST https://api.dblu.ai/governance-challenge

Important

MCP bearer authentication (same as /governance-list). Server-side only. Send either form field text or form file file, not both. Uploaded files must be PDF.

Request

multipart/form-data with:

Field Required Notes
governance_id Yes UUID string for the package (id from /governance-list).
text One of text / file UTF-8 text to evaluate. Must be non-empty if no file is sent.
file One of text / file Single PDF; text is extracted server-side. Max 25 MB.

Payloads longer than 500,000 characters are truncated before evaluation.

Response

200 JSON:

{ "allowed": true, "triggered_rules": [] }
{
  "allowed": false,
  "triggered_rules": [
    "Employee / identity (restricted)",
    "Public pricing & commercial terms"
  ]
}

triggered_rules entries are display labels for DENY/NEVER rows whose keyword signatures matched the combined extracted text (same engine as DBLU chat governance).

Errors

  • 400 — missing payload, both text and file sent, file not PDF, empty text extraction, etc.
  • 401 — invalid MCP token.
  • 404 — governance package not found for this user.
  • 413 — PDF larger than 25 MB.
  • 503 / 500 — same migration / server error patterns as other governance MCP routes.

Examples

curl (text)

curl -sS -X POST "https://api.dblu.ai/governance-challenge" \
  -H "Authorization: Bearer ${DBLU_MCP_TOKEN}" \
  -F "governance_id=${GOVERNANCE_ID}" \
  -F "text=Who is your CEO?"

curl (PDF)

curl -sS -X POST "https://api.dblu.ai/governance-challenge" \
  -H "Authorization: Bearer ${DBLU_MCP_TOKEN}" \
  -F "governance_id=${GOVERNANCE_ID}" \
  -F "file=@./briefing.pdf;type=application/pdf"

Python (server-side)

import os, requests

url = "https://api.dblu.ai/governance-challenge"
headers = {"Authorization": f"Bearer {os.environ['DBLU_MCP_TOKEN']}"}
data = {"governance_id": os.environ["GOVERNANCE_ID"], "text": "Who is your CTO?"}
r = requests.post(url, headers=headers, data=data, timeout=60)
r.raise_for_status()
print(r.json())  # {"allowed": bool, "triggered_rules": [...]}

Path spelling is /governance-challenge (with challenge).

Confirm