Download .md

Start

What KydHub solves

KydHub (Know Your Data Hub) is a verified identity and data hub. It allows a company such as Acme to query authorized data about people and companies without collecting, validating, and maintaining that information on its own.

The idea is simple: the person or company keeps its data in KydHub, KydHub acts as the trusted source, and each integration receives only the fields it needs and is authorized to read.

Important: You do not need to use KydHub Login to use the Data API. If Acme already has its own user system, it can keep that system and use KydHub only to query authorized data from its backend.

Available capabilities now

Query a person

Acme can query authorized Bob Carter data using your KYD, e.g. BOB.CARTER, or a public ID per_....

Person addresses

A company can request only the registered addresses it needs, such as shipping, billing or residence addresses, depending on authorization.

Query a company

Acme may query authorized data from another company by company KYD or Public ID comp_00000000-0000-4000-8000-000000000042, without using KydHub as an open directory.

Company addresses

You can also query addresses or registered locations of a company, when the use case allows.

Login with optional KWID

If you want to delegate login or SSO, KydHub offers OAuth/OIDC. If not, you can skip it and just use Data API.

Advanced security

Server-only API keys, signed webhooks and optional post-quantum encryption for sensitive payloads.

Integration by stack

Recipes for Python/FastAPI, Node.js/Express, Go and desktop apps like Tauri, Electron and Rust.

View integration recipes

What the public API does not do

The public Data API is read-only. Companies do not modify people's data or manage API keys, OAuth clients or webhooks through public APIs; this is done from the KydHub portal.

For companies and people

KydHub connects two needs: companies that need reliable data and people who want to control what information they share.

For companies

  • You query verified data without manually requesting it again.
  • You request only the fields necessary for your use case.
  • You use API keys server-to-server from the Acme backend.
  • You can keep your current login and use KydHub only as a Data API.
  • If needed, you can also offer Login with KWID/OIDC.
  • You receive signed webhook events when the flow requires it.

For people

  • You keep your identity and data in a controlled place.
  • You authorize which company can read which information.
  • You avoid repeating name, profile or addresses in each integration.
  • Companies read authorized data, but do not modify your data via public API.
  • Your KYD, for example BOB.CARTER, works as a verifiable identifier.

Services available at this stage

ServiceWhat it allowsWho uses it
PersonConsult authorized data of a person, such as identity or basic profile.Acme backend with API key.
Person addressesQuery a person's registered addresses when flow and permissions allow.Acme backend with API key.
CompanyQuery authorized data from another company using company KYD or Public ID comp_00000000-0000-4000-8000-000000000042.Acme backend with API key.
Company addressesCheck addresses or registered locations of a company.Acme backend with API key.
Typical sequence: Acme creates an API key in the portal, keeps the secret in its backend, requests only the fields it needs, and shows only the response authorized by KydHub in its system.
Intentional limit: For now, the public API does not create, edit or delete people, companies, addresses, API keys, OAuth clients or webhooks. Those administrative actions live in the portal.

Key concepts

These names appear in almost all examples. The idea is that you know what identifies who before copying a request.

KYD / KWIDHuman-readable public identifier. For a person we use examples like BOB.CARTER. For companies, the business KYD that KydHub has registered is used.
person_idopaque public person ID, for example per_.... It is stable for integrations and does not reveal internal UUIDs.
company_idopaque public ID of a company, for example comp_00000000-0000-4000-8000-000000000042. It is used when you already know the company on KydHub.
fieldsExact list of fields you want to read. KydHub recommends asking for the bare minimum: fewer fields, less exposure, and less authorization friction.
scopePermission that enables a family of fields or actions. The public data API uses read scopes; administrative actions live in the portal.
grantActive authorization that allows a company to read certain data about a person. If it does not exist or does not cover the requested field, KydHub must reject the query.

Before you start

Before making a call, separate three things: the environment where you are going to test, the credential that authorizes the Acme backend, and the identifier of the resource you want to query.

Environments and URLs

ThatValorWhen to use
Dev Dashboardhttps://dev.dashboard.kydhub.comBrowser surface for KydHub dashboard in dev.
Dev Auth / OAuth endpointshttps://dev.auth.kydhub.comTarget canonical dev host for OAuth/OIDC/Login endpoints. Until rollout finishes, read exact endpoints from discovery.
Dev Docshttps://dev.docs.kydhub.comDeveloper documentation in dev.
Data API basehttps://dev.api.kydhub.com/api/v1Server-to-server queries with API key. It does not require you to use KydHub OAuth.
OIDC Issuerhttps://dev.kydhub.comStable issuer. Validate that the iss of the token matches this value.
OIDC Discoveryhttps://dev.kydhub.com/.well-known/openid-configurationSource of truth for authorization, token, UserInfo and JWKS endpoints.

Minimum authentication for Data API

To query data, Acme sends an API key from its backend. This key identifies the company and allows applying limits, scopes, auditing and permissions.

QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json
AtributoWhat isRegla
X-API-KEYCompany server-to-server secret.Never put it in frontend, mobile apps, repositories or public documentation.
Content-TypeRequest body format.Use application/json in examples with body JSON.

Identifiers you are going to use

AtributoExampleWhen to use it
person_kydBOB.CARTERWhen you know the person's public KYD/KWID.
person_idper_...When you already have that person's opaque public ID.
company_kydACME.SUPPLIERWhen you consult data about a company for its business KYD.
company_idcomp_00000000-0000-4000-8000-000000000042When you already have the opaque public ID of that company.
fields["full_name", "addresses.city"]List of requested fields. I ordered only what was necessary for your flow.
service_name"acme-checkout"Technical slug of the internal service making the query. It must be lowercase ASCII with hyphens.
response_profile"standard"Full JSON structure: standard, summary, compliance or audit.
format{ "names": "surname_first_comma", "addresses": "postal" }Presentation rules for authorized values: names, addresses, text, dates and locale.

Response formatting: profile vs presentation

KydHub separates three decisions so integrations stay unambiguous:

AttributeWhat it decidesExample
fieldsWhich data Acme requests.name, addresses.shipping, profile.occupation
response_profileHow the full JSON payload is organized.standard, summary, compliance, audit
formatHow authorized values are presented.surname first, uppercase, no accents, postal address, localized date

format is data presentation. It does not grant permissions, add fields, or replace scopes/Data Grants. If a field is not authorized, KydHub will not return it even if a formatting rule exists for that field.

Response profiles (response_profile)

These profiles do not change name/address casing or text values; they only change the overall payload structure.

response_profileUseReal difference
standardNormal integrations, SDKs, storage and webhooks.Returns requested fields in the canonical fields structure.
summaryUI, cards, checkout, support and listings.Returns a shallower human-readable summary for quick display.
complianceOnboarding, formal review, verification and evidence.Groups formal/verified data and compliance signals; it is not text formatting.
auditLogs, debugging and access evidence.Returns request/authorization/result traceability without expanding unnecessary business data.

Presentation format (format)

The client can ask KydHub to preformat values so each application does not have to implement the same rules.

{
  "fields": ["name", "addresses.shipping", "profile.occupation", "created_at"],
  "response_profile": "standard",
  "format": {
    "names": "surname_first_comma",
    "addresses": "multiline",
    "text": "locale_title_case",
    "dates": "localized_datetime",
    "locale": "es-US",
    "include_raw": true
  }
}

Response when authorization is missing

If the service does not have an approved data grant yet, the Data API returns an actionable error. It does not expose internal UUIDs and it does not use the portal response wrapper.

{
  "status": "error",
  "error": {
    "code": "data_grant_required",
    "person_kyd": "BOB.CARTER",
    "service_name": "acme-checkout",
    "requested_fields": ["name"],
    "required_permissions": [
      {"permission": "person.identity:read", "reason": "Required to identify the requested person in the Data API flow."},
      {"permission": "person.name:read", "reason": "Required to read the requested field: name."}
    ],
    "resolution": {
      "action": "request_data_grant",
      "endpoint": "POST /api/v1/data-grants/requests",
      "request_body": {
        "subject_type": "person",
        "subject_kyd": "BOB.CARTER",
        "requested_scopes": ["person.identity:read", "person.name:read"],
        "purpose": "Allow acme-checkout to read the person's name.",
        "external_reference": "acme-checkout"
      }
    }
  }
}

When the response is successful, person_id always uses the per_ prefix; the internal person-table UUID is never exposed.

