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.
| Fact | Value |
|---|---|
| Base URL | https://passport.ego.ist |
| Server plane | Partner API key (Bearer pp_...) |
| User plane | Delegated OAuth token pair, per user |
| Access token | 1 hour, refresh rotates |
| Onboarding | Managed. One partner record, one key |
| Batch ceiling | 20 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.
| Endpoint | Purpose |
|---|---|
POST /partner/v1/users | Server plane. Provision a passport for a net-new email and receive the delegated token pair. |
GET /partner/v1/connectors | User plane. Connectable sources for this user, connection state, and the health disclosure. |
POST /partner/v1/connectors/connect | User plane. Start a provider OAuth link; returns the provider URL and the pass interstitial URL. |
POST /partner/v1/connectors/connect-key | User plane. Submit an API-key connector's key on the user's behalf. |
POST /partner/v1/source/items | User plane. Push your records as source-attributed memories, up to 20 per call. |
POST /partner/v1/recall | User plane. Read memory: your own rows freely, cross-source rows under the owner's passes. |
POST /token | Standard 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
}
}'| Field | Presence | Meaning |
|---|---|---|
email | Required | The address your user verified with you. Net-new only; an existing passport answers 409. |
external_user_id | Required | Your stable id for this user. Provisioning is idempotent on it. |
attestation.email_verified_at | Required | When your system verified the address. |
attestation.terms_ack | Required | You presented our terms notice in your flow. |
attestation.terms_version | Required | Which version you presented. |
attestation.age_attested | Required | The 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.
Link sources from your UI
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"
}
]
}'| Field | Presence | Meaning |
|---|---|---|
external_id | Required | Your stable id for the record, up to 128 characters, unique within the batch. The dedup handle. |
content | Required | The record text, up to 8,000 UTF-8 bytes. It is stored inside a quoted untrusted-data envelope. |
category | Optional | One of the writable memory categories. Unknown values and instruction are remapped to other. |
occurred_at | Optional | When 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:
| Status | Meaning |
|---|---|
stored | The item landed as an approved, source-attributed memory. |
duplicate | This external_id already landed. The original memory id is returned. |
rejected | Permanently refused: screened content, or content that cannot be stored. Do not retry unchanged. |
locked | The owner's memory store is sealed right now. Retry later. |
retry | A 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 reason | Meaning |
|---|---|
no_pass | The owner has not granted your app a pass for this category. Your own rows for it still arrive. |
once_only | The only live pass is single-use. This endpoint never spends it. Use the approval link to ask for a wider pass. |
locked | The 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
| Status | Error | Meaning |
|---|---|---|
401 | invalid_token | User plane. The token is missing or expired, or the standing behind it is gone: a suspended partner, a severed delegation, or a deleted account. |
401 | invalid_key / key_revoked | Server plane. The API key is malformed, unknown, or revoked. |
403 | forbidden / partner_suspended / delegation_revoked | The credential stands but access does not. Provisioning answers delegation_revoked when the owner already severed this external_user_id. |
409 | account_exists / account_pending_deletion | Provisioning only. The email already has a passport, or one waiting for deletion. Route the user through Sign in with AI Passport. |
429 | rate_limited | Request throttle. Back off and retry. |
429 | quota_exceeded | Source plane only. The daily item quota is spent; the answer carries daily_item_quota. |
400 | invalid_items / invalid_categories / invalid_query / invalid_limit | The request shape is wrong. The body says which field. |
503 | busy / unavailable / partner_not_ready | Transient. 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.