API DOCUMENTATION
Partner API
The Partner API lets a partner create my-data.app accounts for its merchants, check whether an email already exists, and read each registration status. It is a server-to-server API authenticated with a bearer key.
Download openapi.json The OpenAPI 3.1 document is generated from the same contract and covers every endpoint, schema, scope and error code.
Quick start
Five steps from reading the requirements to the registration status.
- Read the requirementsCall GET /requirements with the requirements:read scope for the current legal version, documents, scopes and limits.
- Check the emailCall POST /users/check with the users:check scope to see whether the merchant needs a new account or linking.
- Create the registrationCall POST /registrations with the registrations:write scope, sending an Idempotency-Key and acceptance of the current legal version.
- Complete linking or automatic sign-inFor an existing email hand the merchant the linking_url. For a new account with automatic mode, the automatic_sign_in_url may be used instead.
- Follow the statusCall GET /registrations/{registration_id} with the registrations:read scope until the status is ready.
Base URLs per deployment
- Production
https://my-data.app/api/partners/v1- Sandbox
https://sandbox.my-data.app/api/partners/v1- Local development
http://localhost:4321/api/partners/v1
Authentication and scopes
Every request sends Authorization: Bearer with the partner key. Keys are created and rotated by the my-data.app administrator, shown once, and revoked immediately on rotation.
Scopes
requirements:read- Read the current legal version, documents, scopes and limits.
users:check- Check whether an email already owns a my-data.app account.
registrations:write- Create a merchant registration or a linking request.
registrations:read- Read the status of a registration owned by the same partner.
Partner capabilities
verified_email- Allows accepting the verified_email field. Without it, registrations are refused with partner_capability_required.
legal_acceptance- Allows accepting the current legal version on the merchant's behalf.
automatic_login- Allows login_mode: automatic and issuing automatic_sign_in_url for new accounts.
Limits
- Requests per key
- 60 per minute
- Body size
- 16 KiB JSON
- Automatic sign-in life
- 5 minutes, issued within 10 minutes
- Linking link life
- 15 minutes
Requirements and legal version
GET/api/partners/v1/requirements
Scope: requirements:read
Returns the current legal version, the legal documents the merchant must accept, the available scopes and the limits.
cURL example
curl -sS \
"$MY_DATA_BASE_URL/requirements" \
-H "Authorization: Bearer $MY_DATA_PARTNER_KEY" Responses
200Success401 / 403Invalid key or missing scope405Unsupported method. Use the method defined for the endpoint.429Rate limit exceeded. Check the Retry-After header.503Temporarily unavailable
Response example
Success
{
"legal_version": "2026-07-12",
"legal_documents": {
"terms": "https://my-data.app/terms",
"privacy": "https://my-data.app/privacy",
"data_processing_addendum": "https://my-data.app/data-processing-addendum"
},
"scopes": [
"requirements:read",
"users:check",
"registrations:write",
"registrations:read"
],
"limits": {
"bodyBytes": 16384,
"requestsPerMinute": 60,
"loginSeconds": 300,
"loginWindowSeconds": 600,
"linkSeconds": 900,
"leaseSeconds": 120
}
} The returned legal_version is sent in the registration legal_acceptance.version. Accepting an older version is refused with legal_version_outdated.
Email check
POST/api/partners/v1/users/check
Scope: users:check
Checks whether the email already owns a my-data.app account and whether a registration already exists for this partner.
Request body
{
"email": "merchant@example.com"
} cURL example
curl -sS \
"$MY_DATA_BASE_URL/users/check" \
-H "Authorization: Bearer $MY_DATA_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"merchant@example.com"}' Responses
200Success400Invalid data401 / 403Invalid key or missing scope405Unsupported method. Use the method defined for the endpoint.429 / 503Rate limit exceeded. Check the Retry-After header. / Temporarily unavailable
Response example
Success
{
"exists": true,
"registration": {
"registration_id": "pr_00000000-0000-4000-8000-000000000000",
"status": "ready"
}
} Success
{
"exists": false,
"registration": null
} Read-only. It never creates or changes an account. Use it to choose between a new account and linking.
Create registration
POST/api/partners/v1/registrations
Scope: registrations:write
Creates an account for a new email or a linking request for an existing email. The same email and password are used only when the email is new.
Request body
{
"customer_id": "customer_123",
"email": "merchant@example.com",
"password": "EXAMPLE_PASSWORD_REPLACE_ME",
"locale": "en",
"login_mode": "password",
"verified_email": {
"verified_at": "2026-10-01T10:00:00Z",
"reference": "verification_123"
},
"legal_acceptance": {
"version": "2026-07-12",
"accepted_at": "2026-10-01T10:01:00Z",
"reference": "consent_123"
}
} cURL example
curl -sS \
"$MY_DATA_BASE_URL/registrations" \
-H "Authorization: Bearer $MY_DATA_PARTNER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: idem_example_7c4a1d" \
-d @registration.json Responses
201Registration created200Idempotent replay of the same Idempotency-Key400 / 422Invalid data / Password or account fields rejected405Unsupported method. Use the method defined for the endpoint.409Identity conflict, registration busy or outdated legal version429 / 503Rate limit exceeded. Check the Retry-After header. / Temporarily unavailable
Response example
Ready with password sign-in
{
"registration_id": "pr_00000000-0000-4000-8000-000000000000",
"sign_in_url": "https://my-data.app/sign-in?lang=en",
"status": "ready"
} Ready with automatic sign-in
{
"registration_id": "pr_00000000-0000-4000-8000-000000000000",
"sign_in_url": "https://my-data.app/sign-in?lang=en",
"status": "ready",
"automatic_sign_in_url": "https://my-data.app/connect/partner#ticket=EXAMPLE_TICKET",
"expires_at": "2026-10-01T10:06:00Z"
} Linking required
{
"registration_id": "pr_00000000-0000-4000-8000-000000000000",
"sign_in_url": "https://my-data.app/sign-in?lang=en",
"status": "linking_required",
"linking_url": "https://my-data.app/connect/partner#link=EXAMPLE_LINK",
"expires_at": "2026-10-01T10:16:00Z"
} First use provisions the normal workspace in Test with the trial, with no billing or provider connection.
For an existing email, linking_url is returned and the merchant completes linking consent. The password is not changed, and the request gains no account access without that consent.
With login_mode automatic and the automatic_login capability enabled, a single-use automatic_sign_in_url with a five-minute life is returned, issued within ten minutes of completion.
Registration status
GET/api/partners/v1/registrations/{registration_id}
Scope: registrations:read
Returns only the identifier, the status and the normal sign_in_url for a registration owned by the same partner.
cURL example
curl -sS \
"$MY_DATA_BASE_URL/registrations/pr_00000000-0000-4000-8000-000000000000" \
-H "Authorization: Bearer $MY_DATA_PARTNER_KEY" Responses
200Success401 / 403Invalid key or missing scope404Unknown registration for this partner405Unsupported method. Use the method defined for the endpoint.429 / 503Rate limit exceeded. Check the Retry-After header. / Temporarily unavailable
Response example
Success
{
"registration_id": "pr_00000000-0000-4000-8000-000000000000",
"status": "processing",
"sign_in_url": "https://my-data.app/sign-in?lang=en"
} Linking and automatic sign-in URLs are never returned by this endpoint. Repeat the request until the status is ready.
Recover a registration or linking URL
POST/api/partners/v1/registrations
Scope: registrations:write
After an error or recoverable_failure, replay the same request to reconcile that registration. If status is linking_required and the linking_url was lost or expired, replay POST /registrations with the same Idempotency-Key and body to receive a replacement link.
Request body
{
"customer_id": "customer_123",
"email": "merchant@example.com",
"password": "EXAMPLE_PASSWORD_REPLACE_ME",
"locale": "en",
"login_mode": "password",
"verified_email": {
"verified_at": "2026-10-01T10:00:00Z",
"reference": "verification_123"
},
"legal_acceptance": {
"version": "2026-07-12",
"accepted_at": "2026-10-01T10:01:00Z",
"reference": "consent_123"
}
} cURL example
curl -sS \
"$MY_DATA_BASE_URL/registrations" \
-H "Authorization: Bearer $MY_DATA_PARTNER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: idem_example_7c4a1d" \
-d @registration.json Responses
200Idempotent replay of the same Idempotency-Key201Registration created405Unsupported method. Use the method defined for the endpoint.409Identity conflict, registration busy or outdated legal version503Temporarily unavailable
Response example
503
{
"error": {
"code": "identity_unavailable",
"message": "The identity service is unavailable. Retry with the same key."
}
} Keep the same key and body. A 503 may represent unconfirmed identity creation; replay reconciles that attempt and never blindly creates another account. The status GET does not return a replacement linking_url.
Registration fields
customer_id- Your own customer identifier. Reused to detect duplicates across retries.
email- The merchant email. The /users/check result decides a new account or linking.
password- Required only for a new email. Omit for existing accounts or completed replays. It is never stored and never changes an existing password.
locale- el or en for the merchant language.
first_name / last_name- Optional. Used when the account is created.
login_mode- password (default) or automatic for new accounts.
verified_email- Object with verified_at (ISO 8601) and reference. Declares the email verified by the partner. Future timestamps are allowed up to 60 seconds.
legal_acceptance- Object with version (the current legal_version), accepted_at and reference. The acceptance is stored as evidence.
Statuses
processing- The registration is being processed. Retry with the same Idempotency-Key.
ready- The registration is complete. The merchant signs in with sign_in_url.
linking_required- The email already exists. Linking through linking_url is required.
recoverable_failure- The attempt did not complete. Submit the registration again with the same key.
Error codes
invalid_partner_key- The bearer key is missing, unknown or revoked.
partner_scope_required- The key does not carry the required scope.
partner_capability_required- The partner does not have the required capability enabled.
rate_limited- The key exceeded the limit of 60 requests per minute.
invalid_json- The body is not a valid JSON object with Content-Type application/json.
invalid_input- A field is missing or invalid.
payload_too_large- The body is larger than 16 KiB.
legal_version_outdated- The legal version is not current. Read the requirements again.
idempotency_conflict- The same Idempotency-Key was reused with different data.
registration_busy- The registration is currently being processed. Retry with the same key.
registration_not_found- No such registration exists for the partner.
identity_conflict- The email or customer identity conflicts with a different existing identity.
password_rejected- The identity provider rejected the password or account fields.
identity_unavailable- The identity service is unavailable. Retry with the same key.
login_window_expired- The automatic sign-in window expired. Use sign_in_url.
login_unavailable- Automatic sign-in could not be issued. Use sign_in_url.
link_invalid- The linking link expired or is not valid.
link_email_mismatch- Linking requires the email that owns this registration.
auth_required- Authentication is required for this action.
invalid_origin- HTTPS is required outside local development; browser linking requests must have the same origin.
method_not_allowed- The method is not supported for this endpoint.
not_found- The endpoint or identifier was not found.
partner_unavailable- The partner service is unavailable. Retry later.