Search by

codwelt / sdk-envato

codwelt

SDK PHP de Codwelt para la API oficial de Envato Market.

v1.0.0 2026-10-18 03:34 UTC

This package is not auto-updated.

Last update: 2026-10-19 02:08:47 UTC


README

Codwelt

SDK Envato para PHP

SDK de Codwelt para la API oficial de Envato: catálogo, usuarios, ventas, compras, descargas, verificación de códigos de compra y OAuth.
Parte de Toolsbox, herramientas digitales gratuitas y de código abierto.

Composer PHP 8.1+ Laravel opcional Guzzle 7 32 endpoints Tests MIT

Conoce Toolsbox Usar Toolsbox Documentación de Envato

Toolsbox por Codwelt: herramientas digitales gratuitas

📘 ¿Qué es este SDK?

SDK Envato (codwelt/sdk-envato) es una librería PHP para consumir la API oficial de Envato, documentada en build.envato.com/api. En vez de escribir peticiones HTTP a mano contra https://api.envato.com, instalas el paquete con Composer y llamas a métodos PHP con nombre claro.

La API de Envato ofrece… …y con el SDK lo haces así
Consultar el catálogo de Envato Market: artículos, búsquedas, categorías, populares y precios $envato->catalog()->searchItems([...])
Verificar un código de compra de tu tema o plugin $envato->verifyPurchaseCode($code)
Ver tus ventas como autor y tus extractos $envato->author()->sales() · $envato->account()->statement()
Listar y descargar compras como comprador $envato->buyer()->listPurchases() · $envato->buyer()->download([...])
Consultar perfiles, colecciones y badges de usuarios $envato->user()->details('usuario')
Conectar cuentas de terceros mediante OAuth $envato->oauth()->getAuthorizationUrl()

Para quién es: autores de ThemeForest o CodeCanyon que validan licencias y soporte, plataformas que muestran o sincronizan artículos de Envato, y aplicaciones que permiten a sus usuarios conectar su cuenta de Envato.

Qué necesitas: un token personal creado en build.envato.com/create-token o una app OAuth registrada en build.envato.com/my-apps. El SDK no sustituye esa documentación: la sigue endpoint por endpoint, y cada método de la referencia de recursos indica la ruta de la API que llama.

Note

La API de build.envato.com da acceso a Envato Market (ThemeForest, CodeCanyon, VideoHive, AudioJungle, GraphicRiver, PhotoDune y 3DOcean). Envato Elements no tiene API pública, así que este SDK no incluye endpoints de Elements.

Contenido

📘 ¿Qué es? 🚀 Instalación 🔑 Token personal
⚡ Inicio rápido 🧭 Arquitectura ⚙️ Configuración
🅻 Laravel 🔐 OAuth ✅ Verificar códigos de compra
📚 Referencia de recursos 🚦 Errores y rate limits 🧪 Ejemplos y pruebas
📦 Publicación 🧰 Toolsbox por Codwelt 🤝 Licencia y soporte

✨ Qué incluye

📦 32 endpoints

Catálogo, búsqueda, usuarios, ventas del autor, compras, descargas, cuenta y estadísticas, verificados contra la documentación oficial.

🔐 OAuth completo

URL de autorización con state, intercambio del código, renovación y control de expiración con AccessToken.

✅ Códigos de compra

verifyPurchaseCode() valida el formato, consulta la venta y devuelve null si no existe.

🚦 Errores tipados

Una excepción por cada caso: 400, 401, 403, 404, 429 y 5xx, con status, cuerpo y Retry-After.

🔁 Reintentos seguros

Backoff exponencial con jitter en 429 y 5xx, solo en GET. Respeta Retry-After.

🅻 Laravel opcional

Autodiscovery, config publicable, singletons y Facade Envato. Fuera de Laravel solo necesita Guzzle.

🚀 Instalación

Requiere PHP ^8.1 y Composer. Usa Guzzle ^7.8; Laravel es opcional y no se instala como dependencia del SDK.

composer require codwelt/sdk-envato

Tip

El comando funcionará cuando el paquete esté publicado en Packagist. Mientras tanto, en desarrollo local, declara este repositorio como repositorio Composer de tipo path en tu aplicación.

🔑 Obtener un token personal

flowchart LR
    A["Inicia sesión en<br/>build.envato.com"] --> B["Abre<br/>Create a token"]
    B --> C["Elige los permisos<br/>que necesitas"]
    C --> D["Crea el token"]
    D --> E["Guárdalo en<br/>ENVATO_TOKEN"]
