---
title: "Create an Offer"
description: "Learn how to create credential offers from profiles in the Issuer2 API."
stack: "Community Stack (open source) — version 1.1.1"
stack_version: "1.1.1"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/issuer2/credential-offers/create-offer
generated: 2026-10-01
---
# Create a Credential Offer

This guide walks you through creating a credential offer from one or more existing profiles. The offer generates an [OID4VCI](https://docs.walt.id/concepts/data-exchange-protocols/openid4vci.md) credential offer URL that can be claimed by any compliant wallet.

## Prerequisites

Before creating an offer, ensure you have:

1. [Issuer2 API running](https://docs.walt.id/community-stack/issuer2/setup.md)
2. [Credential Profile configured](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles.md) — or use one of the [ready-made default profiles](https://docs.walt.id/community-stack/issuer2/credential-issuance/sd-jwt-vc.md)

## Create an Offer

**Option: CURL**

**Endpoint:** `POST /issuer2/credential-offers` | [API Reference](https://issuer2.demo.walt.id/swagger/index.html)

##### Example Request

```bash
curl -X 'POST' \
  'http://localhost:7005/issuer2/credential-offers' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED"
  }'
```

**Body Parameters**

- **profileId**: _String_ (required) - The ID of the credential profile to use.
- **authMethod**: _String_ (required) - Authentication method: `PRE_AUTHORIZED` or `AUTHORIZED`.
- **valueMode**: _String_ (optional) - How the credential offer is delivered (default: `BY_REFERENCE`). `BY_REFERENCE` — offer URL contains a reference, wallet fetches the full offer. `BY_VALUE` — full credential offer embedded in the URL.
- **issuerStateMode**: _String_ (optional) - Whether to include issuer state for `AUTHORIZED` offers (default: `INCLUDE`). `OMIT` — no issuer state. `INCLUDE` — include issuer state for session correlation with authorization servers.
- **expiresInSeconds**: _Integer_ (optional) - Offer validity duration in seconds (default: 300). Set to `-1` for no expiration.
- **txCode**: _Object_ (optional) - Transaction code (PIN) configuration for pre-authorized flow. See [Transaction Code Configuration](#transaction-code-configuration).
- **txCodeValue**: _String_ (optional) - Specific PIN value (if not provided, one is generated).
- **runtimeOverrides**: _Object_ (optional) - Override profile values for this offer. See [Runtime Overrides](#runtime-overrides).
- **sessionId**: _String_ (optional) - Session ID for the offer. If not provided, a random session ID will be generated.

---

##### Example Response

```json
{
  "offerId": "abc123-def456-ghi789",
  "profileId": "openBadgeCredential",
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1704067500000,
  "credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7005%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789"
}
```

**Response Fields**

- **offerId**: _String_ - Unique identifier for this offer.
- **profileId**: _String_ - The profile used to create this offer.
- **authMethod**: _String_ - The authentication method.
- **issuerStateMode**: _String_ - The issuer state mode (for authorized flow).
- **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.

**Note:**

Send either a single-profile body (`profileId` plus optional `runtimeOverrides`) or a multi-credential body (`credentials[]`). Do not send both, and do not put top-level `runtimeOverrides` on a `credentials[]` request.

## Multiple Credentials

Use `credentials[]` to offer several profiles, or the same profile with different `credentialData` / `credentialStatus` overrides, in one URL. The wallet redeems each offered item with a **separate** Credential Endpoint request.

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "credentials": [
      { "profileId": "openBadgeCredential" },
      { "profileId": "isoMdl" }
    ],
    "authMethod": "PRE_AUTHORIZED"
  }'
```

Each item can carry its own `runtimeOverrides`. Do not send top-level `runtimeOverrides` on a `credentials[]` request.

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "credentials": [
      {
        "profileId": "openBadgeCredential",
        "runtimeOverrides": {
          "credentialData": {
            "credentialSubject": {
              "achievement": { "name": "Bachelor of Science" }
            }
          }
        }
      },
      { "profileId": "isoMdl" }
    ],
    "authMethod": "PRE_AUTHORIZED"
  }'
```

The create-offer **response** for a `credentials[]` request has the same common fields (`offerId`, `authMethod`, `expiresAt`, `credentialOffer`, …) but **no** `profileId` and no `credentials` array. The wallet reads the offered configuration IDs from the OID4VCI offer itself.

If two items share the same `credential_configuration_id` (for example the same profile twice with different datasets), the wallet must request `authorization_details` at the token endpoint and then use `credential_identifier` on each Credential Request.

A homogeneous cryptographic batch — multiple holder proofs (`proofs.jwt`) for **one** of those items on a single Credential Request — is separate. The shipped `issuer-service.conf` enables it with `batchCredentialIssuance { batchSize = 10 }`. See [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md#batchcredentialissuance). Each proof issues a copy of the selected item's dataset and status entry.

## Examples

### Pre-Authorized Flow with PIN

Require a PIN for additional security:

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED",
    "txCode": {
      "input_mode": "numeric",
      "length": 6,
      "description": "Please enter the PIN sent to your email"
    }
  }'
```

**Response includes the PIN:**

```json
{
  "offerId": "abc123-def456-ghi789",
  "profileId": "openBadgeCredential",
  "authMethod": "PRE_AUTHORIZED",
  "expiresAt": 1704067500000,
  "txCodeValue": "123456",
  "credentialOffer": "openid-credential-offer://..."
}
```

### Pre-Authorized Flow with Custom PIN

Specify your own PIN value:

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED",
    "txCode": {
      "input_mode": "numeric",
      "length": 4
    },
    "txCodeValue": "1234"
  }'
```

### Authorization Code Flow

For user authentication via external IdP:

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "identityCredentialSdJwt",
    "authMethod": "AUTHORIZED",
    "issuerStateMode": "INCLUDE"
  }'
```

`issuerStateMode: "INCLUDE"` lets the Issuer2 API correlate the authorization request it receives back with this offer's issuance session. See [Authorization Code Flow](https://docs.walt.id/community-stack/issuer2/protocol-flows/authorization-code-flow.md) for the full sequence, including how (and when) `issuer_state` is forwarded to the external authorization server.

**Note:**

`issuer_state` can be viewed as a mechanism in OpenID4VCI for correlating 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 it is present in the offer the wallet sends it back in the Authorization Request. Issuer2 includes `issuer_state` by default for `AUTHORIZED` offers. If you explicitly set `issuerStateMode` to `OMIT`, do not use `runtimeOverrides`; offer-specific overrides require issuer-state correlation and the request is rejected.

**Note:**

You can run this example without setting up an identity provider. Out of the box it uses a [hosted walt.id test provider](https://docs.walt.id/community-stack/issuer2/configurations/config-files/authentication-service.md#default-test-provider) — open the offer in a wallet and log in as `jane@walt.id` / `jane`. Because the `identityCredentialSdJwt` profile maps `given_name` and `family_name` from the ID token, the issued credential is populated with Jane's name from her login.

### Offer by Value

Embed the full offer in the URL (useful for offline scenarios):

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED",
    "valueMode": "BY_VALUE"
  }'
```

### Custom Expiration

Set a longer expiration time:

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED",
    "expiresInSeconds": 3600
  }'
```

### Runtime Overrides

Override profile values for a specific offer — including the signing key, not just the credential data:

```bash
curl -X POST 'http://localhost:7005/issuer2/credential-offers' \
  -H 'Content-Type: application/json' \
  -d '{
    "profileId": "openBadgeCredential",
    "authMethod": "PRE_AUTHORIZED",
    "runtimeOverrides": {
      "issuerKey": {
        "type": "jwk",
        "jwk": {
          "kty": "EC",
          "d": "AIZNxblzgCJ4mFW76wpzilIuXofOk1yEccgYMr9-isQ",
          "crv": "P-256",
          "kid": "bx1HUtz7-qAwslnQTDrxk1_kKWJ5kyl6KZCZUqGPH9A",
          "x": "j0zPEQ3j3NkdhSZpkQD4Q1XIQTEDNS21X4I_WH5Z7w4",
          "y": "zl6YSyhrfTP5KP7R7t0xuWeFsqV0Z-nwbmd_61yBbFo"
        }
      },
      "issuerDid": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiYngxSFV0ejctcUF3c2xuUVREcnhrMV9rS1dKNWt5bDZLWkNaVXFHUEg5QSIsIngiOiJqMHpQRVEzajNOa2RoU1pwa1FENFExWElRVEVETlMyMVg0SV9XSDVaN3c0IiwieSI6InpsNllTeWhyZlRQNUtQN1I3dDB4dVdlRnNxVjBaLW53Ym1kXzYxeUJiRm8ifQ",
      "credentialData": {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://www.w3.org/2018/credentials/examples/v1"
        ],
        "type": ["VerifiableCredential", "OpenBadgeCredential"],
        "credentialSubject": {
          "achievement": {
            "name": "Master of Computer Science"
          }
        }
      },
      "notifications": {
        "webhook": {
          "url": "https://my-server.com/webhook/special-offer"
        }
      }
    }
  }'
```

**Note:**

`issuerKey` (and `issuerDid`/`x5Chain`) override the profile's default signing key for this offer only — useful for issuing a one-off credential with a different key without touching the profile itself. Provide both together, since a mismatched key/DID pair will fail to sign.

### Available Override Fields

| Field                             | Description                              |
|-----------------------------------|-------------------------------------------|
| `issuerDid`                       | Override the issuer DID                  |
| `issuerKey`                       | Override the signing key                 |
| `x5Chain`                         | Override the X.509 certificate chain     |
| `credentialData`                  | Override the credential data             |
| `mapping`                         | Override the data mapping                |
| `selectiveDisclosure`             | Override the SD-JWT selective disclosure configuration |
| `idTokenClaimsMapping`            | Override ID token claims mapping         |
| `mDocNameSpacesDataMappingConfig` | Override mDoc data mapping               |
| `authorizedTransactionDataTypes`  | Override mDoc OpenID4VP transaction-data types authorized in the MSO `KeyAuthorizations` |
| `msoData`                         | Field-merge MSO `ValidityInfo` (`validFrom`, `validUntil`, `expectedUpdate`) onto the profile. mDoc only. |
| `credentialStatus`                | Override credential status configuration |
| `notifications`                   | Override notification settings           |
| `expectedCredentialProofKeyJwk`   | Expected holder proof public key (JWK)   |

## Transaction Code Configuration

The `txCode` object configures PIN requirements:

| Property | Type | Description |
|----------|------|-------------|
| `input_mode` | String | Input type: `numeric` or `text` |
| `length` | Integer | Length of the PIN |
| `description` | String | Description shown to the user |

## Using the Credential Offer URL

The `credentialOffer` URL can be:

1. **Displayed as QR Code** – User scans with their wallet app
2. **Sent as Deep Link** – User clicks link on mobile device
3. **Embedded in Email/Message** – User clicks to open in wallet

### Example Decoded Offer URL

```
openid-credential-offer://?credential_offer_uri=http://localhost:7005/openid4vci/credential-offer?id=abc123-def456-ghi789
```

## Next Steps

- [Notifications](https://docs.walt.id/community-stack/issuer2/notifications.md) – Configure webhook notifications
- [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) – Populate credentials with dynamic data
- [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md#batchcredentialissuance) – Enable Credential Endpoint `proofs.jwt` batch issuance