Expected response:

{
  "person_id": "per_7Q9M2K4R",
  "person_kyd": "BOB.CARTER",
  "response_profile_used": "standard",
  "format_used": {
    "names": "surname_first_comma",
    "addresses": "multiline",
    "text": "locale_title_case",
    "dates": "localized_datetime",
    "locale": "es-US",
    "include_raw": true
  },
  "fields": {
    "name": {
      "raw": "Bob José de la Cruz O'Neill García",
      "formatted": "O'Neill García, Bob José de la Cruz"
    },
    "addresses": {
      "shipping": {
        "raw": {
          "line1": "1200 Brickell Ave",
          "line2": "Suite 800",
          "city": "Miami",
          "region": "FL",
          "postal_code": "33131",
          "country": "US"
        },
        "formatted": "1200 Brickell Ave\nSuite 800\nMiami, FL 33131\nUS"
      }
    },
    "profile": {
      "occupation": {
        "raw": "software engineer",
        "formatted": "Software engineer"
      }
    },
    "created_at": {
      "raw": "2026-06-30T18:40:00Z",
      "formatted": "30/06/2026 18:40 UTC"
    }
  }
}

Proposed name formats

Name field granularity: name returns the complete authorized name. name.primary returns first given name + first family name for compact UI. name.parts returns structured arrays: given_names, middle_names, family_names. format.names controls presentation order/case; it must not silently drop second names or second surnames.

Base value used in examples:

{
  "given_names": ["Bob", "José"],
  "middle_names": ["de la Cruz"],
  "family_names": ["O'Neill", "García"],
  "full_name": "Bob José de la Cruz O'Neill García"
}
format.namesformatted outputRecommended use
as_recordedBob José de la Cruz O'Neill GarcíaMaximum fidelity to the stored value.
given_firstBob José de la Cruz O'Neill GarcíaNormal end-user UI.
surname_first_commaO'Neill García, Bob José de la CruzCRM, reports and administrative lists.
surname_first_spaceO'Neill García Bob José de la CruzOlder systems that do not accept commas.
locale_title_caseBob José de la Cruz O'Neill GarcíaLocale-aware capitalization.
upperBOB JOSÉ DE LA CRUZ O'NEILL GARCÍADocuments/reports preserving accents.
lowerbob josé de la cruz o'neill garcíaVisual normalization or soft matching.
asciiBob Jose de la Cruz ONeill GarciaNon-Unicode systems or simple search.
ascii_upperBOB JOSE DE LA CRUZ ONEILL GARCIALegacy/regulatory systems that require uppercase ASCII.
initialsBJDCONGAvatars or ultra-compact views.
initials_dottedB. J. O. G.Avatars, compact signatures or UI.

Example request:

{
  "fields": ["name"],
  "format": {
    "names": "ascii_upper",
    "include_raw": true
  }
}

Response:

{
  "name": {
    "raw": "Bob José de la Cruz O'Neill García",
    "formatted": "BOB JOSE DE LA CRUZ ONEILL GARCIA",
    "format_used": "ascii_upper"
  }
}

Proposed address formats

Base value used in examples:

{
  "line1": "1200 Brickell Ave",
  "line2": "Suite 800",
  "city": "Miami",
  "region": "FL",
  "postal_code": "33131",
  "country": "US"
}
format.addressesformatted outputRecommended use
as_recorded1200 Brickell Ave, Suite 800, Miami, FL, 33131, USFidelity to the original record.
single_line1200 Brickell Ave, Suite 800, Miami, FL 33131, USCards, checkout, dashboards and tables.
multiline1200 Brickell Ave\nSuite 800\nMiami, FL 33131\nUSPDFs, documents and postal blocks.
postal1200 Brickell Ave\nSuite 800\nMiami FL 33131\nUNITED STATESPhysical shipping and country-specific logistics.
compactMiami, FL, USProfile, search or compact lists.
uppercase1200 BRICKELL AVE, SUITE 800, MIAMI, FL 33131, USLabels and systems that require uppercase.
ascii_upperAV. JOSE MARIA MORELOS #123, MERIDA, YUCATAN, MXAccented addresses for legacy systems.

Example with postal:

{
  "address": {
    "formatted": "1200 Brickell Ave\nSuite 800\nMiami FL 33131\nUNITED STATES",
    "format_used": "postal",
    "country_format_used": "US"
  }
}

Proposed text and metadata formats

Applies to fields such as profile.occupation, company.display_name, industry, city, region or visible labels.

Base value:

Inteligencia Artificial & Automatización
format.textformatted outputRecommended use
preserveInteligencia Artificial & AutomatizaciónPreserve the original value.
upperINTELIGENCIA ARTIFICIAL & AUTOMATIZACIÓNUppercase reports or UI.
lowerinteligencia artificial & automatizaciónVisual normalization.
locale_title_caseInteligencia artificial & automatizaciónReadable titles with locale-aware casing.
asciiInteligencia Artificial & AutomatizacionSystems without full Unicode support.
ascii_upperINTELIGENCIA ARTIFICIAL & AUTOMATIZACIONLegacy, matching or rigid exports.
sluginteligencia-artificial-automatizacionURLs, keys, anchors or internal integrations.

Proposed date formats

Base value:

2026-06-30T18:40:00Z
format.datesformatted outputRecommended use
iso2026-06-30T18:40:00ZAPIs, storage and interoperability.
date2026-06-30Day-level reports.
datetime2026-06-30 18:40:00 UTCHuman-readable logs.
localized_date30/06/2026Localized UI.
localized_datetime30/06/2026 18:40 UTCLocalized UI with time.

Invalid format error

{
  "error": "invalid_format",
  "message": "Unsupported presentation format.",
  "field": "format.names",
  "allowed_formats": [
    "as_recorded",
    "given_first",
    "surname_first_comma",
    "surname_first_space",
    "locale_title_case",
    "upper",
    "lower",
    "ascii",
    "ascii_upper",
    "initials",
    "initials_dotted"
  ]
}

API keys and credentials

To use the Data API, Acme needs an API key created from the KydHub portal. This key identifies the company querying, applies usage limits and allows auditing which fields were requested.

Where to get an API key

StepTo doResult
1Enter the KydHub portal with your username.You access your personal or business space.
2Switch to the business context, for example Acme.The credentials remain associated with that company.
3I opened Company → API Access → API keys.You see the active and revoked API keys and their dates.
4Create a new API key and choose whether it expires or never expires.KydHub generates the entire secret only once.
5Copy the secret and save it in the backend of Acme or secret manager.You can now call the Data API from your server.
Copy it at that time: KydHub shows the entire secret only once. Afterwards you will only see the name, prefix, status and metadata of the key.

How to use the API key

The API key travels in the header X-API-KEY. It does not identify a final person; identifies the integrating company that is consulting authorized data.

curl -X QUERY https://dev.api.kydhub.com/api/v1/persons/query \
  -H "X-API-KEY: $KYDHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"person_kyd":"BOB.CARTER","fields":["full_name"]}'
AtributoWhat does it meanUsage rule
$KYDHUB_API_KEYEnvironment variable with Acme's royal secret.Do not paste the actual value in code, logs, tickets or documentation.
X-API-KEYServer-to-server authentication header.It should be sent by the Acme backend, never by the browser.
person_kydPublic KYD of the person consulted.Can be changed by person_id if you already have it.
fieldsExact fields you want to read.I ordered the minimum necessary for your use case.

Difference between credentials

CredentialWhat is it for?Where should you liveCan you go to the browser?
API keyQuery Data API from backend.Backend or secret manager.No.
OAuth client_idStart Login with KWID/OIDC.Frontend/backend according to flow.Yes, it's not a secret.
OAuth client_secretAuthenticate a confidential OAuth client.Backend or secret manager.No.
Secret webhookVerify signatures of incoming events.Webhook receiver backend.No.

Company Free Limits

AreaLimitWhat does it imply?
API keys2 activeYou can have two active keys per company; revoke one before creating a third.
ExpirationOptionalA key may not expire or have an expiration date.
Rate limit60/minIf you exceed the limit, the API responds with a rate limit error.
daily use2,000/dayDesigned for initial integrations and controlled pilots.
Good practices: name each key according to its use, for example acme-production-data-api o acme-sandbox-test, and rotate it if someone on the team no longer needs access.