Loading
  1. Inicia sesión en Envato y abre Create a token.
  2. Nombra el token y concede los permisos necesarios para tu integración.
  3. Acepta los términos, crea el token y guárdalo en ENVATO_TOKEN en tu entorno de ejecución.

Los permisos dependen del recurso: consulta de items y búsquedas, colecciones privadas, verificación de ventas del autor, historial de ventas, compras y descargas del comprador, cuenta, usuario, correo y extractos. Revisa el permiso de cada operación en la documentación oficial.

  • Un código de compra solo se puede verificar como venta de un artículo del autor autenticado.
  • Los endpoints de comprador requieren el token del comprador.
  • Un token válido sin el permiso requerido produce un 403.

Warning

No incluyas tokens ni secretos en el código fuente. El SDK no carga archivos .env: lee los valores que le pases. En Laravel se usa su sistema de configuración habitual.

⚡ Inicio rápido

require 'vendor/autoload.php';

use Codwelt\Envato\EnvatoClient;

$token = getenv('ENVATO_TOKEN') ?: throw new RuntimeException('Falta ENVATO_TOKEN');
$envato = EnvatoClient::make($token);

$item = $envato->catalog()->item(123);
$results = $envato->catalog()->searchItems([
    'term' => 'portfolio', 'site' => 'themeforest', 'page' => 1,
]);
$sale = $envato->verifyPurchaseCode('01234567-89ab-cdef-0123-456789abcdef');
// array de venta, o null si la API responde 404.

Todas las respuestas son arrays con la estructura original de Envato. No hay paginación automática: solicita cada página explícitamente. Los recursos se crean bajo demanda y cada acceso devuelve la misma instancia.

🧭 Arquitectura

flowchart LR
    App["Tu aplicación<br/>(PHP o Laravel)"] --> Client["EnvatoClient"]
    Client --> Catalog["catalog()"]
    Client --> User["user()"]
    Client --> Author["author()"]
    Client --> Buyer["buyer()"]
    Client --> Account["account()"]
    Client --> Stats["stats()"]
    Client --> OAuth["oauth()"]
    Catalog & User & Author & Buyer & Account & Stats & OAuth --> Http["HttpClient<br/>Bearer · reintentos · errores tipados"]
    Http --> Guzzle["Guzzle 7"]
    Guzzle --> API[("api.envato.com")]
Loading
Pieza Responsabilidad
EnvatoClient Punto de entrada. Expone los recursos, verifyPurchaseCode() y withToken().
EnvatoConfig Token, credenciales OAuth, URL base, User-Agent, timeout y reintentos.
HttpClient Añade el Bearer y el User-Agent, decodifica JSON, reintenta y convierte errores en excepciones.
Api\* Un recurso por grupo de la API, con validación de argumentos antes de llamar.
Auth\OAuth y Auth\AccessToken Flujo OAuth y token con expiración absoluta.
Laravel\* ServiceProvider, configuración publicable y Facade.

⚙️ Configuración e inyección de Guzzle

use Codwelt\Envato\Config\EnvatoConfig;
use Codwelt\Envato\EnvatoClient;
use GuzzleHttp\Client;

