AI Passport developer docs

Deep integrations

Create passports inside your signup flow, link sources from your UI, contribute memories as an attributed source, and read them back under the owner's passes.

Build AI Passport into your own product. A deep integration partner creates passports inside its own signup flow, links data sources from its own UI, contributes its records as an attributed memory source, and reads memory back under the owner's passes. Your user never leaves your flow to get a passport. Provider consent happens on the provider's origin. Every AI Passport permission decision happens on ours.

FactValue
Base URLhttps://passport.ego.ist
Server planePartner API key (Bearer pp_...)
User planeDelegated OAuth token pair, per user
Access token1 hour, refresh rotates
OnboardingManaged. One partner record, one key
Batch ceiling20 items per call, daily quota per partner

What it is

The partner API is the server-to-server counterpart of Sign in with AI Passport. Sign-in serves users who already have a passport and are present in a browser. Deep integration serves the moment before that: your signup form, your backend, a user who has never heard of us. You attest to the signup facts. We create the passport. You hold a delegated token that acts for that one user inside an explicit, owner-visible boundary.

The boundary is the point. Everything your integration does is attributed to it. The owner can see the integration and can sever it in one tap. Suspension turns off partner access everywhere on the next request. Your writes land as reference data from a named source, never as instructions. Your reads follow the same passes as every other app.

Onboarding

Partner onboarding is managed. There is no self-serve signup for the partner record itself. Contact us through the developer form or at support@ego.ist. You receive a partner id, an API key shown once at mint, and an OAuth client that the delegated tokens bind to. Key rotation overlaps: we mint the new key first, and we revoke the old prefix when you confirm the cutover.

Two agreement points are settled during onboarding. First, the creation-notice email, which tells a person that a passport was created for their address. The notice is on by default. Only a written agreement can suppress it. Second, your source slug, the stable name your memories carry. The slug is fixed once anything has been attributed under it.

Two credentials, two planes

The API key authenticates your backend to the server plane. It can do exactly one thing: provision. It is never valid from a browser.

The delegated token pair comes back from provisioning and authenticates the user plane, one pair per user. The stored pair holds standing, not capability. The partner scope is injected when the token is verified, and only while three things are true: the integration is enabled, your partner record is active, and the owner has not severed the delegation. Suspension and severing therefore take effect on the next request, with no token sweep.

EndpointPurpose
POST /partner/v1/usersServer plane. Provision a passport for a net-new email and receive the delegated token pair.
GET /partner/v1/connectorsUser plane. Connectable sources for this user, connection state, and the health disclosure.
POST /partner/v1/connectors/connectUser plane. Start a provider OAuth link; returns the provider URL and the pass interstitial URL.
POST /partner/v1/connectors/connect-keyUser plane. Submit an API-key connector's key on the user's behalf.
POST /partner/v1/source/itemsUser plane. Push your records as source-attributed memories, up to 20 per call.
POST /partner/v1/recallUser plane. Read memory: your own rows freely, cross-source rows under the owner's passes.
POST /tokenStandard OAuth refresh grant for the delegated pair. Rotation is one-shot.

Create a passport at signup

Call this from your backend the moment your own account is created. The email shall be net-new to AI Passport. Attestation is never accepted against an existing account. A 409 means the person already has a passport. In that case, offer Sign in with AI Passport instead. That grant creates the same delegation, with the owner deciding on our consent screen.

curl -X POST https://passport.ego.ist/partner/v1/users \
  -H 'authorization: Bearer pp_YOUR_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "email": "new-user@example.com",
    "external_user_id": "user-1042",
    "attestation": {
      "email_verified_at": "2026-08-13T12:00:00Z",
      "terms_ack": true,
      "terms_version": "your-terms-2026-01",
      "age_attested": true
    }
  }'
