Offline Aadhaar KYC

2 endpoints · Core API

What is it?

Offline Aadhaar KYC performs a synchronous, no-redirect Aadhaar identity verification — your users never leave your app and there is no browser handoff. /init accepts either typed demographic fields, a photo of the Aadhaar card front, or both — if a field wasn't typed, OCR fills it in from the photo before verification runs. For most users with an existing linked account, the verified identity (and signed identity document, where available) comes back in the same call. A smaller share of users complete one additional one-time-password step via /verify-otp.

OCR is never itself the verification. Whatever ends up populated — typed or OCR-filled — is exactly what gets checked against Aadhaar by this same call, so a bad OCR read simply surfaces as a 422, never a false positive.


Quick Start

Option A: typed fields

POST /api/v1/aadhaar/offline-kyc/init

{
  "aadhaarNumber": "999999999999",
  "name": "RAM KUMAR",
  "dob": "03071990",
  "gender": "M",
  "mobile": "9999999999"
}

Option B: photo instead of typing

POST /api/v1/aadhaar/offline-kyc/init

{
  "mobile": "9999999999",
  "aadhaarImage": "<base64 photo of the Aadhaar card front>"
}

Mix and match freely — type what you have, attach the photo for the rest; a typed field always wins over an OCR-read one for that same field.

aadhaarImage must be JPEG or PNG. A PDF (including a downloaded eAadhaar) or a HEIC photo straight from an iPhone is rejected with a 400 naming the format — the OCR engine's synchronous API reads only those two. Convert first, or type the details instead.

Field formats: dob is DDMMYYYY, day first — 03071990 is 3 July 1990, not 7 March. gender is a single character, M or F — no other value is accepted. aadhaarNumber is 12 digits; mobile is 10 digits. Both dob and gender are validated before the verification call, so a wrong format returns a 400 naming the problem rather than an ambiguous "did not match Aadhaar records".

Response (either option): transactionId, otpRequired, and — when otpRequired is false — the verified result inline, as 200 OK. When otpRequired is true instead, the call still succeeded but verification isn't finished — that comes back as 202 Accepted (not 200), with no result yet and message naming the mobile the OTP was sent to.

Step 2 (only if otpRequired is true): Verify the OTP

POST /api/v1/aadhaar/offline-kyc/verify-otp

{
  "transactionId": "<from init>",
  "otp": "123456"
}

Response: the verified result.

Result shape

{
  "verifiedId": "stable pseudonymous identifier",
  "name": "RAM KUMAR",
  "dob": "03071990",
  "gender": "M",
  "mobile": "9999999999",
  "verifiedAtUtc": "2026-08-21T10:18:30Z",
  "document": { "data": "<UIDAI-signed eAadhaar XML>", "mime": "application/xml" },
  "details": {
    "name": "RAM KUMAR",
    "dob": "03-07-1990",
    "gender": "M",
    "address": "12, MG Road, Near Park",
    "village": "Pune",
    "district": "Pune",
    "state": "Maharashtra",
    "pinCode": "411001",
    "aadhaarNumber": "XXXXXXXX1234",
    "photo": "<base64 JPEG>",
    "kycDateTime": "25-08-2026/13:33:52"
  },
  "fieldsFilledFromImage": ["dob", "gender"]
}

details carries the demographic data that exists only inside the signed document — full address, photo, KYC timestamp. Its aadhaarNumber is masked to the last four digits; the unmasked number remains inside document.data only because UIDAI's signature covers it and stripping it would make the document unverifiable. details is null when the account has no eAadhaar on file — the identity is still verified.

verifiedId is a stable identifier for the verified individual — use it as your identity key. Never persist the Aadhaar number or photo you submitted; neither is echoed back and neither should be stored on your side. fieldsFilledFromImage lists which fields, if any, came from OCR rather than typing — empty when no photo was attached or nothing needed filling.

The document field is present only when a signed identity document is available for that account.


Error Codes

  • 400 — Missing/invalid input, OCR couldn't read a required field from the photo (retype it and resubmit), or the verification session expired (restart from /init)
  • 401 — Unauthorized: check X-API-KEY and X-API-APP-ID headers
  • 404 — Signed identity document not available for this account (identity is still verified)
  • 422 — Submitted (typed or OCR-filled) name/DOB/gender did not match Aadhaar records
  • 429 — Too many requests

Benefits

  • No redirect — your users stay on your screen throughout
  • Often zero OTP — accounts with an existing link verify in a single call
  • One consolidated result — identity and signed document in one response
  • Type it or photograph it — one endpoint, either input, no separate OCR round trip
  • Full UX ownership — your branding, your copy, your error handling
