> ## Documentation Index
> Fetch the complete documentation index at: https://next-developers.opencard.io/llms.txt
> Use this file to discover all available pages before exploring further.

# TPA Flow — Full Walkthrough

> Complete TPA walkthrough — create the authorization, add signatories, send signing emails, and finish eID signing across Sweden, Norway, Denmark, and Finland.

TPA = **Transaction Processing Authorization**.

It's the legal agreement that says "this company's card transaction data is allowed to flow through OpenCard to the EMS." Without a signed TPA, no transactions.

<Tip>
  New here? Start with the [full customer onboarding flow](/ems/customer-onboarding) — billing, organization, webhook, and card holders around this TPA step.
</Tip>

***

## TPA lifecycle states

| Status               | Meaning                                  |
| -------------------- | ---------------------------------------- |
| `pending-signatures` | Signatories added but not all signed yet |
| `pending-activation` | Waiting for TPA to be confirmed          |
| `activated`          | Live. Transactions flow.                 |

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending-signatures: POST /tpas
    pending-signatures --> pending-activation: all signed
    pending-activation --> activated: TPA confirmed
```

***

## Step 1: Create TPA

```
POST /api/v1/application/accounts/{accountId}/tpas
Scope: account-tpas-write
```

```json theme={null}
{
  "card_issuer_id": 1,
  "name": "Acme AB",
  "country": "SE",
  "organization_number": "5561234567",
  "language": "sv"
}
```

| Field                 | Required | Notes                                                                                                      |
| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `card_issuer_id`      | ✅        | Which card program — from issuers **enabled on your account** (`GET .../accounts/{accountId}/cardissuers`) |
| `name`                | ✅        | Overwritten by registry name if found                                                                      |
| `country`             | ✅        | `SE`, `DK`, `NO`, or `FI` only                                                                             |
| `organization_number` | ✅        | Format depends on country (see below)                                                                      |
| `language`            | ❌        | `sv`, `no`, `da`, `en`, `fi`. Defaults from country                                                        |

**Response `201`:**

```json theme={null}
{
  "id": 42,
  "account_id": 1,
  "card_issuer_id": 1,
  "name": "Acme AB",
  "country": "SE",
  "organization_number": "5561234567",
  "activated": false,
  "signatures_verified": false,
  "signed_document_path": null,
  "signatures_verified_at": null,
  "created_at": "2026-06-08T10:00:00.000000Z",
  "updated_at": "2026-06-08T10:00:00.000000Z"
}
```

### Org number validation

| Country   | Rule              | Example      |
| --------- | ----------------- | ------------ |
| 🇸🇪 `SE` | exactly 10 digits | `5561234567` |
| 🇳🇴 `NO` | exactly 9 digits  | `987654321`  |
| 🇩🇰 `DK` | exactly 8 digits  | `12345678`   |
| 🇫🇮 `FI` | `\d{7}-\d{1}`     | `1234567-8`  |

***

## Step 2: Check who can sign (recommended)

Before adding signatories, query the public registry:

```
GET /accounts/{accountId}/publicrecords?country=SE&organization_number=5561234567
Scope: public-records-read
```

**Example response:**

```json theme={null}
{
  "organization_number": "5561234567",
  "name": "Acme AB",
  "address_street": "Storgatan 1",
  "address_zip": "111 22",
  "address_city": "Stockholm",
  "signature_combinations": [
    {
      "signatories": [
        { "admin_sign": false, "name": "Anna Andersson", "ssn_hash": null },
        { "admin_sign": false, "name": "Erik Eriksson", "ssn_hash": null }
      ]
    }
  ]
}
```

Each entry in `signature_combinations` is one valid signing group — **everyone listed in that group** must sign for the combination to be satisfied.

<Note>
  The registry does not always list who may sign. Many organization types — foundations, associations, and similar entities — are not fully registered as companies in public records across the Nordic countries. The response may include company details but an **empty** `signature_combinations` array. In that case OpenCard cannot suggest signatories; your client must decide internally who is authorized to sign and you add them as signatories manually.
</Note>

***

## Step 3: Add signatories → email fires 📧

```
POST /accounts/{accountId}/tpas/{tpaId}/signatories
Scope: account-tpa-signatories-write
```

```json theme={null}
{
  "email": "ceo@acme.se",
  "name": "Anna Andersson",
  "country_code": "+46",
  "phone_number": "0701234567"
}
```

Only `email` is required. `phone_number` is stored but not returned in API responses.

**What happens immediately:**

1. `token` = random 40-char string generated
2. Signatory record created with `signed: false`
3. **Email queued** via issuer-specific mailer:
   * **Subject:** `Authorise TPA for {tpaName}`
   * **Template:** TPA signing invitation
   * **Link:** `https://{env}/accounts/{accountId}/tpas/{tpaId}/sign/{token}`
   * **Language:** English (hardcoded for signatory emails)

The signatory does **not** need an OpenCard account. The token in the URL is their auth.

### Update / delete signatory

```
PUT  .../signatories/{tpaSignatoryId}   ← only if signed=false, resends email
DELETE .../signatories/{tpaSignatoryId} ← only if signed=false
```

### Reminder emails

If a signatory has not signed after 7+ days, OpenCard may send a reminder email automatically.

***

## Step 4: Signatory signs with eID ✍️

This is a **web flow**, not an API call. The signatory:

1. Clicks email link → `GET /accounts/{accountId}/tpas/{tpaId}/sign/{token}`
2. Sees TPA legal text (markdown rendered)
3. Clicks "Sign" → eID widget loads
4. Completes signing with national eID — see [eID Signing](/ems/eid-signing)
5. eID result is posted back → `POST .../sign/{token}`

**When all required signatories have signed**, OpenCard verifies signing rights against `signature_combinations` from the registry (stored on the TPA at creation) for Sweden and Norway.

If no signatory combinations were available from the registry, OpenCard cannot verify automatically — the TPA goes through **manual review** before `signatures_verified` is set.

***

## Step 5: TPA fully signed → stuff happens

When signing is complete:

1. 📄 **Signed PDF generated** with all signatory eID approval blocks
2. 📧 **Email to each signatory** with signed PDF attached
3. 📧 **Card issuer notified** (signed PDF delivered)
4. 🔔 **`tpa.signed` webhook** fires on orgs that subscribed

**Webhook payload:**

```json theme={null}
{
  "id": "42",
  "name": "Acme AB",
  "organization_number": "5561234567",
  "url": "https://api.opencard.io/files/tpas/abc123.pdf",
  "organization": {
    "reference_id": "client_acme_001"
  }
}
```

### Download signed PDF via API

```
GET /accounts/{accountId}/tpas/{tpaId}/signeddocuments
→ application/pdf
```

***

## Step 6: Activation

TPA moves to `activated` when the TPA is confirmed — triggered on the issuer side, not something you call directly.

Until activated, transactions won't flow even if TPA is signed.

***

## Delete TPA

```
DELETE /accounts/{accountId}/tpas/{tpaId}
```

If TPA was activated → issuer termination email sent, cascades delete organizations/webhooks/cardholders linked to it.

<Warning>
  Only delete TPAs you're sure are unused. Activated TPA deletion is destructive.
</Warning>

***

## TPA embed plugin (optional)

OpenCard ships a JS wizard (`ocTPA`) for self-service TPA onboarding on your site. It collects issuer selection, signatory combos, billing info — then calls your `onDataSend` callback with the payload for you to hit the APIs.

See [Plugins](/ems/plugins).
