---
title: "Create a Credential Offer"
description: "Learn how to create credential offers from profiles in the walt.id Enterprise Issuer2 Service."
stack: "Enterprise Stack (commercial) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-offers/create-offer
generated: 2026-07-20
---
# Create a Credential Offer

This guide walks you through creating a credential offer from an existing profile. The offer generates an OID4VCI credential offer URL that any compliant wallet can claim.

## Prerequisites

Before creating an offer, ensure you have:

- **An Issuer2 service** — A running issuer service. See [Setup](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md).
- **A credential profile** — An existing profile to issue from. See [Create a Profile](https://docs.walt.id/enterprise-stack/services/issuer2-service/credential-profiles/create-profile.md).

---

## Create an Offer

The offer is created against the **profile** as the target — its full resource path, `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}`.

**Option: CURL**

**Endpoint:** `POST /v2/{profileTarget}/issuer-service-api/credentials/offers` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "PRE_AUTHORIZED",
  "valueMode": "BY_REFERENCE",
  "expiresInSeconds": 300
}'
```

**Path Parameters**

- **orgID**: _String_ (required) - Your organization ID, e.g. `test.enterprise-sandbox.waltid.dev`.
- **profileTarget**: _String_ (required) - The resource identifier of the credential profile, `{organizationID}.{tenantID}.{issuerServiceID}.{profileId}`, e.g. `waltid.tenant1.issuer1.profile-abc123`.

**Header Parameters**

- **Authorization**: _String_ (required) - Bearer token. Format: `Bearer {token}`.

**Body Parameters**

- **authMethod**: _String_ (required) - The OID4VCI flow used to claim the offer. Options:
  - `"PRE_AUTHORIZED"` — Pre-authorized code flow (no user authentication).
  - `"AUTHORIZED"` — Authorization code flow (user authentication required).
- **valueMode**: _String_ (optional) - How the offer is delivered (default: `BY_REFERENCE`). Options:
  - `"BY_REFERENCE"` — Offer URL contains a reference; the wallet fetches the full offer.
  - `"BY_VALUE"` — Full credential offer embedded in the URL.
- **issuerStateMode**: _String_ (optional) - Whether to include issuer state. Valid **only** for `AUTHORIZED` offers; omit it for `PRE_AUTHORIZED`. Options:
  - `"INCLUDE"` — Include issuer state for session correlation with the authorization server.
  - `"OMIT"` — No issuer state.
- **expiresInSeconds**: _Integer_ (optional) - Offer validity duration in seconds (default: 300). Set to `-1` for no expiration.
- **txCode**: _Object_ (optional) - Transaction code (PIN) configuration for the pre-authorized flow. See [Transaction Code (PIN)](#transaction-code-pin).
- **txCodeValue**: _String_ (optional) - A specific PIN value. If omitted, one is generated.
- **runtimeOverrides**: _Object_ (optional) - Override profile values for this offer only. See [Runtime Overrides](#runtime-overrides).
- **sessionId**: _String_ (optional) - Session ID for the issuance session. If omitted, a random UUID is generated.

---

##### Example Response

```json
{
  "offerId": "abc123-def456-ghi789",
  "profileId": "profile-abc123",
  "profileVersion": 1,
  "authMethod": "PRE_AUTHORIZED",
  "issuerStateMode": null,
  "expiresAt": 1704067500000,
  "credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789"
}
```

**Response Fields**

- **offerId**: _String_ - Unique identifier for this offer (also the issuance session ID).
- **profileId**: _String_ - The profile used to create this offer.
- **profileVersion**: _Integer_ - The version of the profile used.
- **authMethod**: _String_ - The authentication method.
- **issuerStateMode**: _String_ - The issuer state mode (present only for `AUTHORIZED` offers).
- **expiresAt**: _Long_ - Unix timestamp (ms) when the offer expires.
- **txCodeValue**: _String_ - The PIN value (only for pre-authorized flow with `txCode`).
- **credentialOffer**: _String_ - The OID4VCI credential offer URL.

**Response Codes**

- `201` — Offer created successfully.
- `400` — Invalid request body.
- `401` — Invalid or missing authentication token.

🎉 You've created a credential offer. Hand the `credentialOffer` URL to a wallet as a QR code or deep link to claim it.

---

## Transaction Code (PIN)

For the pre-authorized flow, you can require a PIN for additional security.

```json
{
  "authMethod": "PRE_AUTHORIZED",
  "txCode": {
    "input_mode": "numeric",
    "length": 6,
    "description": "Please enter the PIN sent to your email"
  }
}
```

**txCode Parameters:**

- **input_mode**: _String_ (optional) - Input type: `numeric` or `text` (default: `numeric`).
- **length**: _Integer_ (optional) - Length of the PIN.
- **description**: _String_ (optional) - Description shown to the user.

To set the PIN value yourself, provide `txCodeValue`:

```json
{
  "authMethod": "PRE_AUTHORIZED",
  "txCode": {
    "input_mode": "numeric",
    "length": 6
  },
  "txCodeValue": "123456"
}
```

---

## Runtime Overrides

You can override any profile value for a specific offer using `runtimeOverrides`:

```json
{
  "authMethod": "PRE_AUTHORIZED",
  "runtimeOverrides": {
    "issuerDid": "did:key:z6MkNewDid...",
    "issuerKeyId": "waltid.tenant1.kms1.differentKey",
    "subjectId": "did:key:z6MkSubjectDid...",
    "w3cVersion": "W3CV2",
    "credentialData": {
      "@context": ["https://www.w3.org/ns/credentials/v2"],
      "type": ["VerifiableCredential", "CustomCredential"],
      "credentialSubject": { "customField": "customValue" }
    },
    "mapping": { "id": "<uuid>" },
    "credentialStatus": {
      "statusCredentialConfig": "waltid.tenant1.credentialstatus.config2",
      "initialStatus": "0x0"
    },
    "notifications": {
      "webhook": { "url": "https://different-webhook.com/callback" }
    }
  }
}
```

### Available Override Fields

| Field | Description |
|-------|-------------|
| `issuerDid` | Override the issuer DID. |
| `issuerKeyId` | Override the signing key. |
| `x5Chain` | Override the X.509 certificate chain. |
| `subjectId` | Set the credential subject ID. |
| `credentialData` | Override the credential data. |
| `mapping` | Override the data mapping. |
| `selectiveDisclosure` | Override the SD-JWT selective disclosure configuration. |
| `idTokenClaimsMapping` | Override the ID token claims mapping. |
| `mDocNameSpacesDataMappingConfig` | Override the mDoc data mapping. |
| `credentialStatus` | Override the credential status configuration. |
| `notifications` | Override the notification settings. |
| `w3cVersion` | Override the W3C data model version. |

**Note:**

`issuer_state` correlates a credential offer with the later authorization request in the authorization code flow. The issuer creates it, the wallet treats it as opaque, and if present in the offer the wallet sends it back in the authorization request. `AUTHORIZED` offers omit `issuer_state` unless `issuerStateMode` is `INCLUDE` (the default is `OMIT`). Because offer-specific `runtimeOverrides` are correlated to the issuance session via `issuer_state`, set `issuerStateMode` to `INCLUDE` whenever an `AUTHORIZED` offer uses `runtimeOverrides` — otherwise, with no `issuer_state`, the authorization request cannot be matched back to this offer's overrides. Note that the create request itself is **not** rejected if you combine `runtimeOverrides` with `OMIT` (it returns `201`); the overrides simply cannot take effect.

---

## Examples

### Pre-Authorized Flow with PIN

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "PRE_AUTHORIZED",
  "txCode": {
    "input_mode": "numeric",
    "length": 4
  },
  "expiresInSeconds": 600
}'
```

