{% extends "base.html" %} {% block title %}{{ t('nav.api') }} — painfree{% endblock %} {% block body %}

{{ t('api.heading') }}

{{ t('api.lede') }}

{# This page is about the machine-facing side of the service, and that side is English by decision: an endpoint path, a scope name, an error `code` and an audit `action` are contract values a consumer matches on, so translating one would break every client. What is translated here is the prose around them. What is not is said so on the page rather than left to look like a hole in the catalogue. #}

{{ t('api.english_notice') }}

{{ t('api.schema') }}

{% for path, name, blurb in documents %} {% endfor %}
{{ name }} {{ blurb }}
{{ base_url }}{{ path }}

{{ t('api.schema_note') }}

{{ t('api.authentication') }}

{% if auth_mode == 'basic' %}

{{ t('api.basic_credential') }} {{ t('api.basic_why') }}

{{ t('api.basic_get_one') }}

curl -u "$PAINFREE_ACCOUNT:$PAINFREE_PASSWORD" \
     -H "Idempotency-Key: $(uuidgen)" \
     -X POST {{ base_url }}/v1/connections/<connection>/payments \
     -d @payment.json

{{ t('api.basic_tls') }}

{{ t('api.basic_throttle') }}

{% else %}

{{ t('api.no_tokens') }} {{ t('api.no_tokens_why') }}

{{ t('api.get_a_token') }}

curl -H "Authorization: Bearer $TOKEN" \
     -H "Idempotency-Key: $(uuidgen)" \
     -X POST {{ base_url }}/v1/connections/<connection>/payments \
     -d @payment.json
{% endif %} {% if auth_mode == 'basic' %} {% else %} {% endif %}
{{ t('api.mode') }}{{ auth_mode }}
{{ auth_mode_reason }} {% if auth_mode == 'development' %}
{{ t('api.development_mode') }} {% endif %} {% if auth_mode == 'basic' %}
{{ t('api.basic_mode') }} {% endif %}
{{ t('api.basic_scheme') }}Basic realm="painfree"
{{ t('api.basic_hash') }}{{ password_hashing }}
{{ t('api.basic_lockout') }}{{ t('api.basic_lockout_value') }}
{{ t('api.issuer') }}{{ provider.issuer or t('api.not_configured') }}
{{ t('api.audience') }}{{ provider.audience or '—' }}
{{ t('api.client_id') }}{{ provider.client_id or '—' }}
{{ t('api.roles_claim') }}{{ provider.roles_claim }}
{{ t('api.scope_claim') }}{{ provider.scope_claim }}
{% if auth_mode == 'basic' %}

{{ t('api.basic_sign_out') }}

{% else %}

{{ t('api.roles_note') }}

{% endif %}

{{ t('api.you') }}

{{ t('access.subject') }}{{ me.subject }}
{{ t('api.issuer') }}{{ me.issuer }}
{{ t('api.authenticated_by') }}{{ me.method }}
{{ t('api.role') }}{{ me.role }} {% if me.role == 'admin' %}
{{ t('api.role_admin') }} {% else %}
{{ t('api.role_member') }} {% endif %}
{{ t('access.access') }} {% if me.role == 'admin' %} {{ t('api.every_connection') }} {% elif me.oversight and not me.grants %} oversight {{ t('access.every_connection_readonly') }} {% elif me.grants %} {% if me.oversight %}oversight {{ t('access.every_connection_readonly') }}
{% endif %} {% for grant in me.grants %} {{ grant.connection_id }} {{ grant.level }}{{ '' if loop.last else ' · ' }} {% endfor %} {% else %} {{ t('api.no_grants') }} {% endif %}
{{ t('api.claimed_roles') }}{{ me.roles | join(', ') or t('access.none') }} {% if me.unrecognised_role_count %} {# A count, not the names. The provider sent them, but this deployment does not keep a role name it has no meaning for -- not here either. What a reader needs is that the token carried names and none of them made them an administrator, which is the difference between a five-minute fix and an afternoon; the names themselves are in the provider's own console. #}
{{ t('api.unmapped') }} {{ me.unrecognised_role_count }} {% endif %} {% if not me.roles and not me.unrecognised_role_count %}
{{ t('api.no_roles_in_token') }} {{ provider.roles_claim }} {% endif %}
{{ t('api.expires') }}{{ me.expires_at or '—' }}
{{ t('api.effective_scopes') }} {% if me.scopes %} {% for name in me.scopes %}{{ name }}{{ '' if loop.last else ' · ' }}{% endfor %} {% if me.role != 'admin' and not me.oversight %}
{{ t('api.scopes_per_connection') }} {% elif me.oversight %}
{{ t('api.scopes_oversight') }} {% endif %} {% else %} {{ t('api.scopes_none') }} {% endif %}

{{ t('api.auth_me') }} GET /auth/me{{ t('api.auth_me_tail') }}

{{ t('api.privileges') }}

{{ t('api.privileges_lede') }}

{% for row in roles %}{% endfor %} {% for row in grant_levels %}{% endfor %} {% for scope in scopes %} {% for row in roles %} {% endfor %} {% for row in grant_levels %} {% endfor %} {% endfor %}
{{ t('api.scope') }}{{ t('api.what_it_allows') }}{{ t('api.role_column') }}: {{ row.role }}{{ t('api.level_column') }}: {{ row.level }}oversight {{ t('api.you_column') }}
{{ scope.scope }} {{ scope.summary }} {% if scope.admin_only %}
{{ t('api.admin_only') }} {% endif %}
{{ '✓' if scope.scope in row.scopes else '—' }}{{ '✓' if scope.scope in row.scopes else '—' }}{{ '✓' if scope.oversight else '—' }} {{ '✓' if scope.held else '—' }}

{{ t('api.oversight_carries') }} {% for name in oversight_scopes %}{{ name }}{{ '.' if loop.last else ' · ' }}{% endfor %} {{ t('api.oversight_derived') }}

{{ t('api.endpoints') }}

{{ t('api.endpoints_lede') }}

{% for row in routes %} {% endfor %}
{{ t('api.method') }}{{ t('api.path') }} {{ t('api.what') }}{{ t('api.needs') }}
{{ row.method }} {{ row.path }} {{ row.summary }} {% if row.role %} {{ row.role }} {{ t('api.only') }}
{{ t('api.a_role_not_a_scope') }} {% elif row.scopes %} {% for name in row.scopes %}{{ name }}{{ '' if loop.last else ' + ' }}{% endfor %} {% if row.per_connection %}
{{ t('api.on_this_connection') }} {% endif %} {% elif row.public %} {{ t('api.no_credential') }} {% else %} {{ t('api.any_authenticated') }} {% endif %}

{{ t('api.any_credential') }}

{% if unscoped %}

{{ t('api.any_credential_why') }}

{% else %}

{{ t('api.any_credential_none') }}

{% endif %}

{{ t('api.public_paths') }}

{% endblock %}