# PayoutID API

## Authentication

Payout registers your application as a PayoutID client and gives you a `client_id` (a UUID) and a `client_secret`. The client also has its registered redirect URIs and the scopes it may request.

**Token endpoint.** Authenticate your client at `POST /oauth/token` in one of these ways:

- `client_secret_basic`: HTTP Basic authentication with `client_id` as the user name and `client_secret` as the password.
- `client_secret_post`: `client_secret` as a body parameter.

Both are enabled for every client by default. Always send `client_id` in the body as well. Clients that Payout configured for `client_secret_jwt` or `private_key_jwt` send a signed JWT in `client_assertion` instead of the secret.

**Verifications API.** Send the access token returned by the token endpoint in the `Authorization` header:

```
Authorization: Bearer <access_token>
```

Access tokens are JWTs signed by PayoutID. A token works only in the environment that issued it: `https://id-sa.payout.one` for Sandbox, `https://id.payout.one` for Production.


## Errors

**Token endpoint.** Errors are JSON objects with `error` and `error_description`:

```json
{
  "error": "invalid_grant",
  "error_description": "Given authorization code is invalid, revoked, or expired."
}
```

| Status | `error` | Meaning |
|---|---|---|
| 400 | `invalid_request` | A parameter is missing or malformed, or the PKCE code verifier is wrong |
| 400 | `invalid_grant` | The authorization code or refresh token is invalid, revoked or expired |
| 400 | `invalid_scope` | A requested scope is not enabled for your client or not allowed for this grant |
| 400 | `unsupported_grant_type` | The grant type is not enabled for your client |
| 401 | `invalid_client` | Unknown client, wrong client secret or redirect URI that does not match |
| 500 | `unknown_error` | Unexpected server error |

**Authorization redirect.** Errors are sent to your `redirect_uri` as the `error`, `error_description` and `state` query parameters. If the user denies access, you get `error=access_denied`. If `client_id` or `redirect_uri` is invalid, PayoutID cannot redirect back safely and shows an error page to the user.

**Verifications API.**

| Status | Body | Meaning |
|---|---|---|
| 403 | Plain text `UNAUTHORIZED: Missing or insuficient authorization` | The token is missing, invalid or expired, or it lacks the `verify` scope |
| 422 | `{"errors": {"<field>": ["<message>"]}}` | Validation failed |


## Authorization redirect for user

`GET https://id-sa.payout.one/oauth/authorize`

Endpoint where client should redirect user for authorization.

Open this URL in the user's browser; it is not an API call. PayoutID asks the user to log in or register if needed, then to approve the requested scopes. Afterwards it redirects the browser back to `redirect_uri`:

- on success, with `code` (the authorization code) and `state`;
- on error or when the user denies access, with `error`, `error_description` (when available) and `state`.

The authorization code is valid for at most 60 seconds and can be used once. Exchange it at `POST /oauth/token` with `grant_type=authorization_code`.

### Parameters

- `client_id` (query, required) — Client ID of application that is trying to authorize user
- `response_type` (query, required) — Must be `code` (authorization code flow).
- `redirect_uri` (query, required) — Redirect uri that was registered for this client
- `scope` (query) — Space-separated scopes to authorize. Each scope must be enabled for your client.
- `code_challenge` (query) — code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))), see RFC 7636 https://tools.ietf.org/html/rfc7636. Required when PKCE is enabled for your client.
- `code_challenge_method` (query) — Code challenge method. Use `S256`; `plain` is also accepted.
- `state` (query) — This parameter is sent back in the redirect url to identify request/response pairs

### Example

```bash
curl -X GET 'https://id-sa.payout.one/oauth/authorize?client_id=c24760a3-134f-4ff5-891b-e506a025a530&response_type=code&redirect_uri=https://www.example.com'
```


## Endpoint to retrieve authorization token

`POST https://id-sa.payout.one/oauth/token`

Endpoint where client can retrieve authorization token after successful authorization, refresh an expired access token with a valid refresh token, or get a token for itself with client credentials.

| `grant_type` | Use | Parameters |
|---|---|---|
| `authorization_code` | Exchange the `code` from the authorization redirect | `code`, `redirect_uri`; `code_verifier` when PKCE is enabled for your client |
| `refresh_token` | Get a new access token for the same user and client | `refresh_token`; optional `scope` |
| `client_credentials` | Get a token for your own client, without a user. The Verifications API needs scope `verify`. | optional `scope` |

Every grant needs `client_id` in the body. The client secret (HTTP Basic or `client_secret` in the body) is required for `client_credentials`, for `refresh_token` unless public refresh is enabled for your client, and for `authorization_code` when your client is confidential.

A refresh revokes the refresh token that was used, and the response carries a new one. `id_token` is returned by the `authorization_code` grant when the `openid` scope was granted. Scopes that only a user can grant, such as `PISPSUBMIT`, cannot be requested with `client_credentials`.

