Package: vapi/vapi 2.0.0
Environment: PHP 8.5.5
Summary
The SDK declares credential fields as non-nullable string. The Vapi API redacts
secrets on read and returns null for them. Deserializing any response that
contains one throws a TypeError.
Because list endpoints decode the entire array in one pass, a single affected
record makes the whole call fail. There is no partial result — the caller gets
an exception instead of a list.
Reproduction
Self-contained. No API key and no account needed:
require 'vendor/autoload.php';
Vapi\Types\SipAuthentication::jsonDeserialize([
'realm' => 'sip.vapi.ai',
'username' => 'user',
'password' => null, // what the API returns: secrets are redacted on read
]);
Actual:
TypeError: Cannot assign null to property Vapi\Types\SipAuthentication::$password
of type string
in vendor/vapi/vapi/src/Types/SipAuthentication.php:40
Expected: the object deserializes with password as null.
How it shows up in practice
An account containing one BYO SIP number is enough to break the entire
phone-number listing:
$client->phoneNumbers->list(new ListPhoneNumbersRequest);
// TypeError — no numbers returned at all, not a partial list
SipAuthentication is reached through SipTrunkGateway → BYO phone number, so
the failure surfaces on an endpoint that has nothing obviously to do with SIP
credentials. That makes it hard to attribute: the caller asked for phone
numbers and got a type error about a password.
Cause
src/Types/SipAuthentication.php:
public string $password; // line 26
public function __construct(array $values) {
$this->realm = $values['realm'] ?? null;
$this->username = $values['username'];
$this->password = $values['password']; // line 40 — no null tolerance
}
realm is correctly ?string. password is not, but it is the field the API is
guaranteed not to return, being a secret.
Scope
This is not specific to SipAuthentication. 93 of 1784 type files declare a
non-nullable credential field:
| Field |
Type files |
public string $apiKey |
75 |
public string $credentialId |
7 |
public string $awsSecretAccessKey |
3 |
public string $apiSecret |
2 |
$password, $token, $accessToken, $sessionToken, $secretKey, $secretAccessKey, $clientSecret, $tokenUri |
1 each |
(Some files carry two of these fields, e.g. VonageCredential has both apiKey
and apiSecret.)
Examples carrying $apiKey: HumeCredential, SmallestAiCredential,
TogetherAiCredential, CreateHumeCredentialDto, CreateXAiCredentialDto.
Any endpoint returning an object with a redacted credential fails the same way,
so this is likely to affect credential listing and assistant/call payloads that
embed provider credentials — not just phone numbers.
Suggested fix
Type read-side credential fields as nullable:
public ?string $password;
// ...
$this->password = $values['password'] ?? null;
A field the API never returns on read cannot correctly be non-nullable on a
response type. If the same class is used for both requests and responses, the
write path can keep requiring the value at the call site while the property
itself tolerates null when decoding.
A narrower alternative is to make the decoder tolerant of missing/redacted
credential fields, but the type declarations would still be describing something
the API does not do.
Workaround for anyone hitting this
Bypass the typed layer for the affected endpoint and fetch the plain JSON with a
direct HTTP call (e.g. Guzzle against GET /phone-number with the bearer token).
Fine when you only need identifiers, but it gives up the type safety the SDK
exists to provide.
Package:
vapi/vapi2.0.0Environment: PHP 8.5.5
Summary
The SDK declares credential fields as non-nullable
string. The Vapi API redactssecrets on read and returns
nullfor them. Deserializing any response thatcontains one throws a
TypeError.Because list endpoints decode the entire array in one pass, a single affected
record makes the whole call fail. There is no partial result — the caller gets
an exception instead of a list.
Reproduction
Self-contained. No API key and no account needed:
Actual:
Expected: the object deserializes with
passwordasnull.How it shows up in practice
An account containing one BYO SIP number is enough to break the entire
phone-number listing:
SipAuthenticationis reached throughSipTrunkGateway→ BYO phone number, sothe failure surfaces on an endpoint that has nothing obviously to do with SIP
credentials. That makes it hard to attribute: the caller asked for phone
numbers and got a type error about a password.
Cause
src/Types/SipAuthentication.php:realmis correctly?string.passwordis not, but it is the field the API isguaranteed not to return, being a secret.
Scope
This is not specific to
SipAuthentication. 93 of 1784 type files declare anon-nullable credential field:
public string $apiKeypublic string $credentialIdpublic string $awsSecretAccessKeypublic string $apiSecret$password,$token,$accessToken,$sessionToken,$secretKey,$secretAccessKey,$clientSecret,$tokenUri(Some files carry two of these fields, e.g.
VonageCredentialhas bothapiKeyand
apiSecret.)Examples carrying
$apiKey:HumeCredential,SmallestAiCredential,TogetherAiCredential,CreateHumeCredentialDto,CreateXAiCredentialDto.Any endpoint returning an object with a redacted credential fails the same way,
so this is likely to affect credential listing and assistant/call payloads that
embed provider credentials — not just phone numbers.
Suggested fix
Type read-side credential fields as nullable:
A field the API never returns on read cannot correctly be non-nullable on a
response type. If the same class is used for both requests and responses, the
write path can keep requiring the value at the call site while the property
itself tolerates
nullwhen decoding.A narrower alternative is to make the decoder tolerant of missing/redacted
credential fields, but the type declarations would still be describing something
the API does not do.
Workaround for anyone hitting this
Bypass the typed layer for the affected endpoint and fetch the plain JSON with a
direct HTTP call (e.g. Guzzle against
GET /phone-numberwith the bearer token).Fine when you only need identifiers, but it gives up the type safety the SDK
exists to provide.