POST

/api/v1/aadhaar/offline-kyc/init

#

Start Aadhaar verification — from typed fields, a photo of the Aadhaar card front, or both.

docs.signcare.io/core/api/aadhaar/offline-aadhaar-kyc/#post-api-v1-aadhaar-offline-kyc-init

Runs the Aadhaar demographic-authentication step against the user's linked identity account. For most users — anyone with an existing linked account — the verified identity comes back immediately in this same call, with otpRequired: false and result populated.

A smaller share of users (typically first-time/new accounts) get otpRequired: true instead, with no result yet. In that case, collect a one-time password from the user (delivered to their registered mobile) and call verify-otp with the transactionId returned here — do not call init again for the same attempt. The response's message differs by branch so you don't have to infer it from otpRequired alone: on the OTP branch it names the mobile the OTP was sent to (e.g. "...OTP has been sent to XXXXXX1234..."); on the immediate branch it confirms verification directly.

Two ways to supply aadhaarNumber/name/dob/gender:Type them directly, as before.Attach aadhaarImage (base64 photo/scan of the Aadhaar card front) and leave some or all of those fields blank — the server runs OCR on the image and fills in whatever wasn't typed. A field you did type always takes priority over what OCR reads for that same field. JPEG or PNG only — a PDF (such as a downloaded eAadhaar) or a HEIC photo straight from an iPhone is rejected with a 400 naming the format, because the OCR engine's synchronous API reads only those two.mobile is always required and always typed — it isn't printed on the card, so OCR can never supply it.

Field formats:dob is DDMMYYYY, day first — 03071990 is 3 July 1990, not 7 March. gender is a single character, M or F; no other value is accepted, so a card reading TRANSGENDER cannot be verified through this API. Both are validated before the verification call, so a wrong format comes back as a 400 naming the problem rather than an ambiguous "details did not match Aadhaar records".

OCR is never itself the verification. Whatever values end up populated — typed or OCR-filled — are exactly what get checked against Aadhaar by this same call. A bad OCR read therefore surfaces as a 422 ("did not match Aadhaar records"), never as a false positive, and costs you nothing more than the retry. If OCR genuinely can't locate a field on the image (a blurry photo, bad crop, wrong side of the card), you get a 400 asking you to supply that field manually and resubmit — no partial/guessed values are ever sent to verification.

The response's fieldsFilledFromImage (inside result) lists which fields, if any, came from OCR rather than typing — useful if you want to show the user what was auto-filled.

The submitted Aadhaar number/image are used only for this call and are never persisted — store the response's result.verifiedId as your identity key instead.

Where the Aadhaar details come from. The demographic profile returned by the authentication step carries only name, DOB, gender and mobile. Full address, photo and the KYC timestamp exist solely inside the signed identity document, so when that document is available it is parsed into result.details. result.document.data holds the signed XML verbatim for callers who want to verify the issuer signature themselves. details.aadhaarNumber is masked to the last four digits; the unmasked number stays only inside the signed document, because the signature covers it and altering it would make the document unverifiable.

Headers

  • X-API-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • aadhaarNumber
    stringconditional
    Conditional: required unless API.Models.SignCare.ULIP.OfflineAadhaarKycInitRequest.AadhaarImage is supplied. 12-digit Aadhaar number, digits only. Held in memory only for this call — never persisted or logged.
    nullable
  • name
    stringconditional
    Conditional: required unless API.Models.SignCare.ULIP.OfflineAadhaarKycInitRequest.AadhaarImage is supplied. Name exactly as on the Aadhaar record. Letters and spaces only — normalise dots/hyphens/apostrophes before sending.
    nullable
  • dob
    stringconditional
    Conditional: required unless API.Models.SignCare.ULIP.OfflineAadhaarKycInitRequest.AadhaarImage is supplied. Date of birth in <b>DDMMYYYY</b> format, day first (e.g. 03071990 for 3 July 1990). No separators.
    nullable
  • gender
    stringconditional
    Conditional: required unless API.Models.SignCare.ULIP.OfflineAadhaarKycInitRequest.AadhaarImage is supplied. Gender as a single character, M or F (either case). No other value is accepted.
    nullable
  • mobile
    stringrequired
    Required. 10-digit mobile number registered against the Aadhaar. Always required — a mobile number is not printed on the card, so OCR can never supply this field.
    nullable
  • aadhaarImage
    stringoptional
    Optional. Base64-encoded photo/scan of the Aadhaar card front. When supplied, any of aadhaarNumber/name/dob/gender left blank are filled in via OCR before verification runs. Held in memory only for this call — never persisted or logged. <b>JPEG or PNG only.</b> PDF, HEIC (the iPhone camera default), TIFF, GIF and WebP are rejected with a 400 naming the format — the OCR engine's synchronous API reads JPEG and PNG bytes only. Convert first, or type the details instead.
    nullable
  • request_id
    stringoptional
    Optional. Accepted but ignored: the server generates its own request id for every call.
    nullable