FieldPresenceMeaning
emailRequiredThe address your user verified with you. Net-new only; an existing passport answers 409.
external_user_idRequiredYour stable id for this user. Provisioning is idempotent on it.
attestation.email_verified_atRequiredWhen your system verified the address.
attestation.terms_ackRequiredYou presented our terms notice in your flow.
attestation.terms_versionRequiredWhich version you presented.
attestation.age_attestedRequiredThe user meets the age requirement.

A 201 carries created: true, the passport user id, the delegation id, and the token pair. A replay with the same external_user_id answers 200 with a fresh pair and created: false. You never get a duplicate account. Store the pair server-side, next to your own user record.

Unless your agreement suppresses it, the address receives a notice that you created a passport for it, with a claim link and a delete link. The delete link opens a confirmation page. A person who confirms there ends the delegation with the account.

With the delegated token, you can offer connector linking inside your own product. GET /partner/v1/connectors lists what this user can connect, what each source reads, and the current connection state. Sources that read health-category data carry special_category: true. The response carries the disclosure text and its version for them.

To start a link, post the connector name. For a health-category source, you shall first show the user the disclosure and echo its version in disclosure_version. Without the echo, the call refuses with health_disclosure_required and records nothing. Connectors that use an API key instead of provider OAuth take the user's key at POST /partner/v1/connectors/connect-key.

curl -X POST https://passport.ego.ist/partner/v1/connectors/connect \
  -H 'authorization: Bearer DELEGATED_ACCESS_TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "connector": "google-calendar" }'
{
  "authUrl": "https://.../oauth/connect/google-calendar?...",
  "grant_url": "https://passport.ego.ist/partner-connect/grant?ticket=..."
}

Open authUrl in your popup. It is a hosted connect page that hands the user to the provider's own consent screen, where they authorize the source. Then send the same popup to grant_url. Neither consent happens in your UI. That separation is what makes the embedded flow trustworthy.

A connector whose output needs no pass answers without a grant_url. In that case the link is complete after authUrl.

The pass interstitial

grant_url is a hosted page on our origin, armed by a single-use ticket that expires after 30 minutes. It asks the user one exact question: may your app read the categories this connector produces. Yes mints a bounded, 24-hour category pass for your OAuth client. No closes the flow, and the connector stays linked for the owner's own use. Passes never widen silently. The owner manages them from their passes page afterwards.

This page is where cross-source recall access comes from. Without a granted pass, recall still works, but it serves only your own rows for the ungranted categories.

Contribute memories as a source

Your records land as normal, owner-visible memories attributed to partner:your-slug. They have the same lifecycle as any connector-derived memory: searchable, exportable, and individually deletable. They do not pass through the owner's review inbox. Honest attribution plus revocation is the contract, and no review notification is sent.

curl -X POST https://passport.ego.ist/partner/v1/source/items \
  -H 'authorization: Bearer DELEGATED_ACCESS_TOKEN' \
  -H 'content-type: application/json' \
  -d '{
    "items": [
      {
        "external_id": "res-8841",
        "content": "Booked a window table at Lilia for Friday 19:30, party of 2.",
        "category": "event",
        "occurred_at": "2026-08-13T19:30:00Z"
      }
    ]
  }'
FieldPresenceMeaning
external_idRequiredYour stable id for the record, up to 128 characters, unique within the batch. The dedup handle.
contentRequiredThe record text, up to 8,000 UTF-8 bytes. It is stored inside a quoted untrusted-data envelope.
categoryOptionalOne of the writable memory categories. Unknown values and instruction are remapped to other.
occurred_atOptionalWhen the record happened (ISO 8601). Anchors the memory in time.

The whole batch is admitted or refused up front against your daily quota. Only new identities count. A retry of already-delivered ids is always safe and is never quota-blocked. Each item then answers independently:

StatusMeaning
storedThe item landed as an approved, source-attributed memory.
duplicateThis external_id already landed. The original memory id is returned.
rejectedPermanently refused: screened content, or content that cannot be stored. Do not retry unchanged.
lockedThe owner's memory store is sealed right now. Retry later.
retryA transient failure. Redeliver the same external_id; recovery is idempotent.