### Authorization Code Flow

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "AUTHORIZED",
  "issuerStateMode": "INCLUDE",
  "expiresInSeconds": 900
}'
```

The same `issuer_state` from the offer is forwarded to the authorization server in the authorization request, e.g.:

```text
https://keycloak.demo.walt.id/realms/myrealm/protocol/openid-connect/auth?client_id=issuer_api&redirect_uri=.../issuer-service-api/openid4vci/callback&scope=openid_profile&state=74b5206d7a15c9ec&response_type=code&issuer_state=cadbeaf4-d412-4ce3-b8d4-e8d5c70d4a3f
```

See [Authorization Code Flow](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/authorization-code-flow.md) for the full sequence.

### Offer with Custom Credential Data

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "PRE_AUTHORIZED",
  "runtimeOverrides": {
    "credentialData": {
      "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://www.w3.org/2018/credentials/examples/v1"
      ],
      "type": ["VerifiableCredential", "UniversityDegree"],
      "credentialSubject": {
        "degree": { "type": "MasterDegree", "name": "Master of Computer Science" }
      }
    }
  }
}'
```

### Offer by Value

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "authMethod": "PRE_AUTHORIZED",
  "valueMode": "BY_VALUE"
}'
```

---

## Using the Credential Offer

The `credentialOffer` URL in the response can be:

1. **Displayed as a QR code** – The user scans it with their wallet app.
2. **Sent as a deep link** – The user clicks the link on a mobile device.
3. **Embedded in an email/message** – The user clicks to open in their wallet.

When the wallet resolves this URL, it fetches the full credential offer, displays the credential details to the user, and requests the credential using the appropriate flow.

---

## Next Steps

- [Notifications & Session Events](https://docs.walt.id/enterprise-stack/services/issuer2-service/notifications.md) – Monitor issuance progress in real time.
- [Protocol Flows](https://docs.walt.id/enterprise-stack/services/issuer2-service/protocol-flows/overview.md) – Understand the pre-authorized and authorization code sequences.