Sample request body
{ "aadhaarNumber": "string", "name": "string", "dob": "string", "gender": "string", "mobile": "string", "aadhaarImage": "string", "request_id": "string" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    When OtpRequired is false, Result is populated immediately — the identity was verified without an OTP, which is the common case. When OtpRequired is true, the caller must collect the OTP from the user and call the verify-otp endpoint with the same TransactionId; Result is null until then.
    3 fields
    • transactionId
      stringoptional
      No description.
      nullable
    • otpRequired
      booleanoptional
      No description.
    • result
      objectoptional
      No description.
      9 fields
      • verifiedId
        stringoptional
        Stable pseudonymous identifier — use this as the identity key, never the Aadhaar number.
        nullable
      • name
        stringoptional
        No description.
        nullable
      • dob
        stringoptional
        No description.
        nullable
      • gender
        stringoptional
        No description.
        nullable
      • mobile
        stringoptional
        No description.
        nullable
      • verifiedAtUtc
        string (date-time)optional
        No description.
      • document
        objectoptional
        No description.
        2 fields
        • data
          stringoptional
          The UIDAI-signed eAadhaar KYC XML, verbatim as the issuer signed it. Returned unaltered on purpose: it is the tamper-evident artefact, and editing anything inside it (including the Aadhaar number) would break signature verification.
          nullable
        • mime
          stringoptional
          Media type of API.Models.SignCare.ULIP.OfflineAadhaarKycDocument.Data — `application/xml` for the signed eAadhaar.
          nullable
      • details
        objectoptional
        Demographic details read out of the signed identity document. Field-for-field identical on the wire to the details object the DigiLocker API returns, so a consumer integrating both maps one structure. It is a separate type purely so the published schema name stays provider-neutral — the OpenAPI document is customer-facing, and a schema called after a specific provider would name our upstream in the public contract.
        15 fields
        • name
          stringoptional
          No description.
          nullable
        • dob
          stringoptional
          Date of birth as printed on the document (DD-MM-YYYY).
          nullable
        • gender
          stringoptional
          No description.
          nullable
        • co
          stringoptional
          Care-of line, when the document carries one.
          nullable
        • address
          stringoptional
          No description.
          nullable
        • village
          stringoptional
          No description.
          nullable
        • subDistrict
          stringoptional
          No description.
          nullable
        • district
          stringoptional
          No description.
          nullable
        • state
          stringoptional
          No description.
          nullable
        • country
          stringoptional
          No description.
          nullable
        • pinCode
          stringoptional
          No description.
          nullable
        • aadhaarNumber
          stringoptional
          Masked to the last four digits — the full number is never surfaced in a parsed field.
          nullable
        • kycDateTime
          stringoptional
          When the issuing authority performed the KYC that produced this document.
          nullable
        • photo
          stringoptional
          Base64 JPEG portrait from the document, when present.
          nullable
        • addressDetails
          objectoptional
          The address split into printable lines, as the issuer formats it.
          3 fields
          • addressLine1
            stringoptional
            No description.
            nullable
          • addressLine2
            stringoptional
            No description.
            nullable
          • addressLine3
            stringoptional
            No description.
            nullable
      • fieldsFilledFromImage
        array<string>optional
        Which fields, if any, were filled in from a submitted Aadhaar card-front photo rather than typed — e.g. ["name","dob"]. Empty when no image was supplied or nothing needed filling.
        nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "transactionId": "string", "otpRequired": false, "result": { "verifiedId": "string", "name": "string", "dob": "string", "gender": "string", "mobile": "string", "verifiedAtUtc": "2026-01-01T00:00:00Z", "document": { "data": "string", "mime": "string" }, "details": { "name": "string", "dob": "string", "gender": "string", "co": "string", "address": "string", "village": "string", "subDistrict": "string", "district": "string", "state": "string", "country": "string", "pinCode": "string", "aadhaarNumber": "string", "kycDateTime": "string", "photo": "string", "addressDetails": { "addressLine1": "string", "addressLine2": "string", "addressLine3": "string" } }, "fieldsFilledFromImage": [ "string" ] } }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
202Acceptedsame body as 200
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
422Unprocessable Content
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/aadhaar/offline-kyc/init' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "aadhaarNumber": "string", "name": "string", "dob": "string", "gender": "string", "mobile": "string", "aadhaarImage": "string", "request_id": "string" }'
POST

