Framework-agnostic PHP library for the YNAB API.
Repository: jpry/ynab-sdk-php
- Namespace root:
JPry\\YNAB\\ - Typed resources for users, plans, plan settings, accounts, categories, category groups, payees, payee locations, months, money movements, scheduled transactions, and transactions
- Endpoint-by-name client methods (
user(),plans(),planSettings(),category(),monthCategory(),months(),moneyMovements(),scheduledTransactions(), etc.) - Structured YNAB errors (
error.id,error.name,error.detail) - Supports API key auth and OAuth token auth
- Uses PSR-7/PSR-18 HTTP contracts; any codebase can provide its own sender by implementing
JPry\\YNAB\\Http\\RequestSender
composer require jpry/ynab-sdk-phpIf you want the built-in default sender:
composer require guzzlehttp/guzzleuse JPry\YNAB\Client\YnabClient;
use JPry\YNAB\Config\ClientConfig;
$config = new ClientConfig(
baseUrl: 'https://api.ynab.com/v1',
timeoutSeconds: 30,
maxRetries: 2,
);
$client = YnabClient::withApiKey('your-api-key', config: $config);
$user = $client->user();
$plans = $client->plans();
$settings = $client->planSettings('plan-id');
$months = $client->months('plan-id');
$moneyMovements = $client->moneyMovements('plan-id');
$scheduled = $client->scheduledTransactions('plan-id');
$payeeLocations = $client->payeeLocations('plan-id');
$category = $client->category('plan-id', 'category-id');
$monthCategory = $client->monthCategory('plan-id', '2026-03-01', 'category-id');
$transactions = $client->transactions('plan-id', ['since_date' => '2026-01-01']);Legacy budget-named APIs (budgets(), defaultBudget(), and JPry\\YNAB\\Model\\Budget) remain available for backward compatibility but are deprecated and emit E_USER_DEPRECATED warnings.
Most mutating methods (create/update/delete) return the raw data payload from the API response as array<string,mixed>.
Mutating methods also accept typed request models in JPry\\YNAB\\Model\\Mutation\\* (legacy array and string-id signatures remain supported).
use JPry\YNAB\Model\Mutation\CreateTransactionsRequest;
use JPry\YNAB\Model\Mutation\TransactionPayload;
use JPry\YNAB\Model\Mutation\UpdateTransactionRequest;
$client->createTransactions(
'plan-id',
CreateTransactionsRequest::single(
new TransactionPayload(accountId: 'account-id', amount: -1000)
),
);
$client->updateTransaction(
'plan-id',
new UpdateTransactionRequest('transaction-id', new TransactionPayload(memo: 'Updated memo')),
);- Documentation Index
- Getting Started
- API Usage
- Mutations and Request Models
- OAuth Flow
- Migration Notes: Budgets to Plans
- OpenAPI Spec Snapshot
use JPry\YNAB\Client\YnabClient;
use JPry\YNAB\Config\ClientConfig;
$config = new ClientConfig();
$client = YnabClient::withOAuthToken(
accessToken: 'access-token',
refreshAccessToken: fn (): string => 'new-access-token',
config: $config,
);use JPry\YNAB\Http\GuzzleRequestSender;
use JPry\YNAB\OAuth\OAuthClient;
use JPry\YNAB\OAuth\OAuthConfig;
use JPry\YNAB\Config\ClientConfig;
$oauth = new OAuthClient(
new OAuthConfig(
clientId: 'client-id',
clientSecret: 'client-secret',
redirectUri: 'https://example.com/oauth/callback',
),
new GuzzleRequestSender(new ClientConfig()),
);
$authUrl = $oauth->authorizationUrl('state-value');
$tokens = $oauth->exchangeCodeForTokens('code-from-callback');use JPry\YNAB\Http\RequestSender;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Response;
final class MySender implements RequestSender
{
public function sendRequest(RequestInterface $request): ResponseInterface
{
// Bridge to your own HTTP library
return new Response(200, [], '{"data":{}}');
}
}Then pass it into factory methods:
$client = YnabClient::withApiKey('api-key', requestSender: new MySender());Collection methods automatically follow pagination metadata when present (next_page, next_page_url, or links.next).
The repository includes a local snapshot of the YNAB OpenAPI specification at openapi/ynab-v1.openapi.yaml.
- Sync manually:
./scripts/sync-openapi-spec.sh - Automated sync:
.github/workflows/openapi-spec-sync.yml(weekly + manual dispatch)
composer test