Content is screened before anything is recorded. Payment card numbers, credentials, and identifying personal data are rejected per item. Stored text is wrapped in a quoted envelope that marks it as untrusted reference data from your source. A model that reads it later treats it as a record, not as instructions.

Read memory back

Recall is a read-only call with two tiers in one answer. Rows your integration pushed are readable without a pass whenever the store is available: you already have that data, and a pass that protects it from you would be theater. A sealed store reports locked for every requested category, your own rows included. Everything else follows the category passes the owner granted your app, exactly as on every other read surface.

curl -X POST https://passport.ego.ist/partner/v1/recall \
  -H 'authorization: Bearer DELEGATED_ACCESS_TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "categories": ["event", "preference"], "query": "dinner", "limit": 20 }'
{
  "rows": [ { "memory_id": "...", "content": "...", "source": "partner:your-slug" } ],
  "skipped_categories": [ { "category": "preference", "reason": "no_pass" } ],
  "approval_url": "https://passport.ego.ist/passes"
}
Skip reasonMeaning
no_passThe owner has not granted your app a pass for this category. Your own rows for it still arrive.
once_onlyThe only live pass is single-use. This endpoint never spends it. Use the approval link to ask for a wider pass.
lockedThe owner's store is sealed. Nothing is readable right now, your own rows included.

The call is free of side effects. It never opens approval requests, never spends single-use passes, and never notifies the owner. When a category is skipped, surface approval_url to the user. The grant decision belongs on our origin.

Rotation, severing, deletion

Access tokens live one hour. Refresh at the standard /token endpoint with grant_type=refresh_token. Rotation is one-shot: persist the new pair before you discard the old one. A refresh that answers invalid_grant means the delegation is gone. The owner severed it, the account was deleted, or the pair was revoked. Treat that as the end of the relationship for this user until they reconnect through Sign in with AI Passport.

Severing is immediate and complete. Tokens stop verifying. In-flight reads are refused before they answer. New writes cannot claim. The memories your integration contributed remain the owner's, attributed and manageable like any others.

What the owner sees

Your integration is a first-class object in the owner's passport. The owner sees the delegation on their connected-apps surface, under your name. A partner-created account shows the owner a review moment when they first arrive, and keeps showing it until they acknowledge it. Every memory you contribute carries your source label. The passes you hold are listed with everything else on their passes page. Design your integration expecting the owner to look.

Errors

StatusErrorMeaning
401invalid_tokenUser plane. The token is missing or expired, or the standing behind it is gone: a suspended partner, a severed delegation, or a deleted account.
401invalid_key / key_revokedServer plane. The API key is malformed, unknown, or revoked.
403forbidden / partner_suspended / delegation_revokedThe credential stands but access does not. Provisioning answers delegation_revoked when the owner already severed this external_user_id.
409account_exists / account_pending_deletionProvisioning only. The email already has a passport, or one waiting for deletion. Route the user through Sign in with AI Passport.
429rate_limitedRequest throttle. Back off and retry.
429quota_exceededSource plane only. The daily item quota is spent; the answer carries daily_item_quota.
400invalid_items / invalid_categories / invalid_query / invalid_limitThe request shape is wrong. The body says which field.
503busy / unavailable / partner_not_readyTransient. Retry with backoff; nothing was stored or claimed.

On the user plane, refusals are deliberately content-free. A severed delegation, a suspended partner, and a deleted account all surface as the same invalid_token, and a refresh in that state answers invalid_grant. Treat either as the end of the delegation for this user. Build your retry logic on the status code and the error string. A 503 is safe to retry: nothing was stored or claimed.

A working example

The repository ships a runnable partner, examples/partner-demo. It is a pretend reservations service that provisions a passport at its own signup, links a calendar from its UI, pushes reservations as source items, and reads them back on the two-tier recall contract. It is a single dependency-free Node file. The README walks through minting a local partner record to run it against your own backend.

Questions, redirect hosts, or a partnership conversation: support@ego.ist.