/api/v1/aadhaar/offline-kyc/verify-otp

#

Complete verification for a new account by submitting the OTP sent to the user's registered mobile.

docs.signcare.io/core/api/aadhaar/offline-aadhaar-kyc/#post-api-v1-aadhaar-offline-kyc-verify-otp

Only call this when a prior init call returned otpRequired: true. Use the same transactionId from that response together with the OTP the user received.

The transaction has a short validity window — if it has lapsed, this returns 400 with a message to restart from init; there is no separate resend endpoint, restarting from init triggers a fresh OTP.

Headers

  • X-API-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • transactionId
    stringrequired
    Required. The `transactionId` returned by the init call (only when it returned `otpRequired: true`). Valid for a short time only; after that, restart from init.
    nullable
  • otp
    stringrequired
    Required. OTP delivered to the user's registered mobile (6 digits).
    nullable
Sample request body
{ "transactionId": "string", "otp": "string" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    No description.
    9 fields
    • verifiedId
      stringoptional
      Stable pseudonymous identifier — use this as the identity key, never the Aadhaar number.
      nullable
    • name
      stringoptional
      No description.
      nullable
    • dob
      stringoptional
      No description.
      nullable
    • gender
      stringoptional
      No description.
      nullable
    • mobile
      stringoptional
      No description.
      nullable
    • verifiedAtUtc
      string (date-time)optional
      No description.
    • document
      objectoptional
      No description.
      2 fields
      • data
        stringoptional
        The UIDAI-signed eAadhaar KYC XML, verbatim as the issuer signed it. Returned unaltered on purpose: it is the tamper-evident artefact, and editing anything inside it (including the Aadhaar number) would break signature verification.
        nullable
      • mime
        stringoptional
        Media type of API.Models.SignCare.ULIP.OfflineAadhaarKycDocument.Data — `application/xml` for the signed eAadhaar.
        nullable
    • details
      objectoptional
      Demographic details read out of the signed identity document. Field-for-field identical on the wire to the details object the DigiLocker API returns, so a consumer integrating both maps one structure. It is a separate type purely so the published schema name stays provider-neutral — the OpenAPI document is customer-facing, and a schema called after a specific provider would name our upstream in the public contract.
      15 fields
      • name
        stringoptional
        No description.
        nullable
      • dob
        stringoptional
        Date of birth as printed on the document (DD-MM-YYYY).
        nullable
      • gender
        stringoptional
        No description.
        nullable
      • co
        stringoptional
        Care-of line, when the document carries one.
        nullable
      • address
        stringoptional
        No description.
        nullable
      • village
        stringoptional
        No description.
        nullable
      • subDistrict
        stringoptional
        No description.
        nullable
      • district
        stringoptional
        No description.
        nullable
      • state
        stringoptional
        No description.
        nullable
      • country
        stringoptional
        No description.
        nullable
      • pinCode
        stringoptional
        No description.
        nullable
      • aadhaarNumber
        stringoptional
        Masked to the last four digits — the full number is never surfaced in a parsed field.
        nullable
      • kycDateTime
        stringoptional
        When the issuing authority performed the KYC that produced this document.
        nullable
      • photo
        stringoptional
        Base64 JPEG portrait from the document, when present.
        nullable
      • addressDetails
        objectoptional
        The address split into printable lines, as the issuer formats it.
        3 fields
        • addressLine1
          stringoptional
          No description.
          nullable
        • addressLine2
          stringoptional
          No description.
          nullable
        • addressLine3
          stringoptional
          No description.
          nullable
    • fieldsFilledFromImage
      array<string>optional
      Which fields, if any, were filled in from a submitted Aadhaar card-front photo rather than typed — e.g. ["name","dob"]. Empty when no image was supplied or nothing needed filling.
      nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "verifiedId": "string", "name": "string", "dob": "string", "gender": "string", "mobile": "string", "verifiedAtUtc": "2026-01-01T00:00:00Z", "document": { "data": "string", "mime": "string" }, "details": { "name": "string", "dob": "string", "gender": "string", "co": "string", "address": "string", "village": "string", "subDistrict": "string", "district": "string", "state": "string", "country": "string", "pinCode": "string", "aadhaarNumber": "string", "kycDateTime": "string", "photo": "string", "addressDetails": { "addressLine1": "string", "addressLine2": "string", "addressLine3": "string" } }, "fieldsFilledFromImage": [ "string" ] }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/aadhaar/offline-kyc/verify-otp' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "transactionId": "string", "otp": "string" }'