Integration by stack

Integration recipes: This section is also available in navigable HTML view, formatted Markdown, and raw Markdown. Includes Python/FastAPI, Node.js/Express, Go, Tauri, Electron, and Rust.

These recipes show how to connect your Acme backend or app with KydHub. The main rule is to separate the types of integration well: the Data API use API key server-to-server; OAuth/OIDC it is optional for Login with KWID; desktop apps should use system browser + PKCE and never distribute secrets.

Safety rule: don't put KYDHUB_API_KEY, client_secret nor webhook secrets in frontend, desktop binaries, repositories or logs. On desktop, use PKCE and delegate server-to-server calls to your backend when you need to protect secrets.

What pattern to use according to your stack

StackRecommended useSecret allowedNotes
Python/FastAPIServer-to-server backend for Data API, OAuth callback and webhooks.Yes, in environment variables/secret manager.Ideal for validating tokens, saving API keys and verifying HMAC.
Node.js/ExpressServer-to-server backend for Data API, OAuth callback and webhooks.Yes, in environment variables/secret manager.Good entry point for SPA/SSR web apps.
GoBackend or high-performance internal service.Yes, in environment variables/secret manager.Useful for B2B integrations, workers and business services.
TauriDesktop app with system browser + PKCE.Not within the binary.Use custom callback scheme or local loopback; Responsive data API via backend.
ElectronDesktop app with system browser + PKCE.Not within the app package.Don't use client_secret in main/renderer.
RustCLI/desktop/backend with PKCE or server-side service.It depends on the type of app.Rust Server can save secrets; distributed app no.

Python / FastAPI · consult person

This example exposes an internal Acme endpoint that queries authorized Bob Carter data on KydHub. The API key lives only in the backend.

import os
import httpx
from fastapi import FastAPI, HTTPException

app = FastAPI()
KYDHUB_BASE_URL = os.getenv("KYDHUB_BASE_URL", "https://dev.api.kydhub.com/api/v1")
api_key = os.getenv("KYDHUB_API_KEY", "set-in-secret-manager")

@app.get("/internal/kydhub/person/{person_kyd}")
async def get_person(person_kyd: str):
    payload = {
        "person_kyd": person_kyd,
        "service_name": "acme-checkout",
        "fields": ["full_name", "profile.display_name"],
    }
    headers = {
        "X-API-KEY": api_key,
        "Content-Type": "application/json",
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.request("QUERY", f"{KYDHUB_BASE_URL}/api/v1/persons/query", json=payload, headers=headers)
    if response.status_code >= 400:
        raise HTTPException(status_code=response.status_code, detail=response.json())
    return response.json()

Node.js / Express · consult company

This example queries authorized data for a company by company_kyd. The API key remains in the Express backend.

import express from "express";

const app = express();
app.use(express.json());

const KYDHUB_BASE_URL = process.env.KYDHUB_BASE_URL ?? "https://dev.api.kydhub.com/api/v1";
const apiKey = process.env.KYDHUB_API_KEY ?? "set-in-secret-manager";

app.post("/internal/kydhub/company", async (req, res) => {
  const response = await fetch(${KYDHUB_BASE_URL}/api/v1/companies/query, {
    method: "QUERY",
    headers: {
      "X-API-KEY": apiKey,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      company_kyd: req.body.company_kyd,
      service_name: "acme-supplier-review",
      fields: ["legal_name", "status", "profile.industry"]
    })
  });

  const body = await response.json();
  res.status(response.status).json(body);
});

Go · consult company addresses

Go works great for internal services, workers, or B2B integrations. This example queries locations for a company using company_kyd.

package main

import (
  "bytes"
  "encoding/json"
  "net/http"
  "os"
  "time"
)