$envato = new EnvatoClient(
    new EnvatoConfig(
        token: $token,
        baseUri: 'https://api.envato.com',
        userAgent: 'mi-aplicacion/1.0',
        timeout: 30,
        maxRetries: 3,
    ),
    new Client(), // ClientInterface: admite tu handler o configuración de proxy.
    static function (float $seconds): void {
        usleep((int) round($seconds * 1_000_000));
    },
);
$anotherUser = $envato->withToken($anotherAccessToken);
Opción Valor por defecto
baseUri https://api.envato.com
timeout 30 segundos
maxRetries 3
userAgent codwelt-sdk-envato/1.0 (+https://codwelt.com)
  • withToken() devuelve un clon con recursos nuevos. Conserva el transporte, las opciones y el sleeper; el cliente original mantiene su token.
  • El sleeper recibe segundos.
  • make($token, $options) acepta los nombres camelCase del constructor de EnvatoConfig, más client y sleeper.
  • Los encabezados, el timeout, el tratamiento de errores y las redirecciones los controla el SDK. No se siguen redirecciones HTTP.

🅻 Laravel

El paquete declara autodiscovery para el provider y el alias Envato. En una aplicación Laravel con illuminate/support, publica la configuración:

php artisan vendor:publish --tag=envato-config

Configura tu .env:

ENVATO_TOKEN=
ENVATO_CLIENT_ID=
ENVATO_CLIENT_SECRET=
ENVATO_REDIRECT_URI=https://tu-dominio.com/envato/callback
ENVATO_USER_AGENT="mi-aplicacion/1.0"
ENVATO_TIMEOUT=30
ENVATO_MAX_RETRIES=3
use Codwelt\Envato\Laravel\Facades\Envato;
use Codwelt\Envato\EnvatoClient;

$item = Envato::catalog()->item(123);
$client = app(EnvatoClient::class); // también app('envato')
$oauth = app(\Codwelt\Envato\Auth\OAuth::class);

El provider combina config/envato.php, registra EnvatoClient y OAuth como singletons y publica el archivo con el tag envato-config. Si deshabilitas autodiscovery, registra Codwelt\Envato\Laravel\EnvatoServiceProvider manualmente. Para atender a distintos usuarios OAuth, usa withToken() en cada solicitud.

🔐 OAuth

Registra tu aplicación en My Apps con los permisos requeridos. La redirectUri debe usar HTTPS y coincidir exactamente con la Confirmation URL configurada en Envato.

sequenceDiagram
    autonumber
    actor U as Usuario
    participant A as Tu aplicación
    participant E as api.envato.com
    A->>U: Redirige a getAuthorizationUrl()
    U->>E: Inicia sesión y autoriza la app
    E-->>A: Callback con ?code= (un solo uso)
    A->>E: POST /token · exchangeCode($code)
    E-->>A: access_token + refresh_token (1 hora)
    A->>E: GET /v1/... con Bearer (withToken)
    Note over A,E: Cuando caduca
    A->>E: POST /token · refreshToken($refresh)
    E-->>A: Nuevo access_token
Loading
$envato = EnvatoClient::make('', [
    'clientId' => getenv('ENVATO_CLIENT_ID'),
    'clientSecret' => getenv('ENVATO_CLIENT_SECRET'),
    'redirectUri' => getenv('ENVATO_REDIRECT_URI'),
]);
$oauth = $envato->oauth();
$url = $oauth->getAuthorizationUrl();
// Redirige al usuario a $url; Envato devuelve un código de un solo uso al callback.
$token = $oauth->exchangeCode($codeFromCallback);
$client = $envato->withToken($token->accessToken);

if ($token->isExpired()) {
    $token = $oauth->refreshToken($token->refreshToken);
    $client = $envato->withToken($token->accessToken);
}
  • getAuthorizationUrl(?string $state = null) permite añadir un estado que tu callback debe comprobar. El ejemplo oauth_flow.php muestra autorización, callback, sesión y renovación.
  • Los tokens se piden únicamente desde el servidor: POST /token, formulario application/x-www-form-urlencoded, sin Bearer. Estas peticiones no se reintentan, para no reutilizar un código de un solo uso.
  • AccessToken expone las propiedades readonly accessToken, refreshToken (nullable), expiresAt (timestamp Unix) y tokenType.
  • fromArray($data, ?int $now = null) convierte expires_in en una expiración absoluta. isExpired(?int $now = null) considera expirado el instante exacto de vencimiento.
  • Si una renovación no devuelve refresh_token, se conserva el anterior. La renovación no es automática: guarda los tokens de forma segura y persiste el nuevo después de renovar.

✅ Verificar códigos de compra

El caso de uso más habitual: comprobar que un cliente compró tu tema o plugin antes de darle soporte o activar una licencia.

flowchart TD
    In["verifyPurchaseCode($code)"] --> Trim["Recorta espacios"]
    Trim --> Fmt{"¿Formato UUID?"}
    Fmt -- No --> VE["ValidationException<br/>(sin llamada HTTP)"]
    Fmt -- Sí --> Call["GET /v3/market/author/sale?code="]
    Call --> R{"Respuesta"}
    R -- 200 --> Sale["array con la venta"]
    R -- 404 --> Null["null"]
    R -- Otro error --> Ex["Excepción tipada"]
Loading
$sale = $envato->verifyPurchaseCode($request->input('purchase_code'));

if ($sale === null) {
    // El código no corresponde a una venta de tus artículos.
}

$itemName = $sale['item']['name'];
$buyer = $sale['buyer'];
$supportedUntil = $sale['supported_until'];

📚 Referencia de recursos

Todos los métodos realizan GET. Las rutas son relativas a https://api.envato.com; los parámetros {...} son segmentos codificados con rawurlencode y el resto viaja en la query. Los ids y las páginas deben ser positivos y los strings no vacíos; download() exige item_id o purchase_code. Los argumentos inválidos lanzan InvalidArgumentException antes de hacer la petición.

🛍️ catalog(): catálogo de Envato Market
Método Ruta Query
collection(int $id, ?int $page = null) /v3/market/catalog/collection id=id, page=page
item(int $id) /v3/market/catalog/item id=id
itemVersion(int $id) /v3/market/catalog/item-version id=id
searchItems(array $params = []) /v1/discovery/search/search/item array libre de parámetros documentados
searchComments(int $itemId, array $params = []) /v1/discovery/search/search/comment item_id=itemId + params
similarItems(int $itemId, array $params = []) /v1/discovery/search/search/more_like_this item_id=itemId + params
popular(string $site) /v1/market/popular:{site}.json —
categories(string $site) /v1/market/categories:{site}.json —
itemPrices(int $itemId) /v1/market/item-prices:{itemId}.json —
newItems(string $site, string $category) /v1/market/new-files:{site},{category}.json —
featured(string $site) /v1/market/features:{site}.json —
randomNewItems(string $site) /v1/market/random-new-files:{site}.json —
👤 user(): datos de usuarios
Método Ruta Query
collections() /v3/market/user/collections —
privateCollection(int $id) /v3/market/user/collection id=id
details(string $username) /v1/market/user:{username}.json —
badges(string $username) /v1/market/user-badges:{username}.json —
itemsBySite(string $username) /v1/market/user-items-by-site:{username}.json —
newItems(string $username, string $site) /v1/market/new-files-from-user:{username},{site}.json —
💰 author(): ventas del autor
Método Ruta Query
sales(?int $page = null) /v3/market/author/sales page=page
saleByCode(string $code) /v3/market/author/sale code=code
🛒 buyer(): compras y descargas
Método Ruta Query
listPurchases(array $params = []) /v3/market/buyer/list-purchases array libre de parámetros documentados
purchases(?int $page = null) /v3/market/buyer/purchases page=page
purchaseByCode(string $code) /v3/market/buyer/purchase code=code
download(array $params) /v3/market/buyer/download array libre de parámetros documentados
🪪 account(): cuenta privada
Método Ruta Query
details() /v1/market/private/user/account.json —
username() /v1/market/private/user/username.json —
email() /v1/market/private/user/email.json —
salesByMonth() /v1/market/private/user/earnings-and-sales-by-month.json —
statement(array $params = []) /v3/market/user/statement array libre de parámetros documentados
📊 stats(): estadísticas de Envato Market
Método Ruta Query
totalUsers() /v1/market/total-users.json —
totalItems() /v1/market/total-items.json —
filesInCategory(string $site) /v1/market/number-of-files:{site}.json —

Los arrays $params se transmiten sin lista restrictiva de claves, para admitir los filtros oficiales:

Método Parámetros habituales
searchItems() term, site, category, price_min, price_max, rating_min, sales, tags, username, date, page, page_size, sort_by, sort_direction y otros de la documentación
searchComments() term, page, page_size, sort_by. El itemId explícito prevalece sobre params['item_id']
similarItems() page, page_size
listPurchases() filter_by (wordpress-themes o wordpress-plugins), page, include_all_item_details
download() item_id o purchase_code, opcionalmente shorten_url
statement() page, from_date, to_date, type, site

Verificación de la documentación

El 17 de octubre de 2026 se cotejaron las 32 rutas con el API Method Listing y la especificación oficial que usa esa página. Todas las rutas y sus verbos GET quedaron confirmados, igual que OAuth (/authorization, /token) y Retry-After en segundos.

El parámetro state de OAuth es una extensión del SDK: la documentación pública no confirma que Envato lo devuelva en el callback. Compruébalo con tu aplicación antes de desplegar el ejemplo de sesión. Durante la verificación no se hicieron llamadas autenticadas. Detalles en el informe de endpoints.

🚦 Errores y límites de solicitudes

use Codwelt\Envato\Exceptions\RateLimitException;
use Codwelt\Envato\Exceptions\EnvatoException;

try {
    $items = $envato->catalog()->searchItems(['term' => 'shop']);
} catch (RateLimitException $exception) {
    $waitSeconds = $exception->getRetryAfter(); // int, o null si falta o no es válido
} catch (EnvatoException $exception) {
    $status = $exception->getStatusCode();
    $body = $exception->getBody();
    $message = $exception->getMessage();
}
HTTP Excepción
🟠 400 / 422 ValidationException
🔴 401 AuthenticationException
🔴 403 ForbiddenException
⚪ 404 NotFoundException
🟡 429 RateLimitException
🟣 5xx ServerException
⚫ Otro error HTTP, JSON inválido o fallo de transporte ApiException

Todas heredan de EnvatoException y exponen statusCode, body, getStatusCode() y getBody(). El mensaje usa la primera clave escalar disponible entre error_description, error, message y reason; si no hay ninguna, incluye el estado HTTP. Un error local de formato de código de compra tiene status 0, igual que un fallo de transporte (con la causa en getPrevious()).

flowchart LR
    Req["Petición GET"] --> Resp{"Respuesta"}
    Resp -- 2xx --> OK["Array JSON"]
    Resp -- "429 / 5xx" --> Left{"¿Quedan<br/>reintentos?"}
    Left -- Sí --> Wait["Espera max(Retry-After,<br/>2^intento + jitter)"]
    Wait --> Req
    Left -- No --> Err["Excepción tipada"]
    Resp -- "4xx" --> Err
Loading

Envato impone límites dinámicos, sin una cuota fija. El SDK reintenta solo los 429 y 5xx de peticiones GET, hasta maxRetries veces además del primer intento. La espera es exponencial (2^intento segundos, con el exponente limitado a 20) más un jitter de 0 a 1 segundo, y nunca menor que el Retry-After recibido. El sleeper por defecto espera de forma síncrona; puedes inyectar otro. Los errores de transporte no se reintentan.

🧪 Ejemplos y pruebas

Ejemplo Qué muestra
verify_purchase_code.php Verificar un código de compra desde la línea de comandos
search_items.php Buscar artículos en el catálogo
author_sales.php Listar las ventas del autor por página
oauth_flow.php Flujo OAuth completo: autorización, callback y renovación
composer install
composer validate --strict
composer test
php examples/search_items.php portfolio
php examples/verify_purchase_code.php 01234567-89ab-cdef-0123-456789abcdef
php examples/author_sales.php 1

Los ejemplos de línea de comandos leen ENVATO_TOKEN del entorno. El ejemplo OAuth se sirve como página HTTPS con ENVATO_CLIENT_ID, ENVATO_CLIENT_SECRET y ENVATO_REDIRECT_URI.

Las pruebas usan PHPUnit ^10.5 con MockHandler y Middleware::history: rutas y query de todos los métodos, Bearer, mapeo de errores, reintentos con sleeper falso, OAuth, clonación y códigos de compra. La suite de integración se salta si no hay ENVATO_TOKEN; si lo defines, SmokeTest consulta stats()->totalItems() contra la API real. Para ejecutar solo las pruebas sin red:

vendor/bin/phpunit --testsuite Unit

📦 Publicación en Packagist

  1. Comprueba composer validate --strict, composer install, composer test y el lint de PHP.
  2. Sube el repositorio a GitHub.
  3. Envía su URL a Packagist y activa el webhook de actualización.
  4. Crea y publica un tag semántico (v1.0.0) para que Composer lo resuelva como versión estable.

composer.lock y vendor/ se ignoran por ser una librería. tests/, examples/ y docs/ se excluyen del paquete instalado mediante export-ignore.

🧰 Toolsbox por Codwelt

Toolsbox con tu marca

Herramientas digitales gratuitas. Más tiempo para crear.

Este SDK forma parte del ecosistema abierto de Codwelt. Toolsbox reúne 30 herramientas para imágenes, código, SEO y texto: úsalo en línea, instálalo con tu marca o contribuye con tu propia herramienta.

30 herramientas · 6 categorías · 100 % gratis · licencia MIT

Conoce Toolsbox · Usar las herramientas · Código en GitHub

🤝 Licencia y soporte

Licencia MIT. Autor y marca: Codwelt, Bogotá, Colombia y Guayaquil, Ecuador.

Canal Contacto
✉️ Correo comercial@codwelt.com
🌐 Web codwelt.com
🧰 Proyecto Toolsbox: herramientas digitales gratuitas
📘 API de Envato build.envato.com/api

Codwelt
Hecho con ❤️ por Codwelt · Parte de Toolsbox