The body can be sent as `application/x-www-form-urlencoded` or as JSON.

### Request body

- `grant_type` — string (required) · Grant type. · one of authorization_code, refresh_token, client_credentials
- `client_id` — string<uuid> (required) · Client ID. Send it in the body even when you authenticate with HTTP Basic. · e.g. c24760a3-134f-4ff5-891b-e506a025a530
- `client_secret` — string · Client secret (`client_secret_post`). Omit it when you authenticate with HTTP Basic.
- `code` — string · `authorization_code`: the `code` from the authorization redirect. · e.g. 63a74bf4-fa3d-4b21-8350-8c51ae47d74a
- `redirect_uri` — string · `authorization_code`: the same `redirect_uri` that was used in the authorization request. · e.g. https://www.example.com
- `code_verifier` — string · `authorization_code`: PKCE code verifier. Required when PKCE is enabled for your client. · e.g. dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
- `refresh_token` — string · `refresh_token`: refresh token from a previous token response. · e.g. 4b217f27-836a-4a12-97a3-b962c23dedc5
- `scope` — string · Space-separated scopes. `client_credentials`: each scope must be enabled for your client. `refresh_token`: optional, at most the scopes of the original token, which are used when omitted. · e.g. verify account_info
- `client_assertion_type` — string · Only for clients configured for `client_secret_jwt` or `private_key_jwt`. · one of urn:ietf:params:oauth:client-assertion-type:jwt-bearer
- `client_assertion` — string · Only for clients configured for `client_secret_jwt` or `private_key_jwt`. Signed JWT with `sub` set to your client ID, `aud` set to the PayoutID URL of the environment, and `iss` and `exp` claims.

### Response 200

- `access_token` — string (required) · Access token (JWT). Send it as `Authorization: Bearer <access_token>`.
- `token_type` — string (required) · Token type. · one of bearer
- `expires_in` — integer (required) · Seconds until the access token expires. · e.g. 86400
- `refresh_token` — string · Refresh token for the `refresh_token` grant.
- `id_token` — string · OpenID Connect ID token (JWT). Returned by the `authorization_code` grant when the `openid` scope was granted.

### Example

```bash
curl -X POST 'https://id-sa.payout.one/oauth/token' \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
       "grant_type": "authorization_code",
       "client_id": "c24760a3-134f-4ff5-891b-e506a025a530",
       "code": "63a74bf4-fa3d-4b21-8350-8c51ae47d74a",
       "redirect_uri": "https://www.example.com",
       "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
     }'
```


## Create invitation

`POST https://id-sa.payout.one/api/v1/verification/invitation`

### Invitation to identity verification

Creates URL to which you can redirect customer to get him verified.

Use an access token from the `client_credentials` grant with scope `verify`. Add scope `account_info` to request bank account details (`bank_account_requested`) and scope `aml` to request an AML check (`aml_requested`).

When all data are retrieved, PayoutID sends the user details as a webhook to `notify_url`, and AML check results to `aml_notify_url`. The [identity verification guide](/payout-id/identity-verification.md) describes the webhooks and how to verify their signatures.

### Request body

- `provided_email` — string<email> · Customer email, prefilled in the verification. · e.g. john.doe@example.com
- `provided_name` — string · Customer first name, prefilled in the verification. · e.g. John
- `provided_surname` — string · Customer last name, prefilled in the verification. · e.g. Doe
- `bank_account_requested` — boolean · Indicates if bank account details will be present in the webhook. Requires scope `account_info`.
- `aml_requested` — boolean · Indicates if AML check is requested. Requires scope `aml` and `aml_notify_url`.
- `client_provided_iban` — string · If client requires to verify concrete IBAN, not any IBAN that customer have access to. Must be a valid IBAN of a supported Slovak or Czech bank. · e.g. CZ6508000000192000145399
- `callback_url` — string (required) · The URL to which customer will be redirected after completing required steps to start verification. · e.g. https://example.com
- `notify_url` — string (required) · Where webhook with user details will be sent when all data are retrieved. · e.g. https://example.com/webhooks/payout-id
- `aml_notify_url` — string · Where webhook with AML check details will be sent. Required if `aml_requested` is true. · e.g. https://example.com/webhooks/payout-id-aml

### Response 200

- `id` — string<uuid> (required) · The unique identifier for the verification process.
- `redirect_url` — string (required) · The URL to redirect to for the verification process.

### Example

```bash
curl -X POST 'https://id-sa.payout.one/api/v1/verification/invitation' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
       "client_provided_iban": "CZ6508000000192000145399",
       "bank_account_requested": true,
       "callback_url": "https://example.com",
       "notify_url": "https://example.com/webhooks/payout-id"
     }'
```