func queryCompanyLocations(companyKYD string) (*http.Response, error) {
  baseURL := os.Getenv("KYDHUB_BASE_URL")
  if baseURL == "" { baseURL = "https://dev.api.kydhub.com/api/v1" }

  payload := map[string]any{
    "company_kyd": companyKYD,
    "service_name": "acme-supplier-review",
    "fields": []string{"locations.city", "locations.country"},
  }
  body, err := json.Marshal(payload)
  if err != nil { return nil, err }

  req, err := http.NewRequest("QUERY", baseURL+"/api/v1/companies/query", bytes.NewReader(body))
  if err != nil { return nil, err }
  req.Header.Set("X-API-KEY", os.Getenv("KYDHUB_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{Timeout: 10 * time.Second}
  return client.Do(req)
}

Desktop apps · Tauri, Electron and Rust

Distributed desktop apps must not include secrets. To Login with KWID use system browser, PKCE, state y nonce. For responsive Data API, your app must call your backend and your backend calls KydHub.

OptionUseRedirect URISecret
Custom schemeApp registered as handler.acme://oauth/callbackDo not use client_secret.
Local LoopbackApp lifts temporary callback.http://127.0.0.1:{port}/callbackDo not use client_secret.
Backend brokerDesktop talks to Acme backend.Acme web callback.The backend can keep secrets.

Electron · open Login with KWID

import { shell } from "electron";
import crypto from "node:crypto";

const state = crypto.randomUUID();
const nonce = crypto.randomUUID();
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");

const url = new URL("https://dev.auth.kydhub.com/oauth/authorize");
url.searchParams.set("client_id", "acme_desktop_client");
url.searchParams.set("redirect_uri", "acme://oauth/callback");
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "openid kwid profile");
url.searchParams.set("state", state);
url.searchParams.set("nonce", nonce);
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");

shell.openExternal(url.toString());

Tauri / Rust · open system browser

use open;
use url::Url;

fn start_kydhub_login(code_challenge: &str, state: &str, nonce: &str) -> anyhow::Result<()> {
    let mut url = Url::parse("https://dev.auth.kydhub.com/oauth/authorize")?;
    url.query_pairs_mut()
        .append_pair("client_id", "acme_desktop_client")
        .append_pair("redirect_uri", "acme://oauth/callback")
        .append_pair("response_type", "code")
        .append_pair("scope", "openid kwid profile")
        .append_pair("state", state)
        .append_pair("nonce", nonce)
        .append_pair("code_challenge", code_challenge)
        .append_pair("code_challenge_method", "S256");

    open::that(url.as_str())?;
    Ok(())
}
Desktop: the exchange of code for tokens must validate state, use the same code_verifier and verify nonce in the ID token. Don't save tokens indefinitely if the app doesn't need them.

HTTP QUERY (RFC 10008) and compatibility

QUERY is the canonical method for KydHub complex read-only queries. It is safe, idempotent and retryable. The query is described by JSON request content; it does not modify person or company state.

ContractValue
Canonical operationsQUERY /api/v1/persons/query and QUERY /api/v1/companies/query
Required request headersX-API-KEY, Content-Type: application/json; use Accept: application/json for explicit representation negotiation.
DiscoveryOPTIONS returns Allow: QUERY, POST, OPTIONS and Accept-Query: application/json.
Body limit1 MiB. Oversized content returns 413.
Privacy cache policyCache-Control: no-store, private. Shared caching is disabled in the initial rollout.
CompatibilityPOST /api/v1/*/query is a deprecated temporary alias that invokes the same authorization and query pipeline. Keep it only until Gate C proves client, proxy, WAF and gateway support for QUERY.

Error contract: 400 missing/inconsistent content metadata or malformed JSON; 401 missing/invalid API key; 403 authorization denied; 406 unacceptable response type; 413 body too large; 415 unsupported query media type; 422 valid JSON with unprocessable query content.

Intermediary compatibility: use the POST alias only when a client or deployed intermediary cannot emit/forward QUERY. Production readiness requires Gate C end-to-end evidence.

QUERYQuery person

Query person is a safe, idempotent, read-only Data API operation. It uses QUERY because the request carries structured JSON query content: identifiers, internal service name, requested fields, response profile and presentation format.

Verb rule: use GET for a simple resource read with one identifier; use QUERY /query for a safe, idempotent read that needs structured JSON content. The deprecated POST alias remains only for temporary client/intermediary compatibility.

Request

QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": "acme-checkout",
  "fields": ["name"],
  "response_profile": "standard",
  "format": {
    "names": "given_first",
    "locale": "en-US",
    "include_raw": false
  }
}

Request attributes

AttributeRequiredMeaningRule
X-API-KEYYesServer-only API key for the requesting company.Identifies the caller company; never expose it in browser/mobile clients.
person_kydYesKnown public KYD/KWID of the person.Example: BOB.CARTER. The current backend requires this field.
service_nameYesInternal service slug making the query.Must match ^[a-z0-9]+(?:-[a-z0-9]+)*$, e.g. acme-checkout.
fieldsOptionalRequested person fields.Defaults to identity. Each field must pass API key, scope and grant/consent checks.
response_profileOptionalOverall response profile requested by the client.Currently echoed as response_profile_used; examples use standard.
formatOptionalPresentation options for authorized values.Formatting never grants extra fields or bypasses authorization.

Response

{
  "person_kyd": "BOB.CARTER",
  "person_id": "per_7Q9M2K4R",
  "status": "success",
  "service_name": "acme-checkout",
  "response_profile_used": "standard",
  "format_used": {
    "names": "given_first",
    "addresses": "as_recorded",
    "text": "preserve",
    "dates": "iso",
    "locale": "en-US",
    "include_raw": false
  },
  "fields": {
    "name": {
      "formatted": "Bob Carter",
      "format_used": "given_first"
    }
  }
}

Response attributes

AttributeMeaningHow to use it
person_kydPublic person KYD/KWID.Use it as a human-readable identifier.
person_idOpaque public person ID derived by KydHub.Store it for future references; do not use internal UUIDs.
statusOperation result.success means the requested authorized fields were returned.
service_nameService slug received in the request.Useful for audit and debugging.
response_profile_usedApplied response profile.Confirms the response profile processed by KydHub.
format_usedApplied value-presentation options.Do not treat it as authorization; it only describes formatting.
fieldsAuthorized returned fields.Only fields approved by API key, scopes and grant/consent are present.

Authorization errors

{
  "status": "error",
  "error": {
    "code": "data_grant_required",
    "message": "The service does not have an approved data grant to read the requested person fields.",
    "person_kyd": "BOB.CARTER",
    "service_name": "acme-checkout"
  }
}
Privacy default: KydHub does not return unrequested or unauthorized fields. Do not design integrations that depend on extra data.

QUERYCheck addresses of a person

A company can query a person's registered addresses when the use case warrants it and authorization exists for those fields. For example, Acme may need Bob Carter's city and country to complete a shipping or billing flow.

I asked for the minimum: If Acme only needs city and country, it should not ask for street, zip code, or full address. KydHub allows the request to be explicit field by field.

Petition for KYD of person

QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": acme-shipping,
  "fields": [
    "addresses.type",
    "addresses.city",
    "addresses.region",
    "addresses.country",
    "addresses.postal_code"
  ]
}

Request attributes

AtributoObligatorioWhat does it meanRegla
person_kydone of twoPublic KYD/KWID of the person, such as BOB.CARTER.Use it if you know the person's public alias.
person_idone of twoOpaque public ID of the person, such as per_....Use it if it was already returned by KydHub in a previous query.
service_nameYeahTechnical slug of the service requesting the address.Example: acme-shipping o acme-billing.
fieldsYeahAddress fields you want to read.You should include only necessary and authorized attributes.

Common address fields

FieldWhat does it returnWhen to order it
addresses.typeAddress type, for example shipping, billing o registered.When you need to distinguish use of address.
addresses.line1Main address line.Only if you need a complete address to operate.
addresses.line2Complement, department, floor or reference.Optional; Order it only if your flow uses it.
addresses.cityCity or town.Useful for shipping, coverage or regional validation.
addresses.regionProvince, state or region.Useful for taxes, logistics or local rules.
addresses.countryCountry in readable format or code according to final contract.Useful for coverage, compliance and billing.
addresses.postal_codeZip code.Useful for shipping, taxes or territorial validation.
addresses.is_primaryIndicates if it is the main address.Request it if you need to choose a default address.

Expected response

{
  "data": {
    "person_id": "per_7Q9M2K4R",
    "person_kyd": "BOB.CARTER",
    "fields": {
      "addresses": [
        {
          "type": "shipping",
          "city": "Buenos Aires",
          "region": "Autonomous City of Buenos Aires",
          "country": "AR",
          "postal_code": "C1000"
        }
      ]
    },
    "grant": {
      "status": "active",
      "scopes": ["person.addresses:read"]
    }
  }
}

Response attributes

AtributoWhat does it meanHow to use it
fields.addressesList of authorized and returned addresses.It may come empty if there are no authorized or registered addresses for the requested filter/fields.
typeUse of address.Don't assume that there is always one address of each type.
city ​​/ region / countryGeneral location.Useful for validation, coverage and regional rules.
postal_codeZip code.It may be absent if it was not requested, does not exist or was not authorized.
grant.scopesScopes that support reading.For person addresses, there must be permission to read addresses.

Variant by person_id

If Acme has already saved the person_id, you can query addresses without resending person_kyd.

{
  "person_id": "per_7Q9M2K4R",
  "service_name": acme-shipping,
  "fields": ["addresses.city", "addresses.country"]
}

Common mistakes

ErrorWhat does it meanTo do
grant_requiredAcme does not have active authorization to read Bob's addresses.Ask for consent or adjust the flow to not require direction.
field_not_allowedsome field addresses.* not covered by scopes/grant.Remove the field or request the correct permission.
address_not_availableThere is no registered/authorized address for the requested fields.Show an alternative in your flow; Don't invent direction.
person_not_foundEl person_kyd o person_id does not exist.Verify the received identifier.
Address Privacy: an address can be more sensitive than a name. don't ask line1, line2 or zip code if your integration only needs country or city.

QUERYQuery company

Query company is the company-side equivalent of Query person: a credentialed, read-only query with a JSON body. Its canonical method is QUERY /api/v1/companies/query because RFC 10008 defines safe, idempotent requests with structured query content.

No open directory: the caller must know the target company identifier and pass the three authorization gates: active company/API key, required scopes, and grant/relationship/explicit access basis.

Request

QUERY https://dev.api.kydhub.com/api/v1/companies/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "company_kyd": "ACME.SUPPLIER",
  "service_name": "acme-risk",
  "fields": ["identity", "profile", "locations"],
  "response_profile": "standard",
  "format": { "locale": "en-US" }
}

Request attributes

AttributeRequiredMeaningRule
X-API-KEYYesServer-only API key for the requesting company.Identifies the caller company and applies audit/rate limits.
company_kydone of twoKnown KYD handle of the target company.Example: ACME.SUPPLIER.
company_identifierone of twoKnown company identifier accepted by the backend.Use it when your integration stores the canonical identifier returned by KydHub.
service_nameYesInternal service slug making the query.Must match ^[a-z0-9]+(?:-[a-z0-9]+)*$, e.g. acme-risk.
fieldsOptionalRequested company field groups.Allowed groups: identity, profile, locations. Defaults to identity.
response_profileOptionalOverall response profile.Current examples use standard.
formatOptionalPresentation options.Currently used for locale-sensitive subqueries such as locations.

Response

{
  "status": "success",
  "company_kyd": "ACME.SUPPLIER",
  "company_id": "comp_00000000-0000-4000-8000-000000000042",
  "service_name": "acme-risk",
  "response_profile_used": "standard",
  "fields": {
    "identity": {
      "company_kyd": "ACME.SUPPLIER",
      "company_id": "comp_00000000-0000-4000-8000-000000000042",
      "legal_name": "Acme Supplier LLC",
      "trade_name": "Acme Supplier",
      "country_code": "US",
      "profile_status": "complete",
      "is_active": true
    },
    "profile": {
      "website": "https://supplier.example",
      "logo_url": "https://cdn.example/supplier.svg",
      "activity_id": "activity-1"
    },
    "locations": [
      {
        "id": "loc-1",
        "name": "Headquarters",
        "type_name": "Office",
        "is_primary": true,
        "country_code": "US",
        "country_name": "United States",
        "postal_code": "33131",
        "address_detail": "1200 Brickell Ave",
        "reference": null,
        "latitude": 25.7617,
        "longitude": -80.1918
      }
    ]
  }
}

Response attributes

AttributeMeaningHow to use it
statusOperation result.success means the requested authorized groups were returned.
company_kydKnown public company KYD.Use it as the readable identifier for the company.
company_idPublic company ID for the company.Store it only as a public identifier; do not infer internal database IDs.
service_nameService slug received in the request.Useful for audit and debugging.
fields.identityAuthorized identity fields.Requires company.identity:read.
fields.profileAuthorized profile fields.Requires company.profile:read.
fields.locationsAuthorized public location rows.Requires company.locations:read.

Simple resource reads

For simple reads with one path identifier, the backend also exposes resource-oriented GET endpoints. Use them when no complex JSON request is needed:

GET /api/v1/companies/{company_identifier}
GET /api/v1/companies/{company_identifier}/profile
GET /api/v1/companies/{company_identifier}/locations
Security: neither QUERY nor GET resource reads are public directory search. All company reads are credentialed, scoped, audited and relationship/authorization-gated.

QUERYCheck addresses of a company

Acme may also query another company's registered addresses or locations when it needs to validate billing, operational, branch, or coverage data. Like the rest of the Data API, the query is read-only and requires requesting specific fields.

Key difference: A business address describes a business entity or its locations. It is not a personal address and should not be mixed with addresses.* of a person.

Petition for company_kyd

QUERY https://dev.api.kydhub.com/api/v1/companies/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "company_kyd": "ACME.SUPPLIER",
  "service_name": "acme-supplier-review",
  "fields": [
    "locations.type",
    "locations.name",
    "locations.city",
    "locations.region",
    "locations.country"
  ]
}

Request attributes

AtributoObligatorioWhat does it meanRegla
company_kydone of twoPublic KYD of the consulted company.Use it when you know the business KYD identifier.
company_idone of twoOpaque public ID of the company, such as comp_00000000-0000-4000-8000-000000000042.Use it if KydHub has already returned it before.
service_nameYeahService that consults locations.Example: acme-supplier-review.
fieldsYeahRequested business location/address fields.Order only what you need for your B2B flow.

Common business location fields

FieldWhat does it returnWhen to order it
locations.typeLocation type, e.g. headquarters, branch, billing o operations.When you need to distinguish headquarters, branch or operation.
locations.nameLegible name of the location.Useful to display it in UI or reports.
locations.line1Main line of business direction.Only if you need a complete address.
locations.line2Complement or reference.Optional; Order it only if your operation uses it.
locations.cityCity or town.Regional coverage, logistics or validation.
locations.regionProvince, state or region.Fiscal, legal or logistical rules.
locations.countryCountry.Coverage, compliance or international billing.
locations.postal_codeZip code.Billing, taxes or physical shipping.
locations.is_primaryIndicates if it is the main location.To choose default location.

Expected response

{
  "data": {
    "company_id": "comp_00000000-0000-4000-8000-000000000042",
    "company_kyd": "ACME.SUPPLIER",
    "fields": {
      "locations": [
        {
          "type": "headquarters",
          "name": "Main office",
          "city": "Miami",
          "region": "Florida",
          "country": "US",
          "is_primary": true
        }
      ]
    },
    "scopes": ["company.locations:read"]
  }
}

Response attributes

AtributoWhat does it meanHow to use it
fields.locationsList of authorized business locations or addresses.May come empty if no locations are available/authorized.
typeLocation use or category.Don't assume that all companies have the same types.
nameLegible name of the location.Use it for UI/reports, not as a unique ID.
city ​​/ region / countryGeneral geographic location.Useful for regional rules and coverage validation.
is_primaryMark main location.It may be missing if it was not requested or is not defined.
scopesScopes that support reading.For locations, read permission for business locations must exist.

Variant by company_id

{
  "company_id": "comp_00000000-0000-4000-8000-000000000042",
  "service_name": "acme-supplier-review",
  "fields": ["locations.city", "locations.country"]
}

Common mistakes

ErrorWhat does it meanTo do
company_not_foundThere is no company with that company_kyd o company_id.Verify the identifier through an authorized channel.
company_locations_scope_requiredThe API key does not have permission to read company locations.Request/enable company.locations:read; plan limits control capacity, not the data category itself.
location_not_availableThere is no available/authorized location for the requested fields.Show an alternative or request fewer fields.
field_not_allowedsome field locations.* is not allowed.Remove the field or request the corresponding scope.
No private data by default: Do not document or expect emails, telephone numbers, internal managers or administrative data from a location unless the contract explicitly enables them.

Login with KWID / OAuth OIDC optional

Login with KWID allows Acme to use KydHub as an identity provider using OAuth 2.0 and OpenID Connect. It is useful when you want to delegate login, SSO or identity verification to KydHub.

Recordatorio: OAuth/OIDC is not required to use the Data API. If Acme already has its own login, you can keep it and only use server-to-server API keys to query authorized data.

When to use it and when to skip it

SituationRecommendationBecause
Acme already has its own loginYou can bypass OAuth from KydHub.Use Data API with API key from backend to query authorized data.
Acme wants Login with KWIDUse OAuth 2.0 + OIDC.KydHub authenticates the user and returns verifiable tokens/claims.
Web application with backendUse Authorization Code + PKCE and confidential client if applicable.The backend can store secrets securely.
Mobile or desktop applicationUse PKCE and treat it as a public client unless there is a backend.don't put client_secret within a distributed app.

GET1 · Start Login with KWID

What is it for: Acme redirects the user to KydHub to confirm their identity with KWID. When you return to the Acme callback, the backend receives a code which you then exchange for tokens.

# redirected the user to KydHub
GET https://dev.auth.kydhub.com/oauth/authorize
  ?client_id=kh_client_…
  &redirect_uri=https://app.acme.example/callback
  &response_type=code
  &scope=openid kwid email profile company:read
  &state=RANDOM
  &code_challenge=&code_challenge_method=S256
Parameter¿Obligatorio?What is
client_idYeahThe Acme app ID on KydHub.
redirect_uriYeahWhere the user returns to. must match exacto with the registered one.
response_typeYeahSiempre code.
scopeYeahData you request, separated by space.
stateYeahRandom anti-CSRF value; you validate it when you return.
code_challengeYeahPKCE: the hash of your code_verifier (method S256).
nonceOptionalRecommended: you validate it in the id_token to avoid replays.
login_hintOptionalPre-fill the KWID (e.g. BOB.CARTER).
promptOptionallogin Force re-authentication even if there is SSO.
Tip: guard state, nonce and the code_verifier before redirecting — you'll need them when the user comes back.

POST2 · Email verification

If the user does not have an active KydHub session, the first /authorize can come back with error=login_required y verification_required. This is an expected intermediate state, not a fatal error. Acme should show “Check your email” and start verification from its backend.

POST https://dev.auth.kydhub.com/oauth/login/start
Content-Type: application/json

{
  "client_id": "kh_client_…",
  "redirect_uri": "https://app.acme.example/callback",
  "scope": "openid kwid email profile company:read",
  "state": "RANDOM",
  "code_challenge": "…",
  "code_challenge_method": "S256",
  "verification_identifier": "BOB.CARTER"
}
Field¿Obligatorio?What is
verification_identifierYeahThe KWID/KYDid. Here no va login_hint.
client_idYeahThe ID of your app.
redirect_uriYeahThe same as step 1, exactly.
scopeYeahString with spaces, not array.
stateYeahAnti-CSRF value from step 1.
code_challengeYeahPKCE from step 1.
code_challenge_methodYeahS256.
nonceOptionalRecommended; validate it in the id_token.

The answer comes wrapped in data:

{
  "data": {
    "status": "pending_verification",
    "login_attempt_id": "login_attempt_…",
    "expires_at": "2026-06-19T15:30:00Z",  // TTL 15 min
    "challenge_prefix": "abc12345"     //diagnosis only
  }
}
Email is the verification channel. No you build the URL /oauth/login/verify Don't even open the direct consent: KydHub hosts it after the magic link. Use expires_at for the accountant.
Public apps: If the integration is mobile or desktop, do not distribute a client_secret within the app. Use PKCE and/or your own backend to protect secrets.

POST3 · Exchange the code for the token

What is it for: when KydHub returns you to redirect_uri with a code, you backend exchanges it for the tokens. Here he does come in client_secret.

POST https://dev.auth.kydhub.com/oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "client_id": "kh_client_…",
  "client_secret": "CLIENT_SECRET_DEL_BACKEND",
  "code": "CODE_DEL_CALLBACK",
  "redirect_uri": "https://app.acme.example/callback",
  "code_verifier": "EL_VERIFIER_ORIGINAL"
}
Field¿Obligatorio?What is
grant_typeYeahSiempre authorization_code.
client_idYeahThe ID of your app.
client_secretYeahBackend secret. Never on the client.
codeYeahThe code that reached the callback (single use).
redirect_uriYeahThe same as step 1.
code_verifierYeahThe original PKCE (the one that generated the code_challenge).

Response (raw OAuth object, no wrapper data):

{
  "access_token": "…",
  "id_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid kwid email profile company:read"
}
Hoy /oauth/token espera JSON (no form-urlencoded). If your library only sends forms, add an adapter or you will receive a 422.

4 · Validate the token

Before trusting him id_token, verify your signature with the JWKS and check the claims. I read the jwks_uri from discovery — don't hardcode it.

ChequeoWhat to validate
SignatureCheck with the keys jwks_uri (from discovery).
issIt must be https://dev.kydhub.com.
audIt must be you client_id.
expNot expired.
nonceSame as the one you sent (if you used it).
Discovery and JWKS come wrapped in data: extracted data before passing them to a standard OIDC library.

GETUserInfo

What is it for: bring the data of the logged in user. You call with him access_token like Bearer.

GET /userinfo
Authorization: Bearer ACCESS_TOKEN

{
  "sub": "person_01JDEVEXAMPLE",
  "kwid": "BOB.CARTER",
  "email": "bob.carter@example.test",
  "email_verified": true,
  "name": "Bob Carter",
  "given_name": "Bob",
  "family_name": "Carter",
  "picture": null
}
Current gap: name/given_name/family_name y picture they may come empty (drop to the KWID) until KydHub fills in the real names. For the trusted name, use the Data API.

Claims by scope

Each scope you order enables certain claims:

ScopeClaims that returns
openidsub, iss, aud, exp, iat, auth_time (y nonce if you sent it).
kwidkwid.
emailemail, email_verified.
profilename, given_name, family_name, picture (subject to gap above).

OAuth errors: intermediate vs terminals

Intermediates are resolved following the flow; terminals require reboot.

↻ Intermediates

  • verification_required — start verification by email.
  • pending_consent / consent_required — wait for the host consent.
  • login_required — start login if there is no SSO.

Terminales

  • invalid_grant — expired/used code, redirect or PKCE mismatch.
  • invalid_verification_identifier — Invalid KWID.
  • fresh_authentication_required — restart with prompt=login.
  • access_denied — the user canceled.

Quick Reference Data API

This reference brings together the canonical Data API capabilities. Use it as a quick index; for complete details, read each specific section.

Contract rule: Data API is credentialed and read-only. It does not create, edit or delete people, companies, addresses, API keys, OAuth clients or webhooks.
Three-level authorization: every request needs (1) an active company with a valid server-side X-API-KEY, (2) the required scopes, and (3) an active grant, consent, authorized relationship, or another explicit access basis for the data being returned.

Canonical endpoints

EndpointCapabilityIdentifierAuthorization
QUERY /api/v1/persons/queryQuery authorized person data.person_kyd or person_id.X-API-KEY, service_name, scopes, and active grant/consent.
QUERY /api/v1/companies/queryQuery authorized company identity data.Known company_kyd or supported company identifier.X-API-KEY, company.identity:read, and valid access basis.
QUERY /api/v1/companies/queryQuery authorized company profile fields.Known company_kyd or supported company identifier.X-API-KEY, company.profile:read, and valid access basis.
QUERY /api/v1/companies/queryQuery authorized company locations.Known company_kyd or supported company identifier.X-API-KEY, company.locations:read, and valid access basis.

Query person base request

QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": "acme-checkout",
  "fields": ["person_id", "person_kyd", "name", "email"]
}

Query company base requests

GET https://dev.api.kydhub.com/api/v1/companies/ACME.SUPPLIER
X-API-KEY: $KYDHUB_API_KEY

GET https://dev.api.kydhub.com/api/v1/companies/ACME.SUPPLIER/profile
X-API-KEY: $KYDHUB_API_KEY

GET https://dev.api.kydhub.com/api/v1/companies/ACME.SUPPLIER/locations?lang=en
X-API-KEY: $KYDHUB_API_KEY

Authorization rules

RuleDetail
Requester companyKydHub derives the requester from the server-only X-API-KEY. Do not trust request-body identifiers as requester identity.
ScopesScopes describe what the integration asks for. Plans limit capacity; scopes plus grants/consent decide returned data.
Grants and consentPerson data and protected company data require an active grant, user consent, authorized relationship, or another explicit access basis.
service_nameRequired for person query. Use a lowercase ASCII slug matching ^[a-z0-9]+(?:-[a-z0-9]+)*$, for example acme-checkout.
No open directoryQuery company is for known authorized companies. Do not use it as open company search or browsing.
Public IDsUse per_... and comp_00000000-0000-4000-8000-000000000042 in client-facing contracts; never expose internal UUIDs.
ErrorsIf API key, scope, grant, relationship, or field authorization is missing, the API fails closed and does not expose data.

POSTWebhooks

Webhooks allow Acme to receive signed events from KydHub when something relevant occurs. They are optional: if your integration only needs to query data on demand, you can use the Data API without webhooks.

When to use them: Use webhooks when your backend needs to know about changes, approvals, revocations or deliveries without constantly consulting the API.

General flow

StepWhat happensResponsable
1Acme registers a receiving URL in the KydHub portal.Acme
2KydHub generates or displays the webhook signing secret.KydHub
3When an event occurs, KydHub sends a POST to the Acme backend.KydHub
4Acme validates the HMAC signature before processing the event.Acme
5Acme keeps the event_id to avoid processing duplicates.Acme

Event received by Acme

POST https://app.acme.example/kydhub/webhook
X-KydHub-Event-Id: evt_01HVEXAMPLE
X-KydHub-Timestamp: 1760000000
X-KydHub-Signature-256: sha256=HEX_HMAC
Content-Type: application/json

{
  "id": "evt_01HVEXAMPLE",
  "type": "grant.approved",
  "created_at": "2026-06-28T12:00:00Z",
  "data": {
    "person_kyd": "BOB.CARTER",
    "company_id": "comp_00000000-0000-4000-8000-000000000042",
    "scopes": ["person.identity:read", "person.addresses:read"]
  }
}

Security headers

HeaderWhat does it meanWhat Acme Should Do
X-KydHub-Event-IdUnique ID of the event.Save it before processing so that retries are idempotent.
X-KydHub-TimestampTime KydHub signed/submitted the event.Reject timestamps that are too old to reduce replays.
X-KydHub-Signature-256Signature HMAC-SHA256 of the raw bodysuit.Recalculate the signature with the webhook secret and compare timing-safe.
Content-TypeBodysuit format.Wait application/json.

Verify signature in Node.js

import crypto from 'node:crypto';

function verifyKydHubWebhook(rawBody, signatureHeader, webhookSecret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Typical events

EventWhen it happensWhat should Acme do?
grant.approvedSomeone approved of Acme reading certain data.Update internal state and enable Data API query.
grant.rejectedOne person rejected the request.Show alternative or ask for less data.
grant.revokedAn authorization was revoked.Stop using data that depends on that grant.
webhook.delivery.failedKydHub failed to deliver an event after attempts.Check availability of the receiving endpoint.

Retries, idempotence and limits

TemaReglaRecommendation
Successful responseReply 2xx only after validating signature and saving the event.Don't do slow work before responding; send to a queue if necessary.
RetriesKydHub may retry failed deliveries.Process by event_id to avoid duplicates.
Company Free1 active webhook and 1000 delivery attempts per month.Retries count against your monthly limit.
SecurityThe webhook secret lives only in backend/secret manager.Do not put it in frontend, logs or public documentation.
Don't just trust the URL: any public endpoint can receive requests. Only process events with a valid signature, acceptable timestamp and event_id not processed.

Security, scopes, grants and limits

KydHub security combines server-only credentials, a complete requestable scope catalog, active authorizations, and usage limits. The base rule is simple: the Data API is read-only and returns only the specific scopes/fields that were requested and authorized.

Main rules

Does

  • Save API keys, OAuth secrets and webhook secrets only in backend or secret manager.
  • I requested only the necessary fields in fields.
  • Validate scopes and grants before depending on a response.
  • Verify webhook signatures with HMAC and timing-safe comparison.
  • Rotate credentials when a person on the team loses access.
  • Show alternatives when authorization is missing or a field is not available.

don't do

  • Do not put API keys in browsers, mobile apps, repositories or documentation.
  • don't put client_secret within a distributed app.
  • Do not use KydHub as an open business directory.
  • Don't invent data if KydHub doesn't return a field.
  • Don't ignore grant_required o field_not_allowed.
  • Do not try to modify people, companies, addresses, API keys, OAuth clients or webhooks through the public Data API.

Authorization model

CapaHow validExample
API keyWhat company is calling and if the key is active.Acme calls with X-API-KEY.
Plan/limitsHow many credentials, requests or deliveries the company can use.Company Free allows 2 active API keys.
ScopesWhat data families a credential can read.person.addresses:read.
GrantWhich person/company authorized the reading and for which fields.Bob Carter authorized reading of identity and addresses.
FieldsWhat exact attributes does the request ask for?["full_name", "addresses.city"].
Mental order: A response exists only if the API key is valid, the scope exists in the catalog, the user or another valid access basis authorized it, usage limits allow it, and the field was explicitly requested.

Scope catalog and user approval

KydHub publishes a complete, granular scope catalog. Pro and Enterprise companies may request any published scope; KydHub does not block a data category by plan. The user approves or rejects the exact person-data scopes requested. Plans limit operational capacity: request volume, rate limits, API keys, OAuth clients, webhooks, retention, support and SLA.

AppTypical requested scopesWhy
qbtChatperson.identity:read, person.name:read, person.email:read, person.profile.avatar:readLogin/profile display; no address needed.
Mercado Libreperson.identity:read, person.name:read, person.identity.document:read, person.addresses.primary:readIdentity and shipping verification after explicit user approval.

Documented current scopes

ScopeAllows you to readUsed in
person.identity:readIdentifiers and basic name of person.Consult person.
person.profile:readBasic authorized person profile.Consult person.
person.addresses:readRegistered/authorized addresses of person.Addresses of person.
company.identity:readBasic company identity.Consult company.
company.profile:readAuthorized company profile.Consult company.
company.locations:readRegistered business locations/addresses.Company addresses.
webhooks.events:readWebhook events if diagnostics are exposed.Diagnosis/operation.
usage:readUse and consumption of API if the contract enables it.Diagnosis/operation.
audit:readAllowed audit events.Diagnosis/operation.

Company Free limits

AreaLimitNotes
People per company2 total1 owner/founder and 1 collaborator.
API keys2 activeThey may not expire; optional expiration.
API rate limit60 requests/minApplied by company/API key according to operating contract.
API daily limit2,000 requests/dayDesigned for pilots and initial integrations.
Webhooks1 active1000 delivery attempts/month; retries count.
OAuth / Apps1 connected app, 1 OAuth clientUp to 3 redirect URIs.
Audit logs7 daysBasic retention.
SupportBasic / best-effortNo contractual SLA.

Permitted and prohibited operations

TipoStatusExplanation
Read authorized person dataAllowedWith API key, catalog scope, active grant/consent/access basis and explicit fields.
Read authorized person addressesAllowedWith address permission.
Read authorized company dataAllowedWith company_kyd o company_id conocido.
Read authorized company locationsAllowedWith company.locations:read.
Create/modify people by public APIForbiddenThe person modifies their data within KydHub.
Create/modify companies by public APIForbiddenIt is managed from the portal.
Manage API keys/OAuth/webhooks by public APIForbiddenFor now it is done from the portal.
Search for companies without an identifierForbiddenKydHub should not function as an open directory.

Safe response to lack of permission

{
  "error": "field_not_allowed",
  "message": "One or more requested fields are not allowed for this API key or grant.",
  "fields": ["addresses.line1"]
}
Fail closed: If scope, grant or authorized field is missing, KydHub must respond without exposing the data. The integration must reduce fields, ask for authorization, or continue with an alternative.

Errors and troubleshooting

This section summarizes the most common mistakes when integrating KydHub. The general recommendation is not to invent data or continue as if the response was successful: each error indicates a specific action to correct credentials, permissions, identifiers or configuration.

Rule of thumb: If an error mentions credentials, check the Acme portal. If you mention grant o field, I asked for less data or request authorization. If it mentions webhook, validate signature, endpoint and idempotence.

Recommended error format

{
  "error": "field_not_allowed",
  "message": "One or more requested fields are not allowed for this API key or grant.",
  "request_id": "req_01HVEXAMPLE",
  "fields": ["addresses.line1"]
}
AtributoWhat does it meanHow to use it
errorStable error code.Use it for UI/backend logic.
messageReadable explanation.Show it only if it does not expose internal data; You can map it to your own copy.
request_idCorrelation ID.Include it when requesting support or reviewing logs.
fieldsAffected fields, when applicable.Useful for removing fields or requesting correct authorization.

API keys and authentication

ErrorWhat does it meanHow to solve it
missing_api_keyThe header did not arrive X-API-KEY.Send the API key from the Acme backend. Do not send it from a browser.
invalid_api_keyThe key does not exist, was revoked, expired or was copied incorrectly.Create or rotate a key from Company → API Access.
api_key_limit_reachedThe company already has the maximum number of active API keys.In Company Free, revoke an active key before creating another one.
rate_limit_exceededThe limit of requests per minute was exceeded.Reduce frequency, add backoff and review plan limits.
daily_limit_exceededThe daily limit has been exceeded.Wait for the window to restart or consult the upgrade/contract.

Identifiers and resources

ErrorWhat does it meanHow to solve it
person_not_foundThere is no person for that person_kyd o person_id.Verify the identifier. Don't try to guess identities.
company_not_foundThere is no company for that company_kyd o company_id.Confirm the identifier through authorized channel; KydHub is not an open directory.
invalid_identifierThe identifier is in invalid format.Use BOB.CARTER, per_..., company_kyd o comp_00000000-0000-4000-8000-000000000042 as appropriate.
address_not_availableNo address of person available/authorized.I asked for fewer fields or show the user an alternative.
location_not_availableNo business location available/authorized.Continue without location or request a less sensitive field.

Scopes, grants and fields

ErrorWhat does it meanHow to solve it
grant_requiredThere is no active authorization to read that data.Start the authorization flow or request fields that do not require that grant.
grant_revokedThe authorization existed but was revoked.Stop using that data and request new authorization if applicable.
field_not_allowedOne or more fields are not allowed by scope/grant.Remove fields, request less data or request appropriate permissions.
scope_requiredThe API key does not have the necessary scope.Request/enable the exact scope. The user decides person-data authorization; plans limit volume/capacity.
company_scope_requiredMissing company read scope.Use scopes like company.identity:read or company.profile:read.
company_locations_scope_requiredPermission missing for company locations.Check if the key has company.locations:read.

OAuth/Login with KWID

ErrorWhat does it meanHow to solve it
login_requiredThe user does not have an active session.Show the verification flow or redirect to KydHub.
verification_requiredYou must complete verification by email/magic link.Don't treat it as a fatal flaw; show him “Check your email”.
invalid_redirect_uriThe callback URL does not exactly match the one registered.Register https://app.acme.example/callback or the exact real URL.
invalid_clientclient_id or incorrect secret.Check the OAuth client in the portal. Don't put secrets in public apps.
invalid_grantEl code expired, was already used or does not correspond to the verifier.Restart login and validate PKCE/state.
invalid_tokenInvalid, expired or incorrectly signed token.Validate issuer, audience, expiration and JWKS.

Webhooks

ErrorWhat does it meanHow to solve it
webhook_signature_invalidThe HMAC signature does not match.Use the exact raw body and the correct secret; compare timing-safe.
webhook_timestamp_expiredThe timestamp is too old.Decline the event and check clock/sync.
webhook_duplicate_eventEl event_id It has already been processed.Don't reprocess it; I returned 2xx if it was already persisted.
webhook_delivery_failedKydHub was unable to deliver the event.Check public availability, TLS and 2xx response of the endpoint.
webhook_limit_reachedThe limit of active webhooks or deliveries has been reached.In Company Free there is 1 active webhook and 1000 attempts/month.

Optional post-quantum encryption

ErrorWhat does it meanHow to solve it
pq_encryption_not_enabledThe layer is not enabled for the enterprise.Use normal Data API or request contractual enablement.
unknown_key_idEl kid does not exist or was rotated.Register/obtain the current public key.
decrypt_failedAEAD could not be decrypted or failed.Do not process content; retry with valid password.
unsupported_algorithmThe cryptographic suite is not supported.Negotiate a supported suite or disable this layer.
Support: when asking for help, I shared request_id, endpoint, environment and approximate time. Never share API keys, client secrets, webhook secrets, tokens or payloads with PII.

Agent-readable/endpoint array

This section summarizes the KydHub integration in a compact format for agents, technical teams, and internal documentation. It does not replace the previous sections: it serves as a quick map to implement without losing security rules.

Recommended use: If an agent or developer needs to integrate KydHub, they should first read this matrix and then go to the detailed section of the endpoint they are going to use.

Operational summary

ElementoValorNota
sandbox/dev environmenthttps://dev.api.kydhub.com/api/v1Used for testing and current documentation.
Auth Data APIX-API-KEYBackend/secret manager only. Never browser.
Example companyAcmeFictitious integration company.
Example personBob Carter / BOB.CARTERFictional person for examples.
Company consulted exampleACME.SUPPLIERFictitious business KYD.
API modelRead-onlyDoes not create, modify or delete data via public API.

Available capabilities matrix

CapacidadEducational endpointIdentifieresTypical scopes
Query a personQUERY /api/v1/persons/queryperson_kyd o person_idperson.identity:read, person.profile:read
Check person addressesQUERY /api/v1/persons/queryperson_kyd o person_idperson.addresses:read
Query company identityQUERY /api/v1/companies/queryKnown company_kyd or supported company identifier.company.identity:read
Query company locationsQUERY /api/v1/companies/queryKnown company_kyd or supported company identifier.company.locations:read
Login with KWIDGET /oauth/authorizeclient_id, redirect_uriopenid, kwid, email, profile
WebhooksPOST https://app.acme.example/kydhub/webhookevent_idHMAC signature + contracted events.

JSON for agents

{
  "product": "KydHub",
  "environment": "sandbox",
  "base_url": "https://dev.api.kydhub.com/api/v1",
  "auth": {
    "data_api": {
      "type": "api_key",
      "header": "X-API-KEY",
      "storage": "backend_or_secret_manager_only"
    },
    "oauth_oidc": {
      "optional": true,
      "issuer": "https://dev.kydhub.com",
      "flow": "authorization_code_pkce"
    }
  },
  "read_only_public_api": true,
  "examples": {
    "integrator_company": "Acme",
    "person_kyd": "BOB.CARTER",
    "company_kyd": "ACME.SUPPLIER",
    "person_id": "per_7Q9M2K4R",
    "company_id": "comp_00000000-0000-4000-8000-000000000042"
  }
}

Permitted operations

OperationPermitidaCondition
Read person authorized fieldsYeahValid API key + catalog scope + grant/consent/access basis + explicit fields.
Read authorized person addressesYeahperson.addresses:read and active authorization.
Read company authorized fieldsYeahcompany_kyd o company_id conocido.
Read authorized company locationsYeahcompany.locations:read.
Use Login with KWID/OIDCOptionalOnly if Acme decides to delegate login/verification.
Receive signed webhooksOptionalWebhook registered in portal + HMAC secret.

Prohibited operations for agents

OperationStatusRegla
Modify people data by public APINoThe person manages their data within KydHub.
Modify companies by public APINoThe company is managed from the portal.
Create API keys by public APINoAPI keys are created from the portal.
Create OAuth clients by public APINoOAuth clients are managed from the portal.
Search for companies without an identifierNoKydHub is not an open directory.
Expose secrets in code or logsNuncaUse environment variables or secret manager.

Checklist to implement

#ValidationExpected result
1Create API key in portal.Secret saved in backend/secret manager.
2Make minimum person request.Response contains only requested/authorized fields.
3Test missing scope/field not allowed.type safe error field_not_allowed.
4If there is OAuth, validate state, nonce and JWKS.Secure login and verified tokens.
5If there are webhooks, validate HMAC and idempotency.Signed events and no duplicate processing.
6Document expected errors in the integration.UX with alternatives and without inventing data.
Instruction for agents: do not generate endpoints, scopes or fields not documented here. If a capability is missing, treat it as unavailable until KydHub explicitly documents it.

Optional post-quantum encryption

KydHub can offer an optional post-quantum hybrid encryption layer for especially sensitive payloads. This layer protects the message body in addition to HTTPS/TLS, but does not replace authentication, authorization, scopes, grants, auditing, or signatures.

Simple idea: TLS secures the transport. Optional post-quantum encryption also protects the payload so that only the intended recipient can open it, even if the message is stored or passes through intermediate systems.

When to use it

SituationRecommendationMotivo
Standard Data API IntegrationIt is not mandatory.TLS, API key, scopes and grants are already the basis of security.
Payloads with sensitive PIIIt can be activated if the contract enables it.Adds body confidentiality in addition to the TLS channel.
Webhooks with sensitive dataCan be used in conjunction with HMAC signature.The signature verifies integrity/origin; Encryption protects content.
Client without cryptographic supportDon't activate it yet.Integrate normal Data API first and then add this layer.

What does not replace

ControlIt is still mandatoryBecause
TLS/HTTPSYeahProtects the transport channel.
API keyYeahIdentify the company that is calling.
OAuth/OIDCYes, if you use Login with KWIDAuthenticates users and issues claims/tokens.
Scopes and grantsYeahThey decide what data can be read.
Webhook signingYeahVerify origin and integrity of the event.
AuditYeahAllows you to track who ordered what and when.

How it works at a high level

StepWhat's happeningResult
1 Public keyThe recipient publishes or registers a public encryption key.The sender knows which key to protect the payload with.
2 · Post-quantum KEMThe issuer encapsulates a secret using ML-KEM/Kyber.A shared secret resistant to the post-quantum model is obtained.
3 · Classic ECDHAn ephemeral secret X25519 is also generated.Hybrid classical + post-quantum defense.
4 DerivationBoth secrets enter the HKDF.A symmetric key is derived for encryption.
5 · AEADThe payload is encrypted with AES-256-GCM or another approved AEAD.Confidentiality and integrity of the encrypted body.

Encrypted Wrapper Example

{
  "protected_payload": {
    "alg": "KydHub-HybridPQ-MLKEM768-X25519-HKDF-AES256GCM",
    "kid": "kh_pq_key_2026_06",
    "kem_ciphertext": "BASE64URL_ML_KEM_CT",
    "ecdh_public_key": "BASE64URL_X25519_PUB",
    "nonce": "BASE64URL_96BIT",
    "ciphertext": "BASE64URL_AEAD_CT",
    "aad": "method=QUERY;path=/api/v1/persons/query;event=grant.approved"
  }
}

Wrapper attributes

AtributoWhat does it meanRegla
algCryptographic algorithm/package used.Must be agreed upon by KydHub and Acme.
kidID of the public key used.Allows you to rotate keys without breaking integrations.
kem_ciphertextPost-quantum KEM ciphertext.Used by the recipient to retrieve the shared secret.
ecdh_public_keyEphemeral public key X25519 of the issuer.Participate in hybrid bypass.
nonceNonce used by AEAD encryption.It should not be repeated for the same key.
ciphertextEncrypted payload.Contains the actual protected data.
aadUnencrypted authenticated data.Link the payload with method, path, event or context.

Errors and fallback

ErrorWhat does it meanTo do
pq_encryption_not_enabledThe company does not have this layer enabled.Use normal Data API or request enablement.
unknown_key_idEl kid does not exist or was rotated.Get/register the current public key.
decrypt_failedThe payload could not be decrypted or AEAD authentication failed.Do not process the content; Log the error and retry with a valid key.
unsupported_algorithmThe algorithm is not supported by one of the parties.Negotiate a supported suite or disable this layer.
Don't use it to skip permissions: post-quantum encryption does not authorize data. If API key, scope or grant is missing, KydHub should fail closed before building or accepting sensitive payloads.

Markdown

Markdown version

Download .md
Loading formatted Markdown…
Cargando Markdown…

Reference

API Reference

The KWID identity endpoints (OAuth 2.0 + PKCE) and the server-to-server Data API reference, with parameters and request/response examples for every endpoint.

The client_secret and the X-API-KEY live only in your backend.

Three-level authorization: Data API reads require an active company API key, the required scopes, and an active grant, consent, authorized relationship, or another explicit access basis. Query company is for known authorized companies, not open directory search.