Admin API: Tenants
Operations on tenants in the Odudu admin API, one section each. Every request carries a bearer token; the index says which audience it needs and how a refusal is shaped.
GET /admin/tenants
Requires the "manage-tenants" capability.
Query parameters
| Name | Type | Required | Details |
|---|---|---|---|
cursor | string | no | The next value of the previous page, to continue after it. |
limit | integer | no | How many items to return. A value over 200 is treated as 200; the default is 50. Constraints: at least 1. |
name | string | no | Only tenants whose name starts with this. Constraints: at least 1 character. |
display_name | string | no | Only tenants whose display name starts with this. Constraints: at least 1 character. |
enabled | string | no | true for enabled tenants only, false for disabled ones. Constraints: one of true, false. |
Responses
200 OK
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
items | array of object | yes | The entries of this answer. |
items[] | object | yes | |
items[].id | string | yes | The tenant’s internal identifier, a UUID. |
items[].name | string | yes | The name in the tenant’s addresses and issuer. |
items[].display_name | string or null | yes | The name shown to people on sign-in pages, or null. |
items[].enabled | boolean | yes | A disabled tenant refuses every sign-in and token request. |
items[].created_at | string | yes | When the tenant was created. |
next | string | no | An opaque cursor for the next page; absent on the last page. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
POST /admin/tenants
Requires the "manage-tenants" capability.
A display_name is trimmed, and a blank one is stored as none (null), which the tenant’s pages replace with its name.
Request body
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
name | string | yes | The tenant name, used in every address of the tenant (/tenants/{name}/...). 1 to 63 lowercase letters, digits or hyphens, not starting or ending with a hyphen. Constraints: at least 1 character. |
display_name | string | no | A friendly name for people. Null when none was given. Constraints: no bidirectional control characters, at least 1 character. |
{
"name": "acme",
"display_name": "Acme Corporation"
}
Responses
201 Created
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
id | string | yes | The tenant’s internal identifier, a UUID. |
name | string | yes | The name in the tenant’s addresses and issuer. |
display_name | string or null | yes | The name shown to people on sign-in pages, or null. |
enabled | boolean | yes | A disabled tenant refuses every sign-in and token request. |
created_at | string | yes | When the tenant was created. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
GET /admin/tenants/count
Requires the "manage-tenants" capability.
Query parameters
| Name | Type | Required | Details |
|---|---|---|---|
name | string | no | Only tenants whose name starts with this. Constraints: at least 1 character. |
display_name | string | no | Only tenants whose display name starts with this. Constraints: at least 1 character. |
enabled | string | no | true for enabled tenants only, false for disabled ones. Constraints: one of true, false. |
Responses
200 OK
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
count | integer | yes | How many items match, up to a cap. |
capped | boolean | yes | True when more match than count says, because counting stops at the cap. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
POST /admin/tenant-imports
Requires the "manage-tenants" capability.
A set over 200 entries, more than 1,000 scopes, more than 200 scopes marked for every new client, a client given more than 200 scopes, or more than 200 default roles or groups is refused in the same 400, naming the limit and the count. Creates a new tenant from a document GET /admin/tenants/{tenant}/export answered, with its own signing key and a fresh secret for each confidential client, answered once here. A blank display_name, in the request or the document, is stored as none. Every problem with the request is refused together in one 400 whose errors lists each by its JSON path; a name already in use is refused with 409.
Request body
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
name | string | yes | The name of the tenant to create. Constraints: at least 1 character. |
display_name | string | no | A friendly name for the new tenant. Constraints: no bidirectional control characters, at least 1 character. |
document | any | yes | A tenant document, as GET /admin/tenants/{tenant}/export answers it. |
Responses
201 Created
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
tenant | object | yes | The tenant that was created. |
tenant.id | string | yes | The tenant’s internal identifier, a UUID. |
tenant.name | string | yes | The name in the tenant’s addresses and issuer. |
tenant.display_name | string or null | yes | The name shown to people on sign-in pages, or null. |
tenant.enabled | boolean | yes | A disabled tenant refuses every sign-in and token request. |
tenant.created_at | string | yes | When the tenant was created. |
client_secrets | array of object | yes | The secrets generated for the imported confidential clients. |
client_secrets[] | object | yes | |
client_secrets[].client_id | string | yes | The imported client. |
client_secrets[].secret | string | yes | Its newly generated secret. Shown only in this response. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
GET /admin/tenants/{tenant}
Requires the "manage-tenant" capability.
Path parameters
| Name | Type | Required | Details |
|---|---|---|---|
tenant | string | yes | The tenant being administered, addressed by name. |
Responses
200 OK
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
id | string | yes | The tenant’s internal identifier, a UUID. |
name | string | yes | The name in the tenant’s addresses and issuer. |
display_name | string or null | yes | The name shown to people on sign-in pages, or null. |
enabled | boolean | yes | A disabled tenant refuses every sign-in and token request. |
created_at | string | yes | When the tenant was created. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
PATCH /admin/tenants/{tenant}
Requires the "manage-tenant" capability.
Amends display_name and enabled. A display_name is trimmed, and a blank one is stored as none (null). name is refused with 400: it is already in the issuer URL of every token this tenant has minted. Disabling the system tenant is refused with 409, since every cross-tenant administrator authenticates against it. Once a disable of any other has committed, every live session in it is ended in batches of 500, each its own transaction, queuing a Back-Channel Logout Token for each relying party that used one; the answer comes when all have ended. A batch that fails is answered with 500 sessions-not-ended: the disable stands, and sending enabled: false again ends the rest. Its discovery document and key set stay served.
Path parameters
| Name | Type | Required | Details |
|---|---|---|---|
tenant | string | yes | The tenant being administered, addressed by name. |
Request body
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
(body) | object | yes | Constraints: other fields accepted. |
Responses
200 OK
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
id | string | yes | The tenant’s internal identifier, a UUID. |
name | string | yes | The name in the tenant’s addresses and issuer. |
display_name | string or null | yes | The name shown to people on sign-in pages, or null. |
enabled | boolean | yes | A disabled tenant refuses every sign-in and token request. |
created_at | string | yes | When the tenant was created. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
DELETE /admin/tenants/{tenant}
Requires the "manage-tenants" capability.
Deletes the tenant and every row it holds, in one transaction, recorded in the system tenant’s trail as tenant.delete. confirm must be the tenant’s own name, refused with 400 naming it otherwise. Refused with 409 for system; with 409 tenant-enabled until the tenant is disabled, which ends its sessions and queues their Logout Tokens; with 409 sessions-live while any session is still live; and with 409 logout-deliveries-pending until each of those tokens is delivered or has spent every attempt. The tenant’s keys go with it. Refused with 403 when the tenant’s subjects hold an admin capability the caller does not (the target ceiling, over every subject at once).
Path parameters
| Name | Type | Required | Details |
|---|---|---|---|
tenant | string | yes | The tenant being administered, addressed by name. |
Query parameters
| Name | Type | Required | Details |
|---|---|---|---|
confirm | string | yes | Constraints: at least 1 character. |
Responses
204 OK
Body: application/json
| Field | Type | Required | Details |
|---|---|---|---|
(body) | any | yes |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
GET /admin/tenants/{tenant}/export
Requires the "manage-tenant" capability.
A tenant too large for one document is refused with 413: more than 20,000 clients, roles or groups, 200,000 rows of a link table, or one role, group, scope or subject holding more than 200 of a set, which an import would refuse. The tenant’s configuration, every reference by name, with no secret in it: each secret a reader would expect is named under omitted by its JSON path. Additionally requires manage-clients, the capability every other client read requires, refused with 403 otherwise. ?include=subjects adds subjects and additionally requires view-users, refused with 403 otherwise and with 413 above 10,000 subjects.
Path parameters
| Name | Type | Required | Details |
|---|---|---|---|
tenant | string | yes | The tenant being administered, addressed by name. |
Query parameters
| Name | Type | Required | Details |
|---|---|---|---|
include | string | no | Constraints: always subjects. |
Responses
200 OK
Body: application/vnd.odudu.tenant+json
| Field | Type | Required | Details |
|---|---|---|---|
version | number | yes | The document format version; always 1. Constraints: always 1. |
settings | object | yes | The tenant’s settings. |
settings.display_name | string or null | yes | The name shown to people on sign-in pages. Constraints: no bidirectional control characters. |
settings.enabled | boolean | yes | Whether the tenant accepts sign-ins and token requests. |
settings.reset_password_allowed | boolean | yes | Whether users may ask for a password reset link by mail. |
settings.sso_session_idle_seconds | integer | yes | How long a browser session may sit idle before it ends. |
settings.sso_session_max_seconds | integer | yes | The longest a browser session lasts, however active. |
settings.password_min_length | integer | yes | The fewest characters a password may have. |
settings.password_require_digit | boolean | yes | Whether a password must contain a digit. |
settings.password_require_uppercase | boolean | yes | Whether a password must contain an upper-case letter. |
settings.password_require_lowercase | boolean | yes | Whether a password must contain a lower-case letter. |
settings.password_require_special | boolean | yes | Whether a password must contain a character that is not a letter or digit. |
settings.password_not_username | boolean | yes | Whether a password may not equal the username. |
settings.password_not_email | boolean | yes | Whether a password may not equal the email address. |
settings.password_history_depth | integer | yes | How many previous passwords a user may not reuse. 0 keeps none. |
settings.password_max_age_days | integer | yes | Days before a password must be changed. 0 means it never expires. |
settings.otp_required | boolean | yes | Whether every user must set up a one-time-code authenticator. |
settings.brute_force_max_failures | integer | yes | Failed sign-ins in a row before the account is locked out. |
settings.brute_force_lockout_seconds | integer | yes | How long a lockout lasts. |
settings.brute_force_max_lockout_seconds | integer | yes | The longest a lockout can grow to when it is tripped again. |
settings.brute_force_failure_reset_seconds | integer | yes | How long without a failed sign-in before the count starts again. |
settings.max_clients | integer | yes | The most clients the tenant may hold. |
settings.max_sessions_per_browser | integer | yes | The most sessions one browser may hold at once. |
settings.remember_me_allowed | boolean | yes | Whether the sign-in form offers to stay signed in. |
settings.remember_me_idle_seconds | integer | yes | How long a remembered session may sit idle. |
settings.remember_me_max_seconds | integer | yes | The longest a remembered session lasts. |
settings.audit_retention_days | integer | yes | How many days audit events are kept before odudu reap deletes them. |
settings.username_editable | boolean | yes | Whether a username may be changed after it is set. |
settings.access_token_ttl_seconds | integer | yes | How long an access token lives unless the client says otherwise. |
settings.id_token_ttl_seconds | integer | yes | How long an ID token lives unless the client says otherwise. |
settings.refresh_token_ttl_seconds | integer | yes | How long a refresh token lives unless the client says otherwise. |
settings.authorization_code_ttl_seconds | integer | yes | How long an authorization code can be redeemed. |
settings.login_ttl_seconds | integer | yes | How long a started sign-in may take to finish. |
settings.verify_email_ttl_seconds | integer | yes | How long a mailed email-verification link works. |
settings.reset_password_ttl_seconds | integer | yes | How long a mailed password-reset link works. |
settings.login_with_email | boolean | yes | Whether a user may sign in with the email address instead of the username. |
settings.audit_event_types | array of string | yes | The audit event types the tenant stores. |
flow | array of object | yes | The sign-in flow, in order. |
flow[] | object | yes | |
flow[].authenticator | string | yes | The sign-in step to run. |
flow[].requirement | string | yes | required, alternative, conditional or disabled. Constraints: one of required, alternative, conditional, disabled. |
clients | array of object | yes | The tenant’s clients. Secrets are never exported. |
clients[] | object | yes | |
clients[].client_id | string | yes | The client_id the application sends to the authorization and token endpoints. Chosen when the client is created and unique within the tenant. Constraints: no bidirectional control characters. |
clients[].name | string | yes | A display name for people, shown for example on the consent screen. Constraints: no bidirectional control characters. |
clients[].description | string or null | yes | Free text for administrators, or null when none was set. Constraints: no bidirectional control characters. |
clients[].type | string | yes | public for an application that cannot keep a secret (a single-page or native app), which must use PKCE; confidential for one that authenticates at the token endpoint. Constraints: one of public, confidential. |
clients[].enabled | boolean | yes | A disabled client cannot start a sign-in or obtain tokens, and tokens already issued to it stop being accepted. |
clients[].full_scope_allowed | boolean | yes | When true the client's tokens carry every role the user holds. When false, a role reaches a token only through a scope the client is assigned. |
clients[].registration_origin | string | yes | How the client came to exist: seeded by odudu seed or odudu init, anonymous or token through dynamic registration, or operator through the admin API. Constraints: one of seeded, anonymous, token, operator. |
clients[].redirect_uris | array of string | yes | The addresses the authorization server may send the user back to after sign-in. A request naming any other address is refused. Constraints: at most 200 items. |
clients[].grant_types | array of string | yes | The grants the client may use at the token endpoint, such as authorization_code, refresh_token, client_credentials. |
clients[].token_endpoint_auth_method | string | yes | How a confidential client authenticates at the token endpoint, for example client_secret_basic, client_secret_post or private_key_jwt. |
clients[].audiences | array of string | yes | The audiences (aud) added to the access tokens issued to this client, besides the issuer. Constraints: at most 200 items. |
clients[].access_token_ttl_seconds | integer or null | yes | How long this client's access tokens live, in seconds. Null uses the tenant's setting of the same name. |
clients[].id_token_ttl_seconds | integer or null | yes | How long this client's ID tokens live, in seconds. Null uses the tenant's setting of the same name. |
clients[].refresh_token_ttl_seconds | integer or null | yes | How long this client's refresh tokens live, in seconds. Null uses the tenant's setting of the same name. |
clients[].client_credentials_scopes | array of string | yes | The scopes the client may request with the client_credentials grant, where it acts as itself. Constraints: at most 200 items. |
clients[].web_origins | array of string | yes | The browser origins allowed to call the token and UserInfo endpoints from JavaScript (CORS). Constraints: at most 200 items. |
clients[].post_logout_redirect_uris | array of string | yes | The addresses the user may be sent to after a logout the client started. Constraints: at most 200 items. |
clients[].jwks | any or null | yes | The public keys the client authenticates with when its method is private_key_jwt, as a JWK Set. Null when it uses jwks_uri or a secret. |
clients[].jwks_uri | string or null | yes | The address the server fetches the client public keys from, instead of holding them in jwks. |
clients[].frontchannel_logout_uri | string or null | yes | The client page the user agent loads to tell the client the user signed out (front-channel logout). |
clients[].backchannel_logout_uri | string or null | yes | The client endpoint the server posts a logout token to when a session ends (back-channel logout). |
clients[].frontchannel_logout_session_required | boolean | yes | Whether the front-channel logout request carries the session id (sid) and issuer. |
clients[].backchannel_logout_session_required | boolean | yes | Whether the back-channel logout token carries the session id (sid). |
clients[].consent_required | boolean | yes | Whether the user must approve what the client asks for on a consent screen before tokens are issued. |
clients[].token_exchange_impersonation_allowed | boolean | yes | Whether the client may use token exchange (RFC 8693) to act as a user rather than on behalf of one. |
clients[].userinfo_signed_response_alg | string or null | yes | The algorithm that signs this client’s UserInfo responses. Null returns plain JSON. |
clients[].userinfo_encrypted_response_alg | string or null | yes | The key-management algorithm that encrypts this client’s UserInfo responses, or null. |
clients[].userinfo_encrypted_response_enc | string or null | yes | The content-encryption algorithm for encrypted UserInfo responses, or null. |
clients[].tls_client_auth_subject_dn | string or null | yes | The certificate subject a client using tls_client_auth must present, or null. |
clients[].client_uri | string or null | yes | A web page about the client application, shown to users. |
clients[].policy_uri | string or null | yes | The client privacy policy page, shown to users. |
clients[].tos_uri | string or null | yes | The client terms of service page, shown to users. |
clients[].id_token_signed_response_alg | string or null | yes | The algorithm that signs this client’s ID tokens. Null uses the tenant’s default. |
clients[].default_max_age | integer or null | yes | The longest age in seconds a sign-in may have before the user must sign in again, when the request does not say. Null sets none. |
clients[].require_auth_time | boolean | yes | Whether ID tokens always carry the auth_time claim. |
clients[].service_account_roles | array of object | yes | Constraints: at most 200 items. |
clients[].service_account_roles[] | object | yes | |
clients[].service_account_roles[].name | string | yes | Only roles whose name starts with this. |
clients[].service_account_roles[].client | string or null | yes | Only the roles of one owner: tenant for the tenant’s own roles, or a client’s internal id for the roles scoped to it. |
roles | array of object | yes | The tenant’s roles. |
roles[] | object | yes | |
roles[].name | string | yes | The role name. Constraints: no bidirectional control characters. |
roles[].client | string or null | yes | The client_id the role belongs to, or null for a tenant-wide role. |
roles[].description | string or null | yes | Free text for administrators, or null. Constraints: no bidirectional control characters. |
roles[].default_for_new_subjects | boolean | yes | Whether new users are given the role. |
roles[].builtin | boolean | yes | True for a role every tenant has, such as the admin roles. |
roles[].composites | array of object | yes | The roles this role contains. Constraints: at most 200 items. |
roles[].composites[] | object | yes | |
roles[].composites[].name | string | yes | Only roles whose name starts with this. |
roles[].composites[].client | string or null | yes | Only the roles of one owner: tenant for the tenant’s own roles, or a client’s internal id for the roles scoped to it. |
groups | array of object | yes | The tenant’s groups. |
groups[] | object | yes | |
groups[].path | string | yes | The group’s place in the hierarchy, such as /staff/eu. Constraints: no bidirectional control characters. |
groups[].description | string or null | yes | Free text for administrators, or null. Constraints: no bidirectional control characters. |
groups[].default_for_new_subjects | boolean | yes | Whether new users join the group. |
groups[].roles | array of object | yes | The roles every member of the group is given. Constraints: at most 200 items. |
groups[].roles[] | object | yes | |
groups[].roles[].name | string | yes | Only roles whose name starts with this. |
groups[].roles[].client | string or null | yes | Only the roles of one owner: tenant for the tenant’s own roles, or a client’s internal id for the roles scoped to it. |
scopes | array of object | yes | The tenant’s client scopes. |
scopes[] | object | yes | |
scopes[].name | string | yes | The scope name clients request. |
scopes[].description | string or null | yes | Free text for administrators, or null. Constraints: no bidirectional control characters. |
scopes[].include_in_id_token | boolean | yes | Whether its claims appear in the ID token. |
scopes[].include_in_access_token | boolean | yes | Whether its claims appear in the access token. |
scopes[].default_client_assignment | string or null | yes | How every new client gets the scope, or null for neither. Constraints: one of default, optional. |
scopes[].consent_text | string or null | yes | The sentence the consent screen shows for it, or null. Constraints: no bidirectional control characters. |
scopes[].display_order | integer | yes | Where it sorts on the consent screen; lowest first. |
scopes[].builtin | boolean | yes | True for a scope every tenant has. |
scopes[].roles | array of object | yes | The roles the scope carries into tokens. Constraints: at most 200 items. |
scopes[].roles[] | object | yes | |
scopes[].roles[].name | string | yes | Only roles whose name starts with this. |
scopes[].roles[].client | string or null | yes | Only the roles of one owner: tenant for the tenant’s own roles, or a client’s internal id for the roles scoped to it. |
scopes[].mappers | array of string | yes | The claim mappers the scope uses. |
scopes[].clients | array of object | yes | The clients the scope is assigned to, by client_id. |
scopes[].clients[] | object | yes | |
scopes[].clients[].client_id | string | yes | |
scopes[].clients[].assignment | string | yes | Constraints: one of default, optional. |
registration_policy | object | yes | Who may register themselves and register clients. |
registration_policy.registration_allowed | boolean | yes | Whether people may register themselves. |
registration_policy.verify_email | boolean | yes | Whether a registered address must be verified before sign-in. |
registration_policy.client_registration_policy | string | yes | Who may register clients dynamically: nobody (disabled), anybody (open) or the holder of an initial access token (token). Constraints: one of disabled, open, token. |
smtp | object or null | yes | The tenant’s mail relay, or null when it has none. |
smtp.host | string | yes | The SMTP relay’s host. |
smtp.port | integer | yes | The relay’s port. |
smtp.from_address | string | yes | The address mail is sent from. |
smtp.username | string or null | yes | The user name for the relay, or null. The password is never exported. |
smtp.starttls | boolean | yes | Whether STARTTLS is required. |
subjects | array of object | no | The tenant’s users, only when requested with include=subjects. |
subjects[] | object | yes | |
subjects[].username | string | yes | The sign-in name. Constraints: no bidirectional control characters. |
subjects[].email | string or null | yes | The email address, or null. |
subjects[].enabled | boolean | yes | Whether the subject may sign in. |
subjects[].profile | object | yes | The standard profile claims. |
subjects[].profile.name | string or null | yes | |
subjects[].profile.given_name | string or null | yes | |
subjects[].profile.family_name | string or null | yes | |
subjects[].profile.middle_name | string or null | yes | |
subjects[].profile.nickname | string or null | yes | |
subjects[].profile.preferred_username | string or null | yes | |
subjects[].profile.profile | string or null | yes | |
subjects[].profile.picture | string or null | yes | |
subjects[].profile.website | string or null | yes | |
subjects[].profile.gender | string or null | yes | |
subjects[].profile.birthdate | string or null | yes | |
subjects[].profile.zoneinfo | string or null | yes | |
subjects[].profile.locale | string or null | yes | |
subjects[].profile.phone_number | string or null | yes | |
subjects[].profile.phone_number_verified | boolean | yes | |
subjects[].profile.email_verified | boolean | yes | |
subjects[].profile.address_formatted | string or null | yes | |
subjects[].profile.address_street | string or null | yes | |
subjects[].profile.address_locality | string or null | yes | |
subjects[].profile.address_region | string or null | yes | |
subjects[].profile.address_postal_code | string or null | yes | |
subjects[].profile.address_country | string or null | yes | |
subjects[].roles | array of object | yes | The roles the subject holds directly. Constraints: at most 200 items. |
subjects[].roles[] | object | yes | |
subjects[].roles[].name | string | yes | Only roles whose name starts with this. |
subjects[].roles[].client | string or null | yes | Only the roles of one owner: tenant for the tenant’s own roles, or a client’s internal id for the roles scoped to it. |
subjects[].groups | array of string | yes | The groups the subject belongs to, by path. Constraints: at most 200 items. |
subjects[].required_actions | array of string | yes | The actions the subject must complete at next sign-in. |
omitted | array of string | yes | What the document leaves out on purpose, such as secrets and credentials. |
401 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
403 RFC 9457 problem details
Body: application/problem+json, an RFC 9457 problem document described under problem details.
Documentation coverage: 175 of 202 fields